From 1af95997c4c555cf96e954a395c4295759f00fd7 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Wed, 4 Sep 2019 22:35:29 -0400 Subject: [PATCH 001/127] Start 0.9.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ab3f2cef3..d91010e39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "0.9.0", + "version": "0.9.1-dev", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From 99791690247f27972c314ffd2273d258c17da0ed Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 26 Sep 2019 10:28:50 -0400 Subject: [PATCH 002/127] Ensure given IV matches block size. --- lib/cipherModes.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/cipherModes.js b/lib/cipherModes.js index 1f2d41ddc..339915cc1 100644 --- a/lib/cipherModes.js +++ b/lib/cipherModes.js @@ -119,7 +119,7 @@ modes.cbc.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } else { // save IV as "previous" block - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._prev = this._iv.slice(0); } }; @@ -215,7 +215,7 @@ modes.cfb.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } // use IV as first input - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._inBlock = this._iv.slice(0); this._partialBytes = 0; }; @@ -359,7 +359,7 @@ modes.ofb.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } // use IV as first input - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._inBlock = this._iv.slice(0); this._partialBytes = 0; }; @@ -444,7 +444,7 @@ modes.ctr.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } // use IV as first input - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._inBlock = this._iv.slice(0); this._partialBytes = 0; }; @@ -954,7 +954,7 @@ modes.gcm.prototype.generateSubHashTable = function(mid, bits) { /** Utility functions */ -function transformIV(iv) { +function transformIV(iv, blockSize) { if(typeof iv === 'string') { // convert iv string into byte buffer iv = forge.util.createBuffer(iv); @@ -968,9 +968,21 @@ function transformIV(iv) { iv.putByte(tmp[i]); } } + + if(iv.length() < blockSize) { + throw new Error( + 'Invalid IV length; got ' + iv.length() + + ' bytes and expected ' + blockSize + ' bytes.'); + } + if(!forge.util.isArray(iv)) { // convert iv byte buffer into 32-bit integer array - iv = [iv.getInt32(), iv.getInt32(), iv.getInt32(), iv.getInt32()]; + var ints = []; + var blocks = blockSize / 4; + for(var i = 0; i < blocks; ++i) { + ints.push(iv.getInt32()); + } + iv = ints; } return iv; From 22647b56475404169f8085e33b78564ff0dd2a7d Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Thu, 26 Sep 2019 15:07:56 +0100 Subject: [PATCH 003/127] Add DES-CTR tests --- tests/unit/des.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/unit/des.js b/tests/unit/des.js index 77ac035e4..086391e5f 100644 --- a/tests/unit/des.js +++ b/tests/unit/des.js @@ -61,6 +61,48 @@ 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/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/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'); + }); + // OpenSSL equivalent: // openssl enc -des-ede3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -nosalt it('should 3des-ecb encrypt: foobar', function() { From e7b9a796e67ced6971a9bae62636df2a6499ece7 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Thu, 26 Sep 2019 15:34:07 +0100 Subject: [PATCH 004/127] Add des-ctr decryption tests --- tests/unit/des.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/unit/des.js b/tests/unit/des.js index 086391e5f..37794de5d 100644 --- a/tests/unit/des.js +++ b/tests/unit/des.js @@ -75,6 +75,20 @@ var UTIL = require('../../lib/util'); 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( @@ -89,6 +103,20 @@ var UTIL = require('../../lib/util'); 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( @@ -103,6 +131,20 @@ var UTIL = require('../../lib/util'); 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() { From f178523168b006f9ae094f13c61a61a36aa6cb4b Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 26 Sep 2019 14:24:01 -0400 Subject: [PATCH 005/127] Check DES-CBC short IV. --- tests/unit/des.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/unit/des.js b/tests/unit/des.js index 37794de5d..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() { From 7b6c7e5762b9ad803be4ab671cb3ffadb521b832 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 26 Sep 2019 14:27:20 -0400 Subject: [PATCH 006/127] Update changelog. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 370fe3480..4c9a963d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ Forge ChangeLog =============== +### Fixed +- Ensure DES-CBC given IV is long enough for block size. + ## 0.9.0 - 2019-09-04 ### Added From 197218e159cbf82c611da91994c1126e2cfa35c3 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 26 Sep 2019 15:01:35 -0400 Subject: [PATCH 007/127] Release 0.9.1. --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c9a963d6..a2965cd38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ Forge ChangeLog =============== +## 0.9.1 - 2019-09-26 + ### Fixed - Ensure DES-CBC given IV is long enough for block size. diff --git a/package.json b/package.json index d91010e39..ce9b1c3b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "0.9.1-dev", + "version": "0.9.1", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From 628b17fb5bc0bf22074b632432ffa535805a1b2b Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 26 Sep 2019 15:02:01 -0400 Subject: [PATCH 008/127] Start 0.9.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce9b1c3b8..5d5b2856e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "0.9.1", + "version": "0.9.2-dev", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From c5ab1e503dfd3dedf2de3dd5ce39b8387a0b0f48 Mon Sep 17 00:00:00 2001 From: Matthew Collier Date: Wed, 29 Jul 2020 12:21:13 -0400 Subject: [PATCH 009/127] Ignore .vscode. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3c9cc9129..134b7dedd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ .nyc_output .project .settings +.vscode TAGS coverage dist From e955dcb34f7d5653c3020b81aee09e992e1eab0a Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 1 Sep 2020 21:05:56 -0400 Subject: [PATCH 010/127] Add util.setPath security notes. --- CHANGELOG.md | 20 ++++++++++++++++++++ README.md | 2 ++ lib/util.js | 3 +++ 3 files changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2965cd38..0c0879244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ Forge ChangeLog =============== +### 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 diff --git a/README.md b/README.md index 40bf29561..ae7fc3cbe 100644 --- a/README.md +++ b/README.md @@ -2035,6 +2035,8 @@ When using this code please keep the following in mind: - Certain features in this library are less susceptible to attacks depending on usage. This primarily includes features that deal with data format manipulation or those that are not involved in communication. +- Do not pass unsafe inputs to `util.setPath`. Doing so could expose a + prototype pollution security issue. Library Background ------------------ diff --git a/lib/util.js b/lib/util.js index a86609284..a6ad158f3 100644 --- a/lib/util.js +++ b/lib/util.js @@ -2520,6 +2520,9 @@ util.makeLink = function(path, query, fragment) { * leaf nodes are given as non-final path keys. * Used to avoid exceptions from missing parts of the path. * + * SECURITY NOTE: Do not use unsafe inputs. Doing so could expose a prototype + * pollution security issue. + * * @param object the starting object. * @param keys an array of string keys. * @param value the value to set. From bf049a46d94d22da095f3c294ad5fbdcaa3f3f16 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 1 Sep 2020 21:20:08 -0400 Subject: [PATCH 011/127] Release 0.9.2. --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c0879244..cf06499a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ Forge ChangeLog =============== +## 0.9.2 - 2019-09-01 + ### Changed - Added `util.setPath` security note to function docs and to README. diff --git a/package.json b/package.json index 5d5b2856e..6c78da7b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "0.9.2-dev", + "version": "0.9.2", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From c470b83ef8433d28d393790cd1a3a38408b663b4 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 1 Sep 2020 21:20:37 -0400 Subject: [PATCH 012/127] Start 0.9.3. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c78da7b7..3488ad4fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "0.9.2", + "version": "0.9.3-dev", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From dfa516cc1bc577616d17e3301dcbcc5dd8a7a864 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 11 Feb 2020 18:27:08 -0500 Subject: [PATCH 013/127] Update eslint config. --- .eslintrc.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5477843af..11e4c6eb4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,10 +1,13 @@ module.exports = { + root: true, env: { browser: true, commonjs: true, node: true }, - extends: ['eslint-config-digitalbazaar'], + extends: [ + 'digitalbazaar' + ], parserOptions: { ecmaVersion: 5 }, From ba0207fa359f79a5af0e71d12f488a846e9265f6 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 11 Feb 2020 18:27:27 -0500 Subject: [PATCH 014/127] Test on Node.js 12. --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 24b8a8454..acf170044 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ node_js: - "6" - "8" - "10" + - "12" - "node" sudo: false install: npm install @@ -13,9 +14,9 @@ script: # only run karma tests for one node version matrix: include: - - node_js: "10" + - node_js: "12" env: BUNDLER=webpack - - node_js: "10" + - node_js: "12" env: BUNDLER=browserify notifications: email: From afc5a728f5c6bf5cb80eddb5b85c7340b28bfa51 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 11 Feb 2020 18:33:54 -0500 Subject: [PATCH 015/127] Update dependencies. --- package.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 3488ad4fb..20834a86e 100644 --- a/package.json +++ b/package.json @@ -17,28 +17,28 @@ "devDependencies": { "browserify": "^16.1.0", "commander": "^2.20.0", - "cross-env": "^5.1.3", + "cross-env": "^5.2.1", "eslint": "^5.16.0", - "eslint-config-digitalbazaar": "^2.0.0", + "eslint-config-digitalbazaar": "^2.2.0", "express": "^4.16.2", - "karma": "^3.1.4", - "karma-browserify": "^6.0.0", - "karma-chrome-launcher": "^2.2.0", + "karma": "^4.4.1", + "karma-browserify": "^7.0.0", + "karma-chrome-launcher": "^3.1.0", "karma-edge-launcher": "^0.4.2", - "karma-firefox-launcher": "^1.1.0", + "karma-firefox-launcher": "^1.3.0", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^1.3.0", "karma-mocha-reporter": "^2.2.5", "karma-safari-launcher": "^1.0.0", - "karma-sauce-launcher": "^1.2.0", + "karma-sauce-launcher": "^2.0.2", "karma-sourcemap-loader": "^0.3.7", "karma-tap-reporter": "0.0.6", - "karma-webpack": "^3.0.5", + "karma-webpack": "^4.0.2", "mocha": "^5.2.0", "mocha-lcov-reporter": "^1.2.0", "nodejs-websocket": "^1.7.1", - "nyc": "^14.1.1", - "opts": "^1.2.2", + "nyc": "^15.0.0", + "opts": "^1.2.7", "webpack": "^3.11.0", "worker-loader": "^2.0.0" }, From c8d5395e078f838604eb9df32ef9d298288057fd Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 11 Feb 2020 18:57:09 -0500 Subject: [PATCH 016/127] Add travis browser test names. --- .travis.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index acf170044..31c1915b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,16 +7,19 @@ node_js: - "12" - "node" sudo: false -install: npm install +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: "12" + - name: "Browser Unit Tests (webpack)" + node_js: "12" env: BUNDLER=webpack - - node_js: "12" + - name: "Browser Unit Tests (browserify)" + node_js: "12" env: BUNDLER=browserify notifications: email: From ba13a1c8bfdb1d6abb286fa7fcb82a57b40ae345 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 11 Feb 2020 19:04:41 -0500 Subject: [PATCH 017/127] Update webpack. --- karma.conf.js | 1 + package.json | 3 ++- webpack.config.js | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index 996a5adf4..a61407dc1 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -55,6 +55,7 @@ module.exports = function(config) { }, webpack: { + mode: 'development', devtool: 'inline-source-map', node: { Buffer: false, diff --git a/package.json b/package.json index 20834a86e..d40f0bba1 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "nodejs-websocket": "^1.7.1", "nyc": "^15.0.0", "opts": "^1.2.7", - "webpack": "^3.11.0", + "webpack": "^4.41.6", + "webpack-cli": "^3.3.11", "worker-loader": "^2.0.0" }, "repository": { diff --git a/webpack.config.js b/webpack.config.js index 806401eff..df0db4d34 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,7 +6,6 @@ * Copyright 2011-2016 Digital Bazaar, Inc. */ const path = require('path'); -const webpack = require('webpack'); // build multiple outputs module.exports = []; @@ -79,6 +78,7 @@ outputs.forEach(info => { // plain unoptimized unminified bundle const bundle = Object.assign({}, common, { + mode: 'development', output: { path: path.join(__dirname, 'dist'), filename: info.filenameBase + '.js', @@ -95,6 +95,7 @@ outputs.forEach(info => { // optimized and minified bundle const minify = Object.assign({}, common, { + mode: 'production', output: { path: path.join(__dirname, 'dist'), filename: info.filenameBase + '.min.js', @@ -103,6 +104,7 @@ outputs.forEach(info => { }, devtool: 'cheap-module-source-map', plugins: [ + /* new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { @@ -113,6 +115,7 @@ outputs.forEach(info => { } //beautify: true }) + */ ] }); if(info.library === null) { From 7b59028142bb5f5e299df0d0931a8306d7d7046b Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 1 Sep 2020 16:48:33 -0400 Subject: [PATCH 018/127] Test on Node.js 14. --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 31c1915b5..54aa4b543 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ node_js: - "8" - "10" - "12" + - "14" - "node" sudo: false install: @@ -16,10 +17,10 @@ script: matrix: include: - name: "Browser Unit Tests (webpack)" - node_js: "12" + node_js: "14" env: BUNDLER=webpack - name: "Browser Unit Tests (browserify)" - node_js: "12" + node_js: "14" env: BUNDLER=browserify notifications: email: From 81abd87a9a8ef705af17d1e712a086ef4b7869a1 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 1 Sep 2020 19:46:05 -0400 Subject: [PATCH 019/127] Improve linting. - Code is very out-of-date with proper style. - Fix so it at least shows errors with modern eslint. - Disable some rules with too many hits for current style. --- .eslintrc.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 11e4c6eb4..7fa4468b3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,13 +9,16 @@ module.exports = { 'digitalbazaar' ], parserOptions: { - ecmaVersion: 5 + ecmaVersion: 5, + sourceType: 'script' }, rules: { // overrides to support ES5, remove when updated to ES20xx 'no-unused-vars': 'warn', 'no-var': 'off', 'object-shorthand': 'off', - 'prefer-const': 'off' + 'prefer-const': 'off', + // fix when code is globally reformatted + 'max-len': 'off' } }; From 1ba83ecca436c2e1d0d37ea154467e9934019504 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 1 Sep 2020 19:47:31 -0400 Subject: [PATCH 020/127] Update dependencies. --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d40f0bba1..ac2f4c375 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,11 @@ "Christoph Dorn " ], "devDependencies": { - "browserify": "^16.1.0", + "browserify": "^16.5.2", "commander": "^2.20.0", "cross-env": "^5.2.1", - "eslint": "^5.16.0", - "eslint-config-digitalbazaar": "^2.2.0", + "eslint": "^7.8.1", + "eslint-config-digitalbazaar": "^2.5.0", "express": "^4.16.2", "karma": "^4.4.1", "karma-browserify": "^7.0.0", @@ -31,16 +31,16 @@ "karma-mocha-reporter": "^2.2.5", "karma-safari-launcher": "^1.0.0", "karma-sauce-launcher": "^2.0.2", - "karma-sourcemap-loader": "^0.3.7", + "karma-sourcemap-loader": "^0.3.8", "karma-tap-reporter": "0.0.6", "karma-webpack": "^4.0.2", "mocha": "^5.2.0", "mocha-lcov-reporter": "^1.2.0", "nodejs-websocket": "^1.7.1", - "nyc": "^15.0.0", + "nyc": "^15.1.0", "opts": "^1.2.7", - "webpack": "^4.41.6", - "webpack-cli": "^3.3.11", + "webpack": "^4.44.1", + "webpack-cli": "^3.3.12", "worker-loader": "^2.0.0" }, "repository": { From 30d560c6b522b9a97a67d9101ecf8f860a4ba63a Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 1 Sep 2020 19:58:39 -0400 Subject: [PATCH 021/127] Remove Node.js 4 support. --- .travis.yml | 1 - CHANGELOG.md | 5 +++++ package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 54aa4b543..6ea8d7085 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: node_js node_js: - - "4" - "6" - "8" - "10" diff --git a/CHANGELOG.md b/CHANGELOG.md index cf06499a5..10cc588a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Forge ChangeLog =============== +- **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 very old Node.js versions making testing + difficult. + ## 0.9.2 - 2019-09-01 ### Changed diff --git a/package.json b/package.json index ac2f4c375..1f4afd2e8 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "dist/*.min.js.map" ], "engines": { - "node": ">= 4.5.0" + "node": ">= 6.0.0" }, "keywords": [ "aes", From 6a1e3ef74f6eb345bcff1b82184201d1e28b6756 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 1 Sep 2020 21:37:39 -0400 Subject: [PATCH 022/127] Remove object path functions. - Remove `util.getPath`, `util.setPath`, and `util.deletePath`. - These are unused in `forge` itself. - Path processing has potential security issues. (For `setPath` in particular). - `lodash` has better replacements: `get`, `set`, and `unset`. - See also: - CHANGELOG.md 0.9.2 entry. - https://snyk.io/vuln/SNYK-JS-NODEFORGE-598677 - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-7720 --- CHANGELOG.md | 15 ++++++-- README.md | 2 -- lib/util.js | 96 ---------------------------------------------------- 3 files changed, 13 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10cc588a0..e3f1bfc5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,21 @@ Forge ChangeLog =============== +### 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 very old Node.js versions making testing - difficult. + 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, consier `get`, `set`, and `unset` + from [lodash](https://lodash.com/). But also consider the potential similar + security issues with those APIs. ## 0.9.2 - 2019-09-01 diff --git a/README.md b/README.md index ae7fc3cbe..40bf29561 100644 --- a/README.md +++ b/README.md @@ -2035,8 +2035,6 @@ When using this code please keep the following in mind: - Certain features in this library are less susceptible to attacks depending on usage. This primarily includes features that deal with data format manipulation or those that are not involved in communication. -- Do not pass unsafe inputs to `util.setPath`. Doing so could expose a - prototype pollution security issue. Library Background ------------------ diff --git a/lib/util.js b/lib/util.js index a6ad158f3..98dfd3427 100644 --- a/lib/util.js +++ b/lib/util.js @@ -2513,102 +2513,6 @@ util.makeLink = function(path, query, fragment) { ((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. - * - * SECURITY NOTE: Do not use unsafe inputs. Doing so could expose a prototype - * pollution security issue. - * - * @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. * From 8018c3ea6f8e75d2df1b9ce4ec9c536db3db37e6 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 1 Sep 2020 22:01:59 -0400 Subject: [PATCH 023/127] Release 0.10.0. --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3f1bfc5d..81176fa81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ Forge ChangeLog =============== +## 0.10.0 - 2019-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 diff --git a/package.json b/package.json index 1f4afd2e8..f5cc879f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "0.9.3-dev", + "version": "0.10.0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From 9d286917dba012b6e2c991f694763c590671a967 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 1 Sep 2020 22:02:28 -0400 Subject: [PATCH 024/127] Start 0.10.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f5cc879f9..997982278 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "0.10.0", + "version": "0.10.1-dev", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From 588c41062d9a13f8dc91be3723b159c6cc434b15 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Wed, 2 Sep 2020 10:28:14 -0400 Subject: [PATCH 025/127] Fix release dates. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81176fa81..91d13bdbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Forge ChangeLog =============== -## 0.10.0 - 2019-09-01 +## 0.10.0 - 2020-09-01 ### Changed - **BREAKING**: Node.js 4 no longer supported. The code *may* still work, and @@ -19,7 +19,7 @@ Forge ChangeLog from [lodash](https://lodash.com/). But also consider the potential similar security issues with those APIs. -## 0.9.2 - 2019-09-01 +## 0.9.2 - 2020-09-01 ### Changed - Added `util.setPath` security note to function docs and to README. From e06afc4faa100f4363b013ea6f70002308072c6c Mon Sep 17 00:00:00 2001 From: troyfactor4 <5209556+troyfactor4@users.noreply.github.com> Date: Mon, 16 Nov 2020 01:45:48 -0400 Subject: [PATCH 026/127] fix: make PKCS#7 parameter optional as per RFC 5280 --- lib/pkcs7.js | 2 +- lib/pkcs7asn1.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pkcs7.js b/lib/pkcs7.js index bb87de363..95c54e361 100644 --- a/lib/pkcs7.js +++ b/lib/pkcs7.js @@ -837,7 +837,7 @@ function _recipientFromAsn1(obj) { serialNumber: forge.util.createBuffer(capture.serial).toHex(), encryptedContent: { algorithm: asn1.derToOid(capture.encAlgorithm), - parameter: capture.encParameter.value, + parameter: capture.encParameter ? capture.encParameter.value : null, content: capture.encKey } }; 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', From 8d7595b95e0c0c39ef82d8fdb5b3d890debd2bb7 Mon Sep 17 00:00:00 2001 From: troyfactor4 <5209556+troyfactor4@users.noreply.github.com> Date: Tue, 17 Nov 2020 13:11:56 -0400 Subject: [PATCH 027/127] fix: null check ec.parameter --- lib/pkcs7.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pkcs7.js b/lib/pkcs7.js index 95c54e361..3e6cfe931 100644 --- a/lib/pkcs7.js +++ b/lib/pkcs7.js @@ -1124,8 +1124,10 @@ 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 ? + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, + ec.parameter.getBytes()) : undefined + ) ]), // [0] EncryptedContent asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ From 4292496097d8756bdaeabd85b9cb7520cd80e5f4 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Wed, 18 Nov 2020 15:14:56 -0500 Subject: [PATCH 028/127] Apply suggestions from code review. --- lib/pkcs7.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/pkcs7.js b/lib/pkcs7.js index 3e6cfe931..3a5d845c5 100644 --- a/lib/pkcs7.js +++ b/lib/pkcs7.js @@ -837,7 +837,7 @@ function _recipientFromAsn1(obj) { serialNumber: forge.util.createBuffer(capture.serial).toHex(), encryptedContent: { algorithm: asn1.derToOid(capture.encAlgorithm), - parameter: capture.encParameter ? capture.encParameter.value : null, + parameter: capture.encParameter ? capture.encParameter.value : undefined, content: capture.encKey } }; @@ -1124,10 +1124,11 @@ function _encryptedContentToAsn1(ec) { asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, asn1.oidToDer(ec.algorithm).getBytes()), // Parameters (IV) - ( ec.parameter ? - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, - ec.parameter.getBytes()) : undefined - ) + !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, [ From c666282c812d6dc18e97b419b152dd6ad98c802c Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Thu, 25 Mar 2021 10:21:35 +0000 Subject: [PATCH 029/127] Remove link to nodeguide.com It appears nodeguide.com domain has been taken over by domain squatters and the style.html page appears to push a questionable PDF download. --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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/ From 724158f264be5ad691d1546347a94a6214bd25cf Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Wed, 7 Apr 2021 03:21:46 -0400 Subject: [PATCH 030/127] Remove `forge.task` API. Task API still used in tests so moved to `tests/support/` and files updated appropriately. --- CHANGELOG.md | 9 +++++++++ lib/index.js | 1 - tests/issues/issue-428.html | 1 + tests/issues/issue-428.js | 2 +- tests/legacy/common.html | 1 + tests/legacy/common.js | 2 +- tests/legacy/tasks.html | 1 + tests/legacy/tasks.js | 4 ++-- tests/legacy/xhr.html | 1 + tests/legacy/xhr.js | 2 +- tests/server.js | 2 ++ {lib => tests/support}/task.js | 21 +++++++++------------ 12 files changed, 29 insertions(+), 18 deletions(-) rename {lib => tests/support}/task.js (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91d13bdbb..cfe650070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Forge ChangeLog =============== +## 0.11.0 - 2021-xx-xx + +### Removed +- **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/`. + ## 0.10.0 - 2020-09-01 ### Changed diff --git a/lib/index.js b/lib/index.js index ea8c14cf9..ffb931286 100644 --- a/lib/index.js +++ b/lib/index.js @@ -30,6 +30,5 @@ require('./pss'); require('./random'); require('./rc2'); require('./ssh'); -require('./task'); require('./tls'); require('./util'); diff --git a/tests/issues/issue-428.html b/tests/issues/issue-428.html index fc9af30c6..1685c3e1f 100644 --- a/tests/issues/issue-428.html +++ b/tests/issues/issue-428.html @@ -4,6 +4,7 @@ Forge Issue 428 Test + diff --git a/tests/issues/issue-428.js b/tests/issues/issue-428.js index 7b9427e4a..179477d42 100644 --- a/tests/issues/issue-428.js +++ b/tests/issues/issue-428.js @@ -52,7 +52,7 @@ jQuery(function($) { $('#start').attr('disabled', 'true'); $('#stop').attr('disabled', ''); // meta! use tasks to run the task tests - forge.task.start({ + forge_task.start({ type: 'test', run: function(task) { task.next('starting', function(task) { diff --git a/tests/legacy/common.html b/tests/legacy/common.html index 9ee073117..d835af37f 100644 --- a/tests/legacy/common.html +++ b/tests/legacy/common.html @@ -4,6 +4,7 @@ Forge Common Tests + diff --git a/tests/legacy/common.js b/tests/legacy/common.js index 57dfbc4ba..4c08e1319 100644 --- a/tests/legacy/common.js +++ b/tests/legacy/common.js @@ -39,7 +39,7 @@ jQuery(function($) { $('#start').attr('disabled', 'true'); // meta! use tasks to run the task tests - forge.task.start({ + forge_task.start({ type: 'test', run: function(task) { task.next('starting', function(task) { diff --git a/tests/legacy/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..5bf8e465a 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 @@ -277,7 +274,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 +342,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 +378,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 +615,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 +639,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 +670,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 +685,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: {} From 07942ef36461e6fff9bb50dcdabb0dac78643fb5 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 27 May 2021 21:55:10 -0400 Subject: [PATCH 031/127] Update eslint dependencies. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 997982278..0636212bb 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "browserify": "^16.5.2", "commander": "^2.20.0", "cross-env": "^5.2.1", - "eslint": "^7.8.1", - "eslint-config-digitalbazaar": "^2.5.0", + "eslint": "^7.27.0", + "eslint-config-digitalbazaar": "^2.8.0", "express": "^4.16.2", "karma": "^4.4.1", "karma-browserify": "^7.0.0", From 5d09946b713ff54bd963c47dfee56d9d3d54c680 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 27 May 2021 21:55:25 -0400 Subject: [PATCH 032/127] Add eslint config for tests. --- tests/.eslintrc.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/.eslintrc.js diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js new file mode 100644 index 000000000..bf3479fb6 --- /dev/null +++ b/tests/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + env: { + mocha: true + } +}; From 51228083550dde97701ac8e06c629a5184117562 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 27 May 2021 21:58:45 -0400 Subject: [PATCH 033/127] 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 inteneded to be used with untrusted user intputs. This API was not documented or advertised and is being removed rather than fixed. --- CHANGELOG.md | 5 +++ README.md | 14 -------- lib/debug.js | 78 ------------------------------------------- lib/http.js | 11 ------ lib/index.js | 1 - tests/support/task.js | 4 --- 6 files changed, 5 insertions(+), 108 deletions(-) delete mode 100644 lib/debug.js diff --git a/CHANGELOG.md b/CHANGELOG.md index cfe650070..86241ba04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Forge ChangeLog ## 0.11.0 - 2021-xx-xx ### 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 inteneded to be + used with untrusted user intputs. This API was not documented or advertised + and is being removed rather than fixed. - **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. diff --git a/README.md b/README.md index 40bf29561..bfc640fee 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,6 @@ Documentation * [Tasks](#task) * [Utilities](#util) * [Logging](#log) -* [Debugging](#debug) * [Flash Networking Support](#flash) ### Other @@ -1988,19 +1987,6 @@ __Examples__ // TODO ``` - - -### Debugging - -Provides storage of debugging information normally inaccessible in -closures for viewing/investigation. - -__Examples__ - -```js -// TODO -``` - ### Flash Networking Support diff --git a/lib/debug.js b/lib/debug.js deleted file mode 100644 index 26756350e..000000000 --- a/lib/debug.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Debugging support for web applications. - * - * @author David I. Lehn - * - * Copyright 2008-2013 Digital Bazaar, Inc. - */ -var forge = require('./forge'); - -/* DEBUG API */ -module.exports = forge.debug = forge.debug || {}; - -// Private storage for debugging. -// Useful to expose data that is otherwise unviewable behind closures. -// NOTE: remember that this can hold references to data and cause leaks! -// format is "forge._debug.. = data" -// Example: -// (function() { -// var cat = 'forge.test.Test'; // debugging category -// var sState = {...}; // local state -// forge.debug.set(cat, 'sState', sState); -// })(); -forge.debug.storage = {}; - -/** - * Gets debug data. Omit name for all cat data Omit name and cat for - * all data. - * - * @param cat name of debugging category. - * @param name name of data to get (optional). - * @return object with requested debug data or undefined. - */ -forge.debug.get = function(cat, name) { - var rval; - if(typeof(cat) === 'undefined') { - rval = forge.debug.storage; - } else if(cat in forge.debug.storage) { - if(typeof(name) === 'undefined') { - rval = forge.debug.storage[cat]; - } else { - rval = forge.debug.storage[cat][name]; - } - } - return rval; -}; - -/** - * Sets debug data. - * - * @param cat name of debugging category. - * @param name name of data to set. - * @param data data to set. - */ -forge.debug.set = function(cat, name, data) { - if(!(cat in forge.debug.storage)) { - forge.debug.storage[cat] = {}; - } - forge.debug.storage[cat][name] = data; -}; - -/** - * Clears debug data. Omit name for all cat data. Omit name and cat for - * all data. - * - * @param cat name of debugging category. - * @param name name of data to clear or omit to clear entire category. - */ -forge.debug.clear = function(cat, name) { - if(typeof(cat) === 'undefined') { - forge.debug.storage = {}; - } else if(cat in forge.debug.storage) { - if(typeof(name) === 'undefined') { - delete forge.debug.storage[cat]; - } else { - delete forge.debug.storage[cat][name]; - } - } -}; diff --git a/lib/http.js b/lib/http.js index 1dcb0a65e..0ae863050 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, @@ -484,11 +478,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); diff --git a/lib/index.js b/lib/index.js index ffb931286..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'); diff --git a/tests/support/task.js b/tests/support/task.js index 5bf8e465a..4607ecb12 100644 --- a/tests/support/task.js +++ b/tests/support/task.js @@ -24,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 = '?'; From 7aa796efd838422cfd216f6472e7444c1b57bf0d Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 9 Sep 2021 22:33:10 -0400 Subject: [PATCH 034/127] Switch from travis to github actions. --- .github/workflows/main.yml | 72 ++++++++++++++++++++++++++++++++++++++ .travis.yml | 27 -------------- package.json | 1 + 3 files changed, 73 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..927425990 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,72 @@ +name: Node.js CI + +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] + env: + 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 + 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/.travis.yml b/.travis.yml deleted file mode 100644 index 6ea8d7085..000000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: node_js -node_js: - - "6" - - "8" - - "10" - - "12" - - "14" - - "node" -sudo: false -install: - - npm install -script: - - if [ "x$BUNDLER" = "x" ]; then npm test; fi - - if [ "x$BUNDLER" != "x" ]; then npm run test-karma; fi -# only run karma tests for one node version -matrix: - include: - - name: "Browser Unit Tests (webpack)" - node_js: "14" - env: BUNDLER=webpack - - name: "Browser Unit Tests (browserify)" - node_js: "14" - env: BUNDLER=browserify -notifications: - email: - on_success: change - on_failure: change diff --git a/package.json b/package.json index 0636212bb..60fb1f83c 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "test-server-ws": "node tests/websockets/server-ws.js", "test-server-webid": "node tests/websockets/server-webid.js", "coverage": "rm -rf coverage && nyc --reporter=lcov --reporter=text-summary npm test", + "coverage-ci": "rm -rf coverage && nyc --reporter=lcovonly npm test", "coverage-report": "nyc report", "lint": "eslint *.js lib/*.js tests/*.js tests/**/*.js examples/*.js flash/*.js" }, From cbebc13ffdf4ed97cab5d0b4a2cefaff4e4c6fc8 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 9 Sep 2021 22:39:41 -0400 Subject: [PATCH 035/127] Fix workflow. --- .github/workflows/main.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 927425990..2223c7a94 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,8 +24,7 @@ jobs: strategy: matrix: node-version: [14.x] - env: - BUNDLER: [webpack, browserify] + bundler: [webpack, browserify] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} @@ -35,6 +34,8 @@ jobs: - run: npm install - name: Run karma tests run: npm run test-karma + env: + BUNDLER: ${{ matrix.bundler }} lint: runs-on: ubuntu-latest timeout-minutes: 10 From bff212370e595f77faa9e4e4063e3b2c636026d6 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 9 Sep 2021 22:43:25 -0400 Subject: [PATCH 036/127] Add 'test-node' script target. --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 60fb1f83c..f54ff72d3 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,8 @@ "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", From 423b2f32b2b81153acbf4699ca6da234dd45368e Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 9 Sep 2021 22:45:16 -0400 Subject: [PATCH 037/127] Disable lint check. Code is not even close to ready for the modern digitalbazaar linting style. --- .github/workflows/main.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2223c7a94..3549c9294 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,21 +36,21 @@ jobs: 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 +# 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 From dc9aa5e270b3bb7c200d8cac1f161eab2867b802 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 9 Sep 2021 22:54:51 -0400 Subject: [PATCH 038/127] Rename main workflow. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3549c9294..a63a29963 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Node.js CI +name: Main Checks on: [push] From 99676ae88403178285f25f9948e55510d39c4734 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 9 Sep 2021 22:55:11 -0400 Subject: [PATCH 039/127] Update main checks workflow badge. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bfc640fee..2e7ec3e64 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][]. From f981667d2d3c0f7437090a8e2bff520252df78da Mon Sep 17 00:00:00 2001 From: Renze Nicolai Date: Sun, 14 Mar 2021 13:13:18 +0100 Subject: [PATCH 040/127] Add OIDs for surname, title and givenName --- lib/oids.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/oids.js b/lib/oids.js index 6a937f571..1c8d65a1f 100644 --- a/lib/oids.js +++ b/lib/oids.js @@ -104,6 +104,7 @@ _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.4', 'surname'); _IN('2.5.4.5', 'serialName'); _IN('2.5.4.6', 'countryName'); _IN('2.5.4.7', 'localityName'); @@ -111,9 +112,11 @@ _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'); From 4d9a7939314815623885bd601e1cc64a934aa175 Mon Sep 17 00:00:00 2001 From: Renze Nicolai Date: Fri, 26 Mar 2021 14:12:31 +0100 Subject: [PATCH 041/127] Fix spacing Co-authored-by: Daniel Hensby --- lib/oids.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/oids.js b/lib/oids.js index 1c8d65a1f..2148297c4 100644 --- a/lib/oids.js +++ b/lib/oids.js @@ -104,7 +104,7 @@ _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.4', 'surname'); +_IN('2.5.4.4', 'surname'); _IN('2.5.4.5', 'serialName'); _IN('2.5.4.6', 'countryName'); _IN('2.5.4.7', 'localityName'); From 66145112894b8cefa94a58f1f4656407d243e9ee Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 9 Sep 2021 23:26:12 -0400 Subject: [PATCH 042/127] Update changelog. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86241ba04..8060d8eb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Forge ChangeLog Due to use in the test suite, a modified version is located in `tests/support/`. +### Added +- OIDs for `surname`, `title`, and `givenName`. + ## 0.10.0 - 2020-09-01 ### Changed From e01b2ee72cf1901258ebfcb2e9852a917eb40bfe Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 9 Sep 2021 23:33:31 -0400 Subject: [PATCH 043/127] Fix typos. --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8060d8eb6..5e06ef755 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ Forge ChangeLog ### 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 inteneded to be - used with untrusted user intputs. This API was not documented or advertised + 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. - **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 @@ -32,7 +32,7 @@ Forge ChangeLog from an early time when `forge` was targeted at providing general helper functions. The library direction changed to be more focused on cryptography. Many other excellent libraries are more suitable for general utilities. If - you need a replacement for these functions, consier `get`, `set`, and `unset` + you need a replacement for these functions, consider `get`, `set`, and `unset` from [lodash](https://lodash.com/). But also consider the potential similar security issues with those APIs. From 219bbb2a566d6f8169739d4887a4ab55d6a220b6 Mon Sep 17 00:00:00 2001 From: Ziding Zhang Date: Fri, 10 Sep 2021 12:57:38 +0100 Subject: [PATCH 044/127] Create SECURITY.md A simple instruction for security researchers. Closes #907 --- SECURITY.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..dc070c111 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report security issues to `security@digitalbazaar.com` From c90cd85104e9167703e7a25f6b88e7febc9aa35a Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Fri, 10 Sep 2021 12:43:30 -0400 Subject: [PATCH 045/127] Use plain email. Plain email will let markdown render as a mailto link. --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index dc070c111..090cbbc12 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,4 +2,4 @@ ## Reporting a Vulnerability -Please report security issues to `security@digitalbazaar.com` +Please report security issues to security@digitalbazaar.com. From c0bb359afca73bb0f3ba6feb3f93bbcb9166af2e Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 11 Oct 2021 18:07:48 +0100 Subject: [PATCH 046/127] Fix double call of String.fromCharCode. --- lib/prng.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prng.js b/lib/prng.js index c2f5f0518..d3bd22e05 100644 --- a/lib/prng.js +++ b/lib/prng.js @@ -317,7 +317,7 @@ prng.create = function(plugin) { // throw in more pseudo random next = seed >>> (i << 3); next ^= Math.floor(Math.random() * 0x0100); - b.putByte(String.fromCharCode(next & 0xFF)); + b.putByte(next & 0xFF); } } } From 6a10f7c5bad32286fd2a02eac350109f2333a272 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 9 Sep 2021 23:35:17 -0400 Subject: [PATCH 047/127] Fix OID `serialName` to `serialNumber`. - OID 2.5.4.5 should be `serialNumber`. --- CHANGELOG.md | 5 +++++ lib/oids.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e06ef755..3071b09ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,11 @@ Forge ChangeLog ### 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 diff --git a/lib/oids.js b/lib/oids.js index 2148297c4..1c86c2189 100644 --- a/lib/oids.js +++ b/lib/oids.js @@ -105,7 +105,7 @@ _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.4', 'surname'); -_IN('2.5.4.5', 'serialName'); +_IN('2.5.4.5', 'serialNumber'); _IN('2.5.4.6', 'countryName'); _IN('2.5.4.7', 'localityName'); _IN('2.5.4.8', 'stateOrProvinceName'); From e1a740d0be6c773af1840e0f0620994b8beeb020 Mon Sep 17 00:00:00 2001 From: ctcpip Date: Fri, 20 Aug 2021 14:10:25 -0500 Subject: [PATCH 048/127] =?UTF-8?q?=F0=9F=94=92=20change=20CSR=20examples?= =?UTF-8?q?=20to=20use=202048=20bits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- examples/create-cert.js | 4 ++-- examples/create-csr.js | 4 ++-- examples/create-pkcs12.js | 4 ++-- examples/sign-p7.js | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2e7ec3e64..c308ebe06 100644 --- a/README.md +++ b/README.md @@ -1451,7 +1451,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(); diff --git a/examples/create-cert.js b/examples/create-cert.js index 365f5a782..03df72c95 100644 --- a/examples/create-cert.js +++ b/examples/create-cert.js @@ -1,7 +1,7 @@ var forge = require('..'); -console.log('Generating 1024-bit key-pair...'); -var keys = forge.pki.rsa.generateKeyPair(1024); +console.log('Generating 2048-bit key-pair...'); +var keys = forge.pki.rsa.generateKeyPair(2048); console.log('Key-pair created.'); console.log('Creating self-signed certificate...'); diff --git a/examples/create-csr.js b/examples/create-csr.js index e5be773a2..8961a31fd 100644 --- a/examples/create-csr.js +++ b/examples/create-csr.js @@ -1,7 +1,7 @@ var forge = require('..'); -console.log('Generating 1024-bit key-pair...'); -var keys = forge.pki.rsa.generateKeyPair(1024); +console.log('Generating 2048-bit key-pair...'); +var keys = forge.pki.rsa.generateKeyPair(2048); console.log('Key-pair created.'); console.log('Creating certification request (CSR) ...'); diff --git a/examples/create-pkcs12.js b/examples/create-pkcs12.js index 1125fc0f1..d41965fb1 100644 --- a/examples/create-pkcs12.js +++ b/examples/create-pkcs12.js @@ -2,8 +2,8 @@ var forge = require('..'); try { // generate a keypair - console.log('Generating 1024-bit key-pair...'); - var keys = forge.pki.rsa.generateKeyPair(1024); + console.log('Generating 2048-bit key-pair...'); + var keys = forge.pki.rsa.generateKeyPair(2048); console.log('Key-pair created.'); // create a certificate diff --git a/examples/sign-p7.js b/examples/sign-p7.js index 406ce787e..73d07e566 100644 --- a/examples/sign-p7.js +++ b/examples/sign-p7.js @@ -42,8 +42,8 @@ function createSigner(name) { console.log('Creating signer "' + name + '"...'); // generate a keypair - console.log('Generating 1024-bit key-pair...'); - var keys = forge.pki.rsa.generateKeyPair(1024); + console.log('Generating 2048-bit key-pair...'); + var keys = forge.pki.rsa.generateKeyPair(2048); console.log('Key-pair created:'); console.log(forge.pki.privateKeyToPem(keys.privateKey)); console.log(forge.pki.publicKeyToPem(keys.publicKey)); From db8016c805371e72b06d8e2edfe0ace0df934a5e Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 21 Oct 2021 20:15:32 -0400 Subject: [PATCH 049/127] Remove forge.util.parseUrl. - Switch URL parsing to the WHATWG URL Standard `URL` API. - Older browser or Node.js usage of related code might now require a URL polyfill. --- CHANGELOG.md | 16 +++++++++++++ README.md | 4 ---- lib/http.js | 39 +++++++++++++------------------- lib/util.js | 37 ------------------------------ lib/xhr.js | 14 +++++++----- package.json | 2 +- tests/websockets/server-webid.js | 7 +++--- 7 files changed, 45 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3071b09ad..0bd63e496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,22 @@ Forge ChangeLog 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/`. +### Changed +- **BREAKING**: Increase supported Node.js version to 6.13.0 for URL support. + ### Added - OIDs for `surname`, `title`, and `givenName`. @@ -23,6 +33,12 @@ Forge ChangeLog Depending on how applications used this id to name association it could cause compatibility issues. +### Notes +- 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 appriciated. + ## 0.10.0 - 2020-09-01 ### Changed diff --git a/README.md b/README.md index c308ebe06..bddcffe6e 100644 --- a/README.md +++ b/README.md @@ -1968,10 +1968,6 @@ var nodeBuffer = Buffer.from(forgeBuffer.getBytes(), 'binary'); // make sure you specify the encoding as 'binary' var nodeBuffer = Buffer.from('CAFE', 'hex'); var forgeBuffer = forge.util.createBuffer(nodeBuffer.toString('binary')); - -// parse a URL -var parsed = forge.util.parseUrl('http://example.com/foo?bar=baz'); -// parsed.scheme, parsed.host, parsed.port, parsed.path, parsed.fullHost ``` diff --git a/lib/http.js b/lib/http.js index 0ae863050..fe52986b1 100644 --- a/lib/http.js +++ b/lib/http.js @@ -33,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; }; @@ -121,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 @@ -310,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 @@ -405,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 @@ -441,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; @@ -469,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: {}, @@ -497,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.' }; @@ -512,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, @@ -552,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 @@ -1307,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. * @@ -1336,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/util.js b/lib/util.js index 98dfd3427..5100eab6e 100644 --- a/lib/util.js +++ b/lib/util.js @@ -2258,43 +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; 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 f54ff72d3..22ff40e46 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "dist/*.min.js.map" ], "engines": { - "node": ">= 6.0.0" + "node": ">= 6.13.0" }, "keywords": [ "aes", diff --git a/tests/websockets/server-webid.js b/tests/websockets/server-webid.js index 6f7cf37b8..5319372bb 100644 --- a/tests/websockets/server-webid.js +++ b/tests/websockets/server-webid.js @@ -174,9 +174,10 @@ var fetchUrl = function(url, callback, redirects) { console.log('Fetching URL: \"' + url + '\"'); // parse URL - url = forge.util.parseUrl(url); - var client = http.createClient( - url.port, url.fullHost, url.scheme === 'https'); + url = new URL(url); + var client = http.createClient({ + url: url + }); var request = client.request('GET', url.path, { Host: url.host, Accept: 'application/rdf+xml' From aea85c5cb9e7a1a180298cb4fd84e39cea254e03 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 28 Dec 2021 00:11:31 -0500 Subject: [PATCH 050/127] Remove URL related APIs. **BREAKING**: Remove `forge.util.makeLink`, `forge.util.makeRequest`, `forge.util.parseFragment`, `forge.util.getQueryVariables`. Replace with `URL`, `URLSearchParams`, and custom code as needed. --- CHANGELOG.md | 3 + lib/log.js | 15 ++- lib/util.js | 218 -------------------------------------- tests/legacy/loginDemo.js | 10 +- 4 files changed, 18 insertions(+), 228 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bd63e496..1e4bd343e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ Forge ChangeLog 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. diff --git a/lib/log.js b/lib/log.js index 8d36f4a89..b8a265c20 100644 --- a/lib/log.js +++ b/lib/log.js @@ -298,15 +298,20 @@ if(typeof(console) !== 'undefined' && 'log' in console) { * that could otherwise be limited by a user config. */ if(sConsoleLogger !== null) { - var query = forge.util.getQueryVariables(); - if('console.level' in query) { + var query; + if(typeof(window) !== 'undefined' && window.location) { + query = new URL(window.location.href).searchParams; + } else { + query = new URLSearchParams(); + } + 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/util.js b/lib/util.js index 5100eab6e..aaede5ad2 100644 --- a/lib/util.js +++ b/lib/util.js @@ -2258,224 +2258,6 @@ util.clearItems = function(api, id, location) { _callStorageFunction(_clearItems, arguments, location); }; -/* Storage for query variables */ -var _queryVariables = null; - -/** - * Returns the window location query variables. Query is parsed on the first - * call and the same object is returned on subsequent calls. The mapping - * is from keys to an array of values. Parameters without values will have - * an object key set but no value added to the value array. Values are - * unescaped. - * - * ...?k1=v1&k2=v2: - * { - * "k1": ["v1"], - * "k2": ["v2"] - * } - * - * ...?k1=v1&k1=v2: - * { - * "k1": ["v1", "v2"] - * } - * - * ...?k1=v1&k2: - * { - * "k1": ["v1"], - * "k2": [] - * } - * - * ...?k1=v1&k1: - * { - * "k1": ["v1"] - * } - * - * ...?k1&k1: - * { - * "k1": [] - * } - * - * @param query the query string to parse (optional, default to cached - * results from parsing window location search query). - * - * @return object mapping keys to variables. - */ -util.getQueryVariables = function(query) { - var parse = function(q) { - var rval = {}; - var kvpairs = q.split('&'); - for(var i = 0; i < kvpairs.length; i++) { - var pos = kvpairs[i].indexOf('='); - var key; - var val; - if(pos > 0) { - key = kvpairs[i].substring(0, pos); - val = kvpairs[i].substring(pos + 1); - } else { - key = kvpairs[i]; - val = null; - } - if(!(key in rval)) { - rval[key] = []; - } - // disallow overriding object prototype keys - if(!(key in Object.prototype) && val !== null) { - rval[key].push(unescape(val)); - } - } - return rval; - }; - - var rval; - if(typeof(query) === 'undefined') { - // set cached variables if needed - if(_queryVariables === null) { - if(typeof(window) !== 'undefined' && window.location && window.location.search) { - // parse window search query - _queryVariables = parse(window.location.search.substring(1)); - } else { - // no query variables available - _queryVariables = {}; - } - } - rval = _queryVariables; - } else { - // parse given query - rval = parse(query); - } - return rval; -}; - -/** - * Parses a fragment into a path and query. This method will take a URI - * fragment and break it up as if it were the main URI. For example: - * /bar/baz?a=1&b=2 - * results in: - * { - * path: ["bar", "baz"], - * query: {"k1": ["v1"], "k2": ["v2"]} - * } - * - * @return object with a path array and query object. - */ -util.parseFragment = function(fragment) { - // default to whole fragment - var fp = fragment; - var fq = ''; - // split into path and query if possible at the first '?' - var pos = fragment.indexOf('?'); - if(pos > 0) { - fp = fragment.substring(0, pos); - fq = fragment.substring(pos + 1); - } - // split path based on '/' and ignore first element if empty - var path = fp.split('/'); - if(path.length > 0 && path[0] === '') { - path.shift(); - } - // convert query into object - var query = (fq === '') ? {} : util.getQueryVariables(fq); - - return { - pathString: fp, - queryString: fq, - path: path, - query: query - }; -}; - -/** - * Makes a request out of a URI-like request string. This is intended to - * be used where a fragment id (after a URI '#') is parsed as a URI with - * path and query parts. The string should have a path beginning and - * delimited by '/' and optional query parameters following a '?'. The - * query should be a standard URL set of key value pairs delimited by - * '&'. For backwards compatibility the initial '/' on the path is not - * required. The request object has the following API, (fully described - * in the method code): - * { - * path: . - * query: , - * getPath(i): get part or all of the split path array, - * getQuery(k, i): get part or all of a query key array, - * getQueryLast(k, _default): get last element of a query key array. - * } - * - * @return object with request parameters. - */ -util.makeRequest = function(reqString) { - var frag = util.parseFragment(reqString); - var req = { - // full path string - path: frag.pathString, - // full query string - query: frag.queryString, - /** - * Get path or element in path. - * - * @param i optional path index. - * - * @return path or part of path if i provided. - */ - getPath: function(i) { - return (typeof(i) === 'undefined') ? frag.path : frag.path[i]; - }, - /** - * Get query, values for a key, or value for a key index. - * - * @param k optional query key. - * @param i optional query key index. - * - * @return query, values for a key, or value for a key index. - */ - getQuery: function(k, i) { - var rval; - if(typeof(k) === 'undefined') { - rval = frag.query; - } else { - rval = frag.query[k]; - if(rval && typeof(i) !== 'undefined') { - rval = rval[i]; - } - } - return rval; - }, - getQueryLast: function(k, _default) { - var rval; - var vals = req.getQuery(k); - if(vals) { - rval = vals[vals.length - 1]; - } else { - rval = _default; - } - return rval; - } - }; - return req; -}; - -/** - * Makes a URI out of a path, an object with query parameters, and a - * fragment. Uses jQuery.param() internally for query string creation. - * If the path is an array, it will be joined with '/'. - * - * @param path string path or array of strings. - * @param query object with query parameters. (optional) - * @param fragment fragment string. (optional) - * - * @return string object with request parameters. - */ -util.makeLink = function(path, query, fragment) { - // join path parts if needed - path = jQuery.isArray(path) ? path.join('/') : path; - - var qstr = jQuery.param(query || {}); - fragment = fragment || ''; - return path + - ((qstr.length > 0) ? ('?' + qstr) : '') + - ((fragment.length > 0) ? ('#' + fragment) : ''); -}; - /** * Check if an object is empty. * diff --git a/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) { From a3f48e4078211ec0176b6e387d83bbc3f8470b0a Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 4 Jan 2022 18:31:54 -0500 Subject: [PATCH 051/127] Fix spelling. Co-authored-by: Dave Longley --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e4bd343e..c572f3817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,7 @@ Forge ChangeLog - 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 appriciated. + update the code or tests would be appreciated. ## 0.10.0 - 2020-09-01 From 27286feec0f9ac0094a6b7a3041e5c1a412ad7a5 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 4 Jan 2022 18:32:19 -0500 Subject: [PATCH 052/127] Fix style. Co-authored-by: Dave Longley --- lib/log.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/log.js b/lib/log.js index b8a265c20..5228047f6 100644 --- a/lib/log.js +++ b/lib/log.js @@ -299,7 +299,7 @@ if(typeof(console) !== 'undefined' && 'log' in console) { */ if(sConsoleLogger !== null) { var query; - if(typeof(window) !== 'undefined' && window.location) { + if(typeof window !== 'undefined' && window.location) { query = new URL(window.location.href).searchParams; } else { query = new URLSearchParams(); From 5f8d5c215f157faf8d2e1d8061c4d6086363f871 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 4 Jan 2022 20:54:48 -0500 Subject: [PATCH 053/127] Update docs. - Update to v1.0.0. - Update changelog. - Update release details. - Remove mentions of bower and forge-dist. - Rename master to main. - Add Libera.Chat IRC channel. - Minor other fixes. --- .gitignore | 1 - CHANGELOG.md | 22 ++++++++++----- README.md | 28 ++++++------------- RELEASE.md | 78 +++++++--------------------------------------------- 4 files changed, 34 insertions(+), 95 deletions(-) diff --git a/.gitignore b/.gitignore index 134b7dedd..01519a399 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ *.py[co] *.sw[nop] *~ -.bower.json .cdtproject .classpath .cproject diff --git a/CHANGELOG.md b/CHANGELOG.md index c572f3817..39c7139e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,15 @@ Forge ChangeLog =============== -## 0.11.0 - 2021-xx-xx +## 1.0.0 - 2022-xx-xx + +### 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 @@ -27,6 +35,12 @@ Forge ChangeLog ### 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). Use [NPM][] or + another CDN. (Also be sure to read "Security Considerations" in the README.) ### Added - OIDs for `surname`, `title`, and `givenName`. @@ -36,12 +50,6 @@ Forge ChangeLog Depending on how applications used this id to name association it could cause compatibility issues. -### Notes -- 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. - ## 0.10.0 - 2020-09-01 ### Changed diff --git a/README.md b/README.md index bddcffe6e..6f3279efb 100644 --- a/README.md +++ b/README.md @@ -105,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: @@ -120,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 @@ -145,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 @@ -2003,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. @@ -2030,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 --------- @@ -2045,7 +2034,6 @@ Financial support is welcome and helps contribute to futher development: [3DES]: https://en.wikipedia.org/wiki/Triple_DES [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard [ASN.1]: https://en.wikipedia.org/wiki/ASN.1 -[Bower]: https://bower.io/ [Browserify]: http://browserify.org/ [CBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation [CFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation @@ -2058,7 +2046,9 @@ Financial support is welcome and helps contribute to futher development: [HMAC]: https://en.wikipedia.org/wiki/HMAC [JavaScript]: https://en.wikipedia.org/wiki/JavaScript [Karma]: https://karma-runner.github.io/ +[Libera.Chat]: https://libera.chat/ [MD5]: https://en.wikipedia.org/wiki/MD5 +[NPM]: https://www.npmjs.com/ [Node.js]: https://nodejs.org/ [OFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation [PKCS#10]: https://en.wikipedia.org/wiki/Certificate_signing_request diff --git a/RELEASE.md b/RELEASE.md index c90a249f4..92c01d248 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,77 +1,19 @@ Forge Release Process ===================== -Versioning ----------- +Prepare a Release +----------------- * Follow the [Semantic Versioning][] guidelines. -* Use version X.Y.Z-dev in dev mode. -* Use version X.Y.Z for releases. - -Master Branch Release Process ------------------------------ - * Ensure [tests pass](./README.md#testing). +* Ensure [CHANGELOG.md](./CHANGELOG.md) is up-to-date using [Keep a + CHANGELOG][] style. -## Update the main repository: - -* Commit changes. -* Update the [CHANGELOG](./CHANGELOG.md) as needed using rougly - [Keep a CHANGELOG][] style. -* `$EDITOR package.json`: update to release version and remove `-dev` suffix. -* `git commit package.json -m "Release {version}."` -* `git tag {version}` -* `$EDITOR package.json`: update to next version and add `-dev` suffix. -* `git commit package.json -m "Start {next-version}."` -* `git push` -* `git push --tags` - -## Publish to NPM: - -To ensure a clean upload, use a clean updated checkout, and run the following: - -* `git checkout {version}` -* `npm install` -* `npm publish` - -## Update bundled distribution - -This is kept in a different repository to avoid the accumulated size when -adding per-release bundles. - -* Checkout [forge-dist][]. -* Build a clean Forge version you want to distribute: - * `git checkout {version}` - * `npm install` - * `npm run build` -* Copy files to `forge-dist`: - * `cp dist/forge.min.js{,.map} dist/prime.worker.min.js{,.map} FORGEDIST/dist/` -* Release `forge-dist`: - * `git commit -a -m "Release {version}."` - * `git tag {version}` - * `git push` - * `git push origin {version}` - -Older Branch Release Process ----------------------------- - -In order to provide support for Bower (and similar) for current built bundle -releases and historical releases the [forge-dist][] repository needs to be -updated with code changes and tags from the main repository. Once a historical -branch, like 0.6.x, on the main repository is updated and tagged, do the -following: +Publish to NPM +-------------- -* Checkout [forge-dist][]. -* Setup an upstream branch: - * `git remote add upstream git@github.com:digitalbazaar/forge.git` - * `git fetch upstream` -* Merge changes: - * `git checkout 0.6.x` - * `git merge upstream/0.6.x` -* Push code and tag(s): - * `git push` - * `git push origin {version}` +As of Forge 1.0.0 publishing is performed using the `pubnpm` script from +https://github.com/digitalbazaar/publish-script. -[Keep a CHANGELOG]: http://keepachangelog.com/ -[Semantic Versioning]: http://semver.org/ -[forge-dist]: https://github.com/digitalbazaar/forge-dist +[Keep a CHANGELOG]: https://keepachangelog.com/ +[Semantic Versioning]: https://semver.org/ From 69395d0684eb56ee0cdd9a0ea0e541a4013dafd2 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 4 Jan 2022 20:59:12 -0500 Subject: [PATCH 054/127] Fix install note. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39c7139e4..8a63b7089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,8 +39,8 @@ Forge ChangeLog - **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). Use [NPM][] or - another CDN. (Also be sure to read "Security Considerations" in the README.) + [forge-dist](https://github.com/digitalbazaar/forge-dist). Install using + [another method](./README.md#installation). ### Added - OIDs for `surname`, `title`, and `givenName`. From 9055d6f6099e5199d7e62027e8eb0f5860d33938 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 4 Jan 2022 22:00:24 -0500 Subject: [PATCH 055/127] Update changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a63b7089..981521ce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Forge ChangeLog =============== -## 1.0.0 - 2022-xx-xx +## 1.0.0 - 2022-01-04 ### Notes - **1.0.0**! From bc1a8d8837ef29672dbd320c5d03f06068ae4116 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 4 Jan 2022 22:00:24 -0500 Subject: [PATCH 056/127] Release 1.0.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 22ff40e46..1d010d195 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "0.10.1-dev", + "version": "1.0.0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From ea4a83c9bb8cdc667623ff7ffc750a628f8d9181 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 4 Jan 2022 22:00:57 -0500 Subject: [PATCH 057/127] Start 1.0.1-0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1d010d195..ff9979cfe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.0.0", + "version": "1.0.1-0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From cebb5ff18773610b4078193c864f028b4ab417e6 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Fri, 23 Apr 2021 12:19:59 +0100 Subject: [PATCH 058/127] FIX [x509] Certificate issuer and subject hash correctly computed --- lib/x509.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/x509.js b/lib/x509.js index 95dbc2946..5dce82da1 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -1372,6 +1372,8 @@ pki.certificateFromAsn1 = function(obj, computeHash) { // handle issuer, build issuer message digest var imd = forge.md.sha1.create(); + var ibytes = asn1.toDer(capture.certIssuer); + imd.update(ibytes.getBytes()); cert.issuer.getField = function(sn) { return _getAttribute(cert.issuer, sn); }; @@ -1379,7 +1381,7 @@ pki.certificateFromAsn1 = function(obj, computeHash) { _fillMissingFields([attr]); cert.issuer.attributes.push(attr); }; - cert.issuer.attributes = pki.RDNAttributesAsArray(capture.certIssuer, imd); + cert.issuer.attributes = pki.RDNAttributesAsArray(capture.certIssuer); if(capture.certIssuerUniqueId) { cert.issuer.uniqueId = capture.certIssuerUniqueId; } @@ -1387,6 +1389,8 @@ pki.certificateFromAsn1 = function(obj, computeHash) { // handle subject, build subject message digest var smd = forge.md.sha1.create(); + var sbytes = asn1.toDer(capture.certSubject); + smd.update(sbytes.getBytes()); cert.subject.getField = function(sn) { return _getAttribute(cert.subject, sn); }; @@ -1394,7 +1398,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; } From eca152796ab05a639e7b03b23584953f93b7af74 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Fri, 23 Apr 2021 12:32:12 +0100 Subject: [PATCH 059/127] Add test for issuer and subject hashes --- tests/unit/x509.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/unit/x509.js b/tests/unit/x509.js index 43a9ea61b..8ae5c4698 100644 --- a/tests/unit/x509.js +++ b/tests/unit/x509.js @@ -1206,6 +1206,52 @@ 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 sha256WithRSAEncryption signature', function() { var certPem = '-----BEGIN CERTIFICATE-----\r\n' + 'MIIDuzCCAqOgAwIBAgIEO5vZjDANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\n' + From fb9d7f1e4e2b89f8abf01f0044906b0cc07eac92 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 6 Jan 2022 18:35:27 -0500 Subject: [PATCH 060/127] Update changelog. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 981521ce3..062af27f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Forge ChangeLog =============== +### Fixed +- [x509]: Correctly compute certificate issuer and subject hashes to match + behavior of openssl. + ## 1.0.0 - 2022-01-04 ### Notes From 9c2aba10fd4af7799177c21f56363af696c629a5 Mon Sep 17 00:00:00 2001 From: mundry Date: Mon, 14 Oct 2019 19:09:28 +0200 Subject: [PATCH 061/127] Accept CSRs with NEW in the label for decoding. --- lib/pem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pem.js b/lib/pem.js index aed8bdf92..7dddf1135 100644 --- a/lib/pem.js +++ b/lib/pem.js @@ -96,7 +96,7 @@ pem.decode = function(str) { var rval = []; // split string into PEM messages (be lenient w/EOF on BEGIN line) - var rMessage = /\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END \1-----/g; + var rMessage = /\s*-----BEGIN(?: NEW)? ([A-Z0-9- ]+)-----\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END(?: NEW)? \1-----/g; var rHeader = /([\x21-\x7e]+):\s*([\x21-\x7e\s^:]+)/; var rCRLF = /\r?\n/; var match; From e1e8762f085d0f6cd7ba216cf260cf31050d19f5 Mon Sep 17 00:00:00 2001 From: mundry Date: Tue, 15 Oct 2019 20:30:15 +0200 Subject: [PATCH 062/127] Add pem unit tests for NEW in CSR labels. --- tests/unit/pem.js | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) 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'); + } + }); }); })(); From bbd80a40df7bc2d573b1e95ea7bde0fa194417c1 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 6 Jan 2022 18:59:13 -0500 Subject: [PATCH 063/127] Adjust how PEM CSR with "NEW" are handled. - Remove "NEW" from regex to avoid it being accepted for all types. - Use a special case for "NEW CERTIFICATE REQUEST". - Update changelog. --- CHANGELOG.md | 2 ++ lib/pem.js | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 062af27f3..7d5621c77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Forge ChangeLog ### 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 diff --git a/lib/pem.js b/lib/pem.js index 7dddf1135..1992bc77b 100644 --- a/lib/pem.js +++ b/lib/pem.js @@ -96,7 +96,7 @@ pem.decode = function(str) { var rval = []; // split string into PEM messages (be lenient w/EOF on BEGIN line) - var rMessage = /\s*-----BEGIN(?: NEW)? ([A-Z0-9- ]+)-----\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END(?: NEW)? \1-----/g; + var rMessage = /\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END \1-----/g; var rHeader = /([\x21-\x7e]+):\s*([\x21-\x7e\s^:]+)/; var rCRLF = /\r?\n/; var match; @@ -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, From c9d3cb72cc97c5ac41a53348a0179f190135ae8e Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 6 Jan 2022 23:00:49 -0500 Subject: [PATCH 064/127] Update changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d5621c77..d1bf64cc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ Forge ChangeLog =============== +## 1.1.0 - 2022-01-06 + ### Fixed - [x509]: Correctly compute certificate issuer and subject hashes to match behavior of openssl. From cb93fe5ce2d6f9a4fd9aaca5067c2cfe887a24ca Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 6 Jan 2022 23:00:49 -0500 Subject: [PATCH 065/127] Release 1.1.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ff9979cfe..e6e3eb5e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.0.1-0", + "version": "1.1.0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From c61b204874d9341fa8bf8bb626aa532520c3e205 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 6 Jan 2022 23:01:23 -0500 Subject: [PATCH 066/127] Start 1.1.1-0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e6e3eb5e7..5855ee67c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.1.0", + "version": "1.1.1-0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From cca7eea9d6a885913522d56f43f20d8ed6bd7f14 Mon Sep 17 00:00:00 2001 From: Jeremy Barber <38042377+JeremyBarber@users.noreply.github.com> Date: Wed, 22 Jul 2020 16:25:16 +0100 Subject: [PATCH 067/127] 'Expected' and 'Actual' issuers were backwards in verification failure message --- lib/x509.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/x509.js b/lib/x509.js index 5dce82da1..65dd854ec 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -1069,8 +1069,8 @@ pki.createCertificate = function() { 'The parent certificate did not issue the given child ' + 'certificate; the child certificate\'s issuer does not match the ' + 'parent\'s subject.'); - error.expectedIssuer = issuer.attributes; - error.actualIssuer = subject.attributes; + error.expectedIssuer = subject.attributes; + error.actualIssuer = issuer.attributes; throw error; } From 874cef8c9a2e7d756603a08a740c24dbca70df58 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Fri, 7 Jan 2022 00:03:14 -0500 Subject: [PATCH 068/127] Update changelog. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1bf64cc5..bb5ff0362 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Forge ChangeLog =============== +### Fixed +- [x509] 'Expected' and 'Actual' issuers were backwards in verification failure + message. + ## 1.1.0 - 2022-01-06 ### Fixed From 03d3ed73711cf7b391edeb9a50fdbaca2986a893 Mon Sep 17 00:00:00 2001 From: David Watrous Date: Wed, 21 Oct 2020 15:16:01 -0400 Subject: [PATCH 069/127] Added alternate OID 1.3.14.3.2.29 for sha1 with RSA --- lib/oids.js | 2 ++ lib/x509.js | 18 ++++++++++++------ tests/unit/x509.js | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/oids.js b/lib/oids.js index 1c86c2189..0ca96e989 100644 --- a/lib/oids.js +++ b/lib/oids.js @@ -42,6 +42,8 @@ _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'); diff --git a/lib/x509.js b/lib/x509.js index 65dd854ec..d46e0948c 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -1081,8 +1081,9 @@ pki.createCertificate = function() { var oid = oids[child.signatureOid]; switch(oid) { case 'sha1WithRSAEncryption': - md = forge.md.sha1.create(); - break; + case 'sha1WithRSASignature': + md = forge.md.sha1.create(); + break; case 'md5WithRSAEncryption': md = forge.md.md5.create(); break; @@ -1118,8 +1119,9 @@ pki.createCertificate = function() { switch(child.signatureOid) { case oids.sha1WithRSAEncryption: - scheme = undefined; /* use PKCS#1 v1.5 padding scheme */ - break; + case oids.sha1WithRSASignature: + scheme = undefined; /* use PKCS#1 v1.5 padding scheme */ + break; case oids['RSASSA-PSS']: var hash, mgf; @@ -1339,8 +1341,9 @@ pki.certificateFromAsn1 = function(obj, computeHash) { var oid = oids[cert.signatureOid]; switch(oid) { case 'sha1WithRSAEncryption': - cert.md = forge.md.sha1.create(); - break; + case 'sha1WithRSASignature': + cert.md = forge.md.sha1.create(); + break; case 'md5WithRSAEncryption': cert.md = forge.md.md5.create(); break; @@ -1687,6 +1690,7 @@ pki.certificationRequestFromAsn1 = function(obj, computeHash) { var oid = oids[csr.signatureOid]; switch(oid) { case 'sha1WithRSAEncryption': + case 'sha1WithRSASignature': csr.md = forge.md.sha1.create(); break; case 'md5WithRSAEncryption': @@ -1857,6 +1861,7 @@ pki.createCertificationRequest = function() { var oid = oids[csr.signatureOid]; switch(oid) { case 'sha1WithRSAEncryption': + case 'sha1WithRSASignature': md = forge.md.sha1.create(); break; case 'md5WithRSAEncryption': @@ -1896,6 +1901,7 @@ pki.createCertificationRequest = function() { switch(csr.signatureOid) { case oids.sha1WithRSAEncryption: + case oids.sha1WithRSASignature: /* use PKCS#1 v1.5 padding scheme */ break; case oids['RSASSA-PSS']: diff --git a/tests/unit/x509.js b/tests/unit/x509.js index 8ae5c4698..474e97a89 100644 --- a/tests/unit/x509.js +++ b/tests/unit/x509.js @@ -1252,6 +1252,24 @@ var UTIL = require('../../lib/util'); 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' + From 2fb9995d783626aec7519641b06223c9d58f67c8 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 6 Jan 2022 22:11:48 -0500 Subject: [PATCH 070/127] Add helper to create signature digest. - Reduce duplicate code. - Fix style nit. - Update changelog. --- CHANGELOG.md | 10 +++ lib/x509.js | 189 ++++++++++++++++----------------------------------- 2 files changed, 69 insertions(+), 130 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb5ff0362..8f6c22f5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ Forge ChangeLog - [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 with a helper function to create a signature + digest given an signature algorithm OID. + ## 1.1.0 - 2022-01-06 ### Fixed diff --git a/lib/x509.js b/lib/x509.js index d46e0948c..d7d2f892f 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -689,6 +689,44 @@ 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': + case 'sha1WithRSASignature': + return forge.md.sha1.create(); + break; + case 'md5WithRSAEncryption': + return forge.md.md5.create(); + break; + case 'sha256WithRSAEncryption': + return forge.md.sha256.create(); + break; + case 'sha384WithRSAEncryption': + return forge.md.sha384.create(); + break; + case 'sha512WithRSAEncryption': + return forge.md.sha512.create(); + break; + case 'RSASSA-PSS': + return forge.md.sha256.create(); + break; + default: + var error = new Error( + 'Could not compute ' + options.type + ' digest. ' + + 'Unknown signature OID.'); + error.signatureOid = options.signatureOid; + throw error; + } +}; + /** * Converts an X.509 certificate from PEM format. * @@ -1076,37 +1114,11 @@ pki.createCertificate = function() { 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': - case 'sha1WithRSASignature': - 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); @@ -1120,8 +1132,8 @@ pki.createCertificate = function() { switch(child.signatureOid) { case oids.sha1WithRSAEncryption: case oids.sha1WithRSASignature: - scheme = undefined; /* use PKCS#1 v1.5 padding scheme */ - break; + scheme = undefined; /* use PKCS#1 v1.5 padding scheme */ + break; case oids['RSASSA-PSS']: var hash, mgf; @@ -1335,38 +1347,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': - case 'sha1WithRSASignature': - 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); @@ -1684,38 +1669,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': - case 'sha1WithRSASignature': - 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); @@ -1855,39 +1813,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': - case 'sha1WithRSASignature': - 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 || From 9d8b0eea8196d49d73e5e2f4c971d53c81d6e233 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 6 Jan 2022 22:48:44 -0500 Subject: [PATCH 071/127] Add verification helper. - Reduce duplicate code. - Small cleanups. - Add note about deprecated OID alias. --- CHANGELOG.md | 5 +- lib/x509.js | 168 +++++++++++++++++++++------------------------------ 2 files changed, 72 insertions(+), 101 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f6c22f5d..32efeb70f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,9 @@ Forge ChangeLog links](https://github.com/digitalbazaar/forge/issues/825). ### Changed -- [x509]: Reduce duplicate code with a helper function to create a signature - digest given an signature algorithm OID. +- [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 diff --git a/lib/x509.js b/lib/x509.js index d7d2f892f..25096c17d 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -700,24 +700,19 @@ var _readSignatureParameters = function(oid, obj, fillDefaults) { var _createSignatureDigest = function(options) { switch(oids[options.signatureOid]) { case 'sha1WithRSAEncryption': + // depreacted alias case 'sha1WithRSASignature': return forge.md.sha1.create(); - break; case 'md5WithRSAEncryption': return forge.md.md5.create(); - break; case 'sha256WithRSAEncryption': return forge.md.sha256.create(); - break; case 'sha384WithRSAEncryption': return forge.md.sha384.create(); - break; case 'sha512WithRSAEncryption': return forge.md.sha512.create(); - break; case 'RSASSA-PSS': return forge.md.sha256.create(); - break; default: var error = new Error( 'Could not compute ' + options.type + ' digest. ' + @@ -727,6 +722,68 @@ var _createSignatureDigest = function(options) { } }; +/** + * 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: + // depreacted 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. * @@ -1127,53 +1184,9 @@ pki.createCertificate = function() { } if(md !== null) { - var scheme; - - switch(child.signatureOid) { - case oids.sha1WithRSAEncryption: - case oids.sha1WithRSASignature: - 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; @@ -1826,52 +1839,9 @@ pki.createCertificationRequest = function() { } if(md !== null) { - var scheme; - - switch(csr.signatureOid) { - case oids.sha1WithRSAEncryption: - case oids.sha1WithRSASignature: - /* 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; From f8e498a6682dc32233eb361400ee7198cf95c855 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 6 Jan 2022 22:49:48 -0500 Subject: [PATCH 072/127] Fix typos. --- lib/x509.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/x509.js b/lib/x509.js index 25096c17d..2877810c1 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -700,7 +700,7 @@ var _readSignatureParameters = function(oid, obj, fillDefaults) { var _createSignatureDigest = function(options) { switch(oids[options.signatureOid]) { case 'sha1WithRSAEncryption': - // depreacted alias + // deprecated alias case 'sha1WithRSASignature': return forge.md.sha1.create(); case 'md5WithRSAEncryption': @@ -737,7 +737,7 @@ var _verifySignature = function(options) { switch(cert.signatureOid) { case oids.sha1WithRSAEncryption: - // depreacted alias + // deprecated alias case oids.sha1WithRSASignature: /* use PKCS#1 v1.5 padding scheme */ break; From a9f013ab985cdb87536826e86d2adb0b26c7652d Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Fri, 7 Jan 2022 21:19:48 -0500 Subject: [PATCH 073/127] Update changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32efeb70f..6d7849f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ Forge ChangeLog =============== +## 1.2.0 - 2022-01-07 + ### Fixed - [x509] 'Expected' and 'Actual' issuers were backwards in verification failure message. From 866ed40ae64264d48ffcc8cf663a6d13b9446e78 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Fri, 7 Jan 2022 21:19:48 -0500 Subject: [PATCH 074/127] Release 1.2.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5855ee67c..a75316c16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.1.1-0", + "version": "1.2.0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From 154531600a7c928774e402148215664945961d53 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Fri, 7 Jan 2022 21:20:23 -0500 Subject: [PATCH 075/127] Start 1.2.1-0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a75316c16..736e6c76f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.2.0", + "version": "1.2.1-0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From 50a20ec77ee6b0a1b5e8124b3c6c4aba6a37bebe Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 11 Jan 2022 13:00:20 -0500 Subject: [PATCH 076/127] Load entire module while testing. - Improves top-level testing. - Improves coverage reporting. --- CHANGELOG.md | 4 ++++ tests/unit/forge.js | 2 ++ tests/unit/index.js | 1 + 3 files changed, 7 insertions(+) create mode 100644 tests/unit/forge.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d7849f94..5fe424fe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Forge ChangeLog =============== +### Fixed +- [tests] Load entire module to improve top-level testing and coverage + reporting. + ## 1.2.0 - 2022-01-07 ### Fixed 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'); From 2f3820a138413860a64aeecbfc47d89e8fa91310 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 11 Jan 2022 14:51:44 -0500 Subject: [PATCH 077/127] Refactor logging to avoid use of URLSearchParams. The logic path was hitting URLSearchParams initialization in older Node.js versions that don't have that global. Removing since it's not needed. --- CHANGELOG.md | 3 ++- lib/log.js | 13 +++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fe424fe5..0a6862653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ Forge ChangeLog =============== ### Fixed -- [tests] Load entire module to improve top-level testing and coverage +- [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 diff --git a/lib/log.js b/lib/log.js index 5228047f6..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,13 +297,10 @@ 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; - if(typeof window !== 'undefined' && window.location) { - query = new URL(window.location.href).searchParams; - } else { - query = new URLSearchParams(); - } +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( From 43a456e4d5d707563609becf8ea5dbbfaa5bf3ff Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 11 Jan 2022 14:56:26 -0500 Subject: [PATCH 078/127] Update changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a6862653..730767d0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ Forge ChangeLog =============== +## 1.2.1 - 2022-01-11 + ### Fixed - [tests]: Load entire module to improve top-level testing and coverage reporting. From 2162bfca12ef16de04a99d8bfa208eabcdf177be Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 11 Jan 2022 14:56:26 -0500 Subject: [PATCH 079/127] Release 1.2.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 736e6c76f..cfe91332b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.2.1-0", + "version": "1.2.1", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From 7928551717b60e5def1785cfa7728c1107716c91 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 11 Jan 2022 14:56:57 -0500 Subject: [PATCH 080/127] Start 1.2.2-0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cfe91332b..ab7771410 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.2.1", + "version": "1.2.2-0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From 2b1f368c93861ef751e32574b08ee4caa5e80c7f Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 7 Mar 2022 19:41:19 -0500 Subject: [PATCH 081/127] Add fallback to pretty print invalid UTF8 data. Malformed UTF8 data can cause the escaping code to fail. Capture the failures and print out a hex version with error note. --- CHANGELOG.md | 5 +++++ lib/asn1.js | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 730767d0b..075ea30a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Forge ChangeLog =============== +## 1.3.0 - 2022-XXX + +### Fixed +- [asn1] Add fallback to pretty print invalid UTF8 data. + ## 1.2.1 - 2022-01-11 ### Fixed diff --git a/lib/asn1.js b/lib/asn1.js index e0fea0e08..c142f6358 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -1391,7 +1391,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; From e27f61230f19fb9f085a163f31d0573305271b84 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 7 Mar 2022 19:42:23 -0500 Subject: [PATCH 082/127] Remove unused option. --- lib/asn1.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/asn1.js b/lib/asn1.js index c142f6358..44ee5d439 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -566,7 +566,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 }; From c20f309311d83445e11abe7c313cc4b467c18914 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 7 Mar 2022 19:45:11 -0500 Subject: [PATCH 083/127] Adjust remaining length. This is not strictly needed since the value isn't used after this point. However, when temporary debugging is added to the code it is helpful for this valud to be accurate. Adding it in to avoid future confusion. --- lib/asn1.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/asn1.js b/lib/asn1.js index 44ee5d439..b6465351c 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -614,6 +614,7 @@ function _fromDer(bytes, remaining, depth, options) { } } else { value = bytes.getBytes(length); + remaining -= length; } } From 3f0b49a0573ef1bb7af7f5673c0cfebf00424df1 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 7 Mar 2022 19:53:18 -0500 Subject: [PATCH 084/127] Fix signature verification issues. **SECURITY**: Three RSA PKCS#1 v1.5 signature verification issues were reported by Moosa Yahyazadeh (moosa-yahyazadeh@uiowa.edu): - 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. - 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. - 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. For more information, please see "Bleichenbacher's RSA signature forgery based on implementation error" by Hal Finney: https://mailarchive.ietf.org/arch/msg/openpgp/5rnE9ZRN1AokBVj3VqblGlP63QE/ Fixed with the following: - [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. An invalid `DigestInfo` or algorithm identifier will now cause an error to be thrown. - [oid] Added `1.2.840.113549.2.2` / `md2` for hash algorithm checking. - [tests] Tests were added for all of the reported issues. A private verify option was added to assist in checking multiple possible failures in the test data. --- CHANGELOG.md | 38 ++++++++++++ lib/asn1.js | 19 +++++- lib/oids.js | 1 + lib/rsa.js | 80 ++++++++++++++++++++++++- tests/unit/rsa.js | 150 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 284 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 075ea30a7..29b968d37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,46 @@ Forge ChangeLog ## 1.3.0 - 2022-XXX +### Security +- **SECURITY**: Three RSA PKCS#1 v1.5 signature verification issues were + reported by Moosa Yahyazadeh (moosa-yahyazadeh@uiowa.edu). + - 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. + - 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. + - 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. + ### 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. An invalid `DigestInfo` or algorithm + identifier will now cause an error to be thrown. + +### Added +- [oid] Added `1.2.840.113549.2.2` / `md2` for hash algorithm checking. ## 1.2.1 - 2022-01-11 diff --git a/lib/asn1.js b/lib/asn1.js index b6465351c..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; }; /** diff --git a/lib/oids.js b/lib/oids.js index 0ca96e989..5483d72cd 100644 --- a/lib/oids.js +++ b/lib/oids.js @@ -47,6 +47,7 @@ _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('1.2.840.113549.2.2', 'md2'); _IN('1.2.840.113549.2.5', 'md5'); // pkcs#7 content types diff --git a/lib/rsa.js b/lib/rsa.js index 7c67917ce..48a4bd261 100644 --- a/lib/rsa.js +++ b/lib/rsa.js @@ -264,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. * @@ -1092,15 +1126,27 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { * a Forge PSS object for RSASSA-PSS, * 'NONE' or null for none, DigestInfo will not be expected, but * PKCS#1 v1.5 padding will still be used. + * @param options optional verify options + * _parseAllDigestBytes testing flag to control parsing of all + * digest bytes. Unsupported and not for general usage. + * (default: true) * * @return true if the signature was verified, false if not. */ - key.verify = function(digest, signature, scheme) { + key.verify = function(digest, signature, scheme, options) { if(typeof scheme === 'string') { scheme = scheme.toUpperCase(); } else if(scheme === undefined) { scheme = 'RSASSA-PKCS1-V1_5'; } + if(options === undefined) { + options = { + _parseAllDigestBytes: true + }; + } + if(!('_parseAllDigestBytes' in options)) { + options._parseAllDigestBytes = true; + } if(scheme === 'RSASSA-PKCS1-V1_5') { scheme = { @@ -1108,9 +1154,37 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { // remove padding d = _decodePkcs1_v1_5(d, key, true); // d is ASN.1 BER-encoded DigestInfo - var obj = asn1.fromDer(d); + var obj = asn1.fromDer(d, { + parseAllBytes: options._parseAllDigestBytes + }); + + // validate DigestInfo + var capture = {}; + var errors = []; + if(!asn1.validate(obj, digestInfoValidator, capture, errors)) { + var error = new Error( + 'ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 ' + + 'DigestInfo value.'); + error.errors = errors; + throw error; + } + // check hash algorithm identifier + // 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.sha256 || + oid === forge.oids.sha384 || + oid === forge.oids.sha512)) { + 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 === obj.value[1].value; + return digest === capture.digest; } }; } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) { diff --git a/tests/unit/rsa.js b/tests/unit/rsa.js index 0cdd28e01..def5c2d78 100644 --- a/tests/unit/rsa.js +++ b/tests/unit/rsa.js @@ -1,5 +1,6 @@ var ASSERT = require('assert'); var FORGE = require('../../lib/forge'); +var JSBN = require('../../lib/jsbn'); var MD = require('../../lib/md.all'); var MGF = require('../../lib/mgf'); var PKI = require('../../lib/pki'); @@ -773,5 +774,154 @@ var UTIL = require('../../lib/util'); }); } })(); + + describe('bad data', function() { + // 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); + }, { + message: '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 + }); + }, { + message: '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); + var S = UTIL.binary.hex.decode( + 'e7410e05bdc38d1c72fab784be41df3d3de2ae83894d9ec86cb5fe343d5dc7d45df2a36fc60363faf32f0d37ab457648af40a48a6c53ae7af0575e92cb1ffc236d55e1325af8c71b3ac313f2630fb498b8e1546093aca1ed56026a96cb525d991159a2d6ccbfd5ef63ae718f8ace2469e357ccf3f6a048bbf9760f5fb36b9dd38fb330eab504f05078b83f5d8bd95dce8fccc6b46babd56f678300f2b39083e53e04e79f503358a6222f8dd66b561fea3a51ecf3be16c9e2ea6ba8aaed9fbe6ba510ff752e4529385f759d4d6120b15f65534248ed5bbb1307a7d0a9838329697f5fbae91f48e478dcbb77190f0d173b6cb8b1299cf4202570d25d11a7862b47'); + + _checkBadDigestInfo(publicKey, S); + }); + + it('should check tailing garbage and DigestInfo [1]', function() { + var publicKey = RSA.setPublicKey(N, e); + var S = UTIL.binary.hex.decode( + 'c2ad2fa23c246ee98c453d69023e7ec05956b48bd0e287341ba9d342ad49b0fff2bcbb9adc50f1ccbfc54106305cc74a88db89ff94901a08359893a08426373e7949a8794798233445af6c48bc6ccbe278bdeb62c31e40c3bf0014af2faadcc9ed7885756789a5b95c2a355fbb3f04412f42e0f9ed335ab51af8f091a62aaaaf6577422220917daaece3ca2f4e66dc4e0574356762592052b406768c31c25cf4c1754e6da9dc3440e238c4f9b25cccc174dd1b17b027e0f9ce2763b86f0e6871690ddd018d2e774bc968c9c6e907a000daf5044ba31a0b9eefbd7b4b1ec466d20bc1dd3f020cb1091af6b476416da3024ea046b09fbbbc4d2355da9a2bc6ddb9'); + + _checkBadTailingGarbage(publicKey, S); + _checkBadDigestInfo(publicKey, S, true); + }); + + it('should check tailing garbage and DigestIfno [2]', function() { + var publicKey = RSA.setPublicKey(N, e); + var S = UTIL.binary.hex.decode( + 'a7c5812d7fc0eef766a481aac18c8c48483daf9b5ffb6614bd98ebe4ecb746dd493cf5dd2cbe16ecaa0b52109b744930eda49316605fc823fd57a68b5b2c62e8c1b158b26e1547a2e33cdd79427d7c513f07d02261ffe43db197d8cddca2b5b43c1df85aaed6e91aadd44a46bff7f5c70f1acc1a193917e3908444632f30e69cfe95d8036d3b6ad318eefd3952804f16613c969e6d13604bb4e723dfad24c42c8d9b5b16a9f5a4b40dcf17b167d319017740f9cc0836436c14d51c3d8a697f1fa2b65196deb5c21b1559c7dea7f598007fa7320909825009f8bf376491c298d8155a382e967042db952e995d14b2f961e1b22f911d1b77895def1c7ef229c87e'); + + _checkBadTailingGarbage(publicKey, S); + _checkBadDigestInfo(publicKey, S, true); + }); + + it('should check tailing garbage and DigestInfo [e=3]', function() { + var N = new JSBN.BigInteger( + '29438513389594867490232201282478838726734464161887801289068585100507839535636256317277708295678804401391394313946142335874609638666081950936114152574870224034382561784743283763961349980806819078028975594777103388280272392844112380900374508170221075553517641170327441791034393719271744724924194371070527213991317221667249077972700842199037403799480569910844701030644322616045408039715278394572328099192023924503077673178227614549351191204851805076359472439160130994385433568113626206477097769842080459156024112389406200687233341779381667082591421496870666931268548504674362230725756397511775557878046572472650613407143'); + var e = new JSBN.BigInteger('3'); + var publicKey = RSA.setPublicKey(N, e); + + var S = UTIL.binary.hex.decode( + '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002853ccc2cd32a8d430dd3bde37e70782ac82cdb7bce3c044219b50aefd689c20d3b840299f28e2fde6c67c8a7f9e528ac222fae947a6dee0d812e3c3b3452171717396e8bedc3132d92d8317e3593642640d1431ef'); + + _checkBadTailingGarbage(publicKey, S); + _checkBadDigestInfo(publicKey, S, true); + }); + + it('should check tailing garbage and DigestInfo [e=5]', function() { + var N = new JSBN.BigInteger( + '29438513389594867490232201282478838726734464161887801289068585100507839535636256317277708295678804401391394313946142335874609638666081950936114152574870224034382561784743283763961349980806819078028975594777103388280272392844112380900374508170221075553517641170327441791034393719271744724924194371070527213991317221667249077972700842199037403799480569910844701030644322616045408039715278394572328099192023924503077673178227614549351191204851805076359472439160130994385433568113626206477097769842080459156024112389406200687233341779381667082591421496870666931268548504674362230725756397511775557878046572472650613407143'); + var e = new JSBN.BigInteger('5'); + var publicKey = RSA.setPublicKey(N, e); + + var S = UTIL.binary.hex.decode( + '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005475fe2681d7125972bd2c2f2c7ab7b8003b03d4a487d6dee07c14eb5212a9fe0071b93f84ba5bb4b0cfaf20c976b11d902013'); + + _checkBadTailingGarbage(publicKey, S); + _checkBadDigestInfo(publicKey, S, true); + }); + + it('should check tailing garbage and DigestInfo [e=17]', function() { + var N = new JSBN.BigInteger( + '928365641661298526294114382771769657905695995680009680444002258089796055192245321020911051590379097587133341820043795407471021630328875171430160513961779154294247563032373839871165519961382202811828883364651574763124699947662060849683176689286181021501400261976653416725246403933613615758181648971537689642956474563961490989544033629566558036444831495046301215543198107208071526376318961481739278769122885031686763776874806317352741548232110892401401727195758835975800106904020775937891505819798776295294696516670437057465296389148672556848624501468669295285428387365416747516180652630054765393335211528084329716917821726670549155619986875030049107668205064454104328601041931972319966348825621299693193542460060799067674344247887198933507132592770898312271636011037138984729256515515185153334743685479709085410902269777563691615719884708908509618352792737826421059819474305949001978916949447029010362775778664826653636547333219983468955600305523140183269580452792812503399042201081785972707218144968460623663922470814889738564730816412201128810370324070680245854669130551872958017494277468722193869883705529583737211815974801292292728082721785855274147991979220001018156560009927148374995236030383474031418802554714043680969417015155298092390680188406177667101020936206754551985229636814788735090951246816765035721775759652424641736739668936540450232814857289312589998505627375553038062765493408460941597629291231866042662108291164359496334978563287523685872262509560463225096226739991402761266388226652661345282274508037924611589455395655512013078629375186805951823181371561289129616028768733583565439798508002546685505512478002960132511531323264596144585611962969372672455541953777622436993987703564293487820434112162562492086865147598436647725445230861246093950020099084994990632102506848190196407855705745530407617253129971665939853842224965079537303198339986953399517682750248394628026225887174258267456078564070387327653989505416943226163989004419377363130466566387761757272563996086708621913140580687414698126490572618509858141748692837570235128900627675422927964369356691123905362222855545719945605604307263252851081309622569225811979426856464673233875589085773616373798857001344093594417138323005260179781153950803127773817702016534081581157881295739782000814998795398671806283018844936919299070562538763900037469485135699677248580365379125702903186174995651938469412191388327852955727869345476087173047665259892129895247785416834855450881318585909376917039'); + var e = new JSBN.BigInteger('17'); + var publicKey = RSA.setPublicKey(N, e); + + var S = UTIL.binary.hex.decode( + '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001eb90acbec1bf590ba1e50960db8381fb5bdc363d46379d09956560a616b88616ce7fa4309dc45f47f5fa47d61bf66baa3d11732ce71768ded295f962'); + + _checkBadTailingGarbage(publicKey, S); + _checkBadDigestInfo(publicKey, S, true); + }); + + it('should check DigestInfo type octet [1]', function() { + var publicKey = RSA.setPublicKey(N, e); + var S = UTIL.binary.hex.decode( + 'd8298a199e1b6ac18f3c0067a004bd9ff7af87be6ad857d73cc3d24ef06195b82aaddb0194f8e61fc31453b9163062255e8baf9c480200d0991a5f764f63d5f6afd283b9cd6afe54f0b7f738707b4eb6b8807539bb627e74db87a50413ab18e504e37975aad1edc612bc8ecad53b81ea249deb5a2acc27e6419c61ab9acec6608f5ae6a2985ba0b6f42d831bc6cce4b044864154b935cf179967d129e0ad8eda9bfbb638121c3ff13c64d439632e62250d4be928a3deb112ef76a025c5d918051e601878eac0049fc9d82be9ae3475deb7ca515c830c20b91b7bedf2184fef66aea0bde62ccd1659afbfd1342322b095309451b1a87e007e640e368fb68a13c9'); + + _checkBadDigestInfo(publicKey, S); + }); + + it('should check DigestInfo type octet [2]', function() { + var publicKey = RSA.setPublicKey(N, e); + var S = UTIL.binary.hex.decode( + 'c1acdd3aef5f0439c254980295fc0d81b628df00726310a1041d79b5dd94c11d3bcaf0236763c77c25d9ab49522ed2a7d6ea3a4e483a29838acd48f2d60a790275f4cd46e4b1d09c527a426ec373e8a21746ad3ea541d3b85ba4c303ff793ea8a0a3458e93a7ec42ed66f675d7c299b0817ac95f7f45b2f48c09b3c070171f31a33ac789da9943da5dabcda1c95b42531d45484ac1efde0fe0519077debb93183e63de8f80d7f3cbfecb03cbb44ac4a2d56699e33fca0663b79ca627755fc4fc684b4ab358a0b4ac5b7e9d0cc18b6ab6300b40781502a1c03d34f31dd19d81195f8a44bc03a2595a706f06f0cb39b8e3f4afe06675fe7439b057f1200a06f4fd'); + + _checkBadDigestInfo(publicKey, S); + }); + }); }); })(); From aa9372d6dd78eb1479392b9274457036c2404b66 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 8 Mar 2022 18:28:05 -0500 Subject: [PATCH 085/127] Add missing RFC 8017 algorithm identifiers. --- CHANGELOG.md | 12 +++++++++--- lib/oids.js | 3 +++ lib/rsa.js | 6 +++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29b968d37..ed4288966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,11 +38,17 @@ Forge ChangeLog 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. An invalid `DigestInfo` or algorithm - identifier will now cause an error to be thrown. + algorithm identifier is a known value from RFC 8017 + `PKCS1-v1-5DigestAlgorithms`. An invalid `DigestInfo` or algorithm identifier + will now cause an error to be thrown. ### Added -- [oid] Added `1.2.840.113549.2.2` / `md2` for hash algorithm checking. +- [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 diff --git a/lib/oids.js b/lib/oids.js index 5483d72cd..d1504eb16 100644 --- a/lib/oids.js +++ b/lib/oids.js @@ -47,6 +47,9 @@ _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'); diff --git a/lib/rsa.js b/lib/rsa.js index 48a4bd261..f3b320212 100644 --- a/lib/rsa.js +++ b/lib/rsa.js @@ -1169,14 +1169,18 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { 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 || + oid === forge.oids['sha512-224'] || + oid === forge.oids['sha512-256'])) { var error = new Error( 'Unknown RSASSA-PKCS1-v1_5 DigestAlgorithm identifier.'); error.oid = oid; From a4405bb9d6b638084df478fa4ac60a410332c2d8 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 15 Mar 2022 22:51:59 -0400 Subject: [PATCH 086/127] Improve signature verification tests. - Attribute tests to Moosa Yahyazadeh (moosa-yahyazadeh@uiowa.edu) - Add some details from initial report until it can be included in full. - Line wrap test data. --- tests/unit/rsa.js | 247 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 234 insertions(+), 13 deletions(-) diff --git a/tests/unit/rsa.js b/tests/unit/rsa.js index def5c2d78..f1493545b 100644 --- a/tests/unit/rsa.js +++ b/tests/unit/rsa.js @@ -775,7 +775,12 @@ var UTIL = require('../../lib/util'); } })(); - describe('bad data', function() { + 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 @@ -844,64 +849,244 @@ var UTIL = require('../../lib/util'); 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( - 'e7410e05bdc38d1c72fab784be41df3d3de2ae83894d9ec86cb5fe343d5dc7d45df2a36fc60363faf32f0d37ab457648af40a48a6c53ae7af0575e92cb1ffc236d55e1325af8c71b3ac313f2630fb498b8e1546093aca1ed56026a96cb525d991159a2d6ccbfd5ef63ae718f8ace2469e357ccf3f6a048bbf9760f5fb36b9dd38fb330eab504f05078b83f5d8bd95dce8fccc6b46babd56f678300f2b39083e53e04e79f503358a6222f8dd66b561fea3a51ecf3be16c9e2ea6ba8aaed9fbe6ba510ff752e4529385f759d4d6120b15f65534248ed5bbb1307a7d0a9838329697f5fbae91f48e478dcbb77190f0d173b6cb8b1299cf4202570d25d11a7862b47'); + '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( - 'c2ad2fa23c246ee98c453d69023e7ec05956b48bd0e287341ba9d342ad49b0fff2bcbb9adc50f1ccbfc54106305cc74a88db89ff94901a08359893a08426373e7949a8794798233445af6c48bc6ccbe278bdeb62c31e40c3bf0014af2faadcc9ed7885756789a5b95c2a355fbb3f04412f42e0f9ed335ab51af8f091a62aaaaf6577422220917daaece3ca2f4e66dc4e0574356762592052b406768c31c25cf4c1754e6da9dc3440e238c4f9b25cccc174dd1b17b027e0f9ce2763b86f0e6871690ddd018d2e774bc968c9c6e907a000daf5044ba31a0b9eefbd7b4b1ec466d20bc1dd3f020cb1091af6b476416da3024ea046b09fbbbc4d2355da9a2bc6ddb9'); + 'c2ad2fa23c246ee98c453d69023e7ec05956b48bd0e287341ba9d342ad49b0ff' + + 'f2bcbb9adc50f1ccbfc54106305cc74a88db89ff94901a08359893a08426373e' + + '7949a8794798233445af6c48bc6ccbe278bdeb62c31e40c3bf0014af2faadcc9' + + 'ed7885756789a5b95c2a355fbb3f04412f42e0f9ed335ab51af8f091a62aaaaf' + + '6577422220917daaece3ca2f4e66dc4e0574356762592052b406768c31c25cf4' + + 'c1754e6da9dc3440e238c4f9b25cccc174dd1b17b027e0f9ce2763b86f0e6871' + + '690ddd018d2e774bc968c9c6e907a000daf5044ba31a0b9eefbd7b4b1ec466d2' + + '0bc1dd3f020cb1091af6b476416da3024ea046b09fbbbc4d2355da9a2bc6ddb9'); _checkBadTailingGarbage(publicKey, S); _checkBadDigestInfo(publicKey, S, true); }); - it('should check tailing garbage and DigestIfno [2]', function() { + 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( - 'a7c5812d7fc0eef766a481aac18c8c48483daf9b5ffb6614bd98ebe4ecb746dd493cf5dd2cbe16ecaa0b52109b744930eda49316605fc823fd57a68b5b2c62e8c1b158b26e1547a2e33cdd79427d7c513f07d02261ffe43db197d8cddca2b5b43c1df85aaed6e91aadd44a46bff7f5c70f1acc1a193917e3908444632f30e69cfe95d8036d3b6ad318eefd3952804f16613c969e6d13604bb4e723dfad24c42c8d9b5b16a9f5a4b40dcf17b167d319017740f9cc0836436c14d51c3d8a697f1fa2b65196deb5c21b1559c7dea7f598007fa7320909825009f8bf376491c298d8155a382e967042db952e995d14b2f961e1b22f911d1b77895def1c7ef229c87e'); + '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( - '29438513389594867490232201282478838726734464161887801289068585100507839535636256317277708295678804401391394313946142335874609638666081950936114152574870224034382561784743283763961349980806819078028975594777103388280272392844112380900374508170221075553517641170327441791034393719271744724924194371070527213991317221667249077972700842199037403799480569910844701030644322616045408039715278394572328099192023924503077673178227614549351191204851805076359472439160130994385433568113626206477097769842080459156024112389406200687233341779381667082591421496870666931268548504674362230725756397511775557878046572472650613407143'); + '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( - '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002853ccc2cd32a8d430dd3bde37e70782ac82cdb7bce3c044219b50aefd689c20d3b840299f28e2fde6c67c8a7f9e528ac222fae947a6dee0d812e3c3b3452171717396e8bedc3132d92d8317e3593642640d1431ef'); + '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( - '29438513389594867490232201282478838726734464161887801289068585100507839535636256317277708295678804401391394313946142335874609638666081950936114152574870224034382561784743283763961349980806819078028975594777103388280272392844112380900374508170221075553517641170327441791034393719271744724924194371070527213991317221667249077972700842199037403799480569910844701030644322616045408039715278394572328099192023924503077673178227614549351191204851805076359472439160130994385433568113626206477097769842080459156024112389406200687233341779381667082591421496870666931268548504674362230725756397511775557878046572472650613407143'); + '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( - '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005475fe2681d7125972bd2c2f2c7ab7b8003b03d4a487d6dee07c14eb5212a9fe0071b93f84ba5bb4b0cfaf20c976b11d902013'); + '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( - '928365641661298526294114382771769657905695995680009680444002258089796055192245321020911051590379097587133341820043795407471021630328875171430160513961779154294247563032373839871165519961382202811828883364651574763124699947662060849683176689286181021501400261976653416725246403933613615758181648971537689642956474563961490989544033629566558036444831495046301215543198107208071526376318961481739278769122885031686763776874806317352741548232110892401401727195758835975800106904020775937891505819798776295294696516670437057465296389148672556848624501468669295285428387365416747516180652630054765393335211528084329716917821726670549155619986875030049107668205064454104328601041931972319966348825621299693193542460060799067674344247887198933507132592770898312271636011037138984729256515515185153334743685479709085410902269777563691615719884708908509618352792737826421059819474305949001978916949447029010362775778664826653636547333219983468955600305523140183269580452792812503399042201081785972707218144968460623663922470814889738564730816412201128810370324070680245854669130551872958017494277468722193869883705529583737211815974801292292728082721785855274147991979220001018156560009927148374995236030383474031418802554714043680969417015155298092390680188406177667101020936206754551985229636814788735090951246816765035721775759652424641736739668936540450232814857289312589998505627375553038062765493408460941597629291231866042662108291164359496334978563287523685872262509560463225096226739991402761266388226652661345282274508037924611589455395655512013078629375186805951823181371561289129616028768733583565439798508002546685505512478002960132511531323264596144585611962969372672455541953777622436993987703564293487820434112162562492086865147598436647725445230861246093950020099084994990632102506848190196407855705745530407617253129971665939853842224965079537303198339986953399517682750248394628026225887174258267456078564070387327653989505416943226163989004419377363130466566387761757272563996086708621913140580687414698126490572618509858141748692837570235128900627675422927964369356691123905362222855545719945605604307263252851081309622569225811979426856464673233875589085773616373798857001344093594417138323005260179781153950803127773817702016534081581157881295739782000814998795398671806283018844936919299070562538763900037469485135699677248580365379125702903186174995651938469412191388327852955727869345476087173047665259892129895247785416834855450881318585909376917039'); + '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( - '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001eb90acbec1bf590ba1e50960db8381fb5bdc363d46379d09956560a616b88616ce7fa4309dc45f47f5fa47d61bf66baa3d11732ce71768ded295f962'); + '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); @@ -909,16 +1094,52 @@ var UTIL = require('../../lib/util'); 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( - 'd8298a199e1b6ac18f3c0067a004bd9ff7af87be6ad857d73cc3d24ef06195b82aaddb0194f8e61fc31453b9163062255e8baf9c480200d0991a5f764f63d5f6afd283b9cd6afe54f0b7f738707b4eb6b8807539bb627e74db87a50413ab18e504e37975aad1edc612bc8ecad53b81ea249deb5a2acc27e6419c61ab9acec6608f5ae6a2985ba0b6f42d831bc6cce4b044864154b935cf179967d129e0ad8eda9bfbb638121c3ff13c64d439632e62250d4be928a3deb112ef76a025c5d918051e601878eac0049fc9d82be9ae3475deb7ca515c830c20b91b7bedf2184fef66aea0bde62ccd1659afbfd1342322b095309451b1a87e007e640e368fb68a13c9'); + '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( - 'c1acdd3aef5f0439c254980295fc0d81b628df00726310a1041d79b5dd94c11d3bcaf0236763c77c25d9ab49522ed2a7d6ea3a4e483a29838acd48f2d60a790275f4cd46e4b1d09c527a426ec373e8a21746ad3ea541d3b85ba4c303ff793ea8a0a3458e93a7ec42ed66f675d7c299b0817ac95f7f45b2f48c09b3c070171f31a33ac789da9943da5dabcda1c95b42531d45484ac1efde0fe0519077debb93183e63de8f80d7f3cbfecb03cbb44ac4a2d56699e33fca0663b79ca627755fc4fc684b4ab358a0b4ac5b7e9d0cc18b6ab6300b40781502a1c03d34f31dd19d81195f8a44bc03a2595a706f06f0cb39b8e3f4afe06675fe7439b057f1200a06f4fd'); + 'c1acdd3aef5f0439c254980295fc0d81b628df00726310a1041d79b5dd94c11d' + + '3bcaf0236763c77c25d9ab49522ed2a7d6ea3a4e483a29838acd48f2d60a7902' + + '75f4cd46e4b1d09c527a426ec373e8a21746ad3ea541d3b85ba4c303ff793ea8' + + 'a0a3458e93a7ec42ed66f675d7c299b0817ac95f7f45b2f48c09b3c070171f31' + + 'a33ac789da9943da5dabcda1c95b42531d45484ac1efde0fe0519077debb9318' + + '3e63de8f80d7f3cbfecb03cbb44ac4a2d56699e33fca0663b79ca627755fc4fc' + + '684b4ab358a0b4ac5b7e9d0cc18b6ab6300b40781502a1c03d34f31dd19d8119' + + '5f8a44bc03a2595a706f06f0cb39b8e3f4afe06675fe7439b057f1200a06f4fd'); _checkBadDigestInfo(publicKey, S); }); From d4395fec831622837ecfec9e428d4620e208f9a8 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 15 Mar 2022 23:09:41 -0400 Subject: [PATCH 087/127] Update changelog. --- CHANGELOG.md | 52 ++++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed4288966..231efebae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,29 +4,30 @@ Forge ChangeLog ## 1.3.0 - 2022-XXX ### Security -- **SECURITY**: Three RSA PKCS#1 v1.5 signature verification issues were - reported by Moosa Yahyazadeh (moosa-yahyazadeh@uiowa.edu). - - 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. - - 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. - - 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. +- 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. +- **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. +- **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. ### Fixed - [asn1] Add fallback to pretty print invalid UTF8 data. @@ -40,7 +41,10 @@ Forge ChangeLog `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 cause an error to be thrown. + 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 From bb822c02df0b61211836472e29b9790cc541cdb2 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 17 Mar 2022 18:54:51 -0400 Subject: [PATCH 088/127] Add advisory links. --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 231efebae..a7384fb85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Forge ChangeLog ["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 @@ -24,10 +26,14 @@ Forge ChangeLog 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. From dc77b39dd347e7f8b60a0f25a311fe5f06130579 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 17 Mar 2022 19:12:01 -0400 Subject: [PATCH 089/127] Fix error checking. - Newer object style not available in older platforms. - Using message regex style. --- tests/unit/rsa.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/unit/rsa.js b/tests/unit/rsa.js index f1493545b..1cd9e072e 100644 --- a/tests/unit/rsa.js +++ b/tests/unit/rsa.js @@ -829,9 +829,8 @@ var UTIL = require('../../lib/util'); ASSERT.throws(function() { publicKey.verify(md.digest().getBytes(), S); - }, { - message: 'Unparsed DER bytes remain after ASN.1 parsing.' - }); + }, + /^Error: Unparsed DER bytes remain after ASN.1 parsing.$/); } function _checkBadDigestInfo(publicKey, S, skipTailingGarbage) { @@ -842,9 +841,8 @@ var UTIL = require('../../lib/util'); publicKey.verify(md.digest().getBytes(), S, undefined, { _parseAllDigestBytes: !skipTailingGarbage }); - }, { - message: 'ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 DigestInfo value.' - }); + }, + /^Error: ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 DigestInfo value.$/); } it('should check DigestInfo structure', function() { From 0f3972ad5883a9869703c6f54a0627bc454bca47 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 17 Mar 2022 19:20:23 -0400 Subject: [PATCH 090/127] Update changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7384fb85..2987c54b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Forge ChangeLog =============== -## 1.3.0 - 2022-XXX +## 1.3.0 - 2022-03-17 ### Security - Three RSA PKCS#1 v1.5 signature verification issues were reported by Moosa From 6c5b90133d46af63d139b98bf65371732c8c7dad Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 17 Mar 2022 19:20:24 -0400 Subject: [PATCH 091/127] Release 1.3.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ab7771410..68231c0ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.2.2-0", + "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": { From cbf0bd590d47fe3120a57e7c36f2f4e64381ad81 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 17 Mar 2022 19:21:01 -0400 Subject: [PATCH 092/127] Start 1.3.1-0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 68231c0ee..69d67648a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.3.0", + "version": "1.3.1-0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From 56f4316b4cc6592e678f8c416209c45984b6547b Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Mon, 28 Mar 2022 14:41:04 +0100 Subject: [PATCH 093/127] Allow DigestInfo.DigestAlgorith.parameters to be optional --- lib/rsa.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rsa.js b/lib/rsa.js index f3b320212..86a7557e0 100644 --- a/lib/rsa.js +++ b/lib/rsa.js @@ -286,6 +286,7 @@ var digestInfoValidator = { name: 'DigestInfo.DigestAlgorithm.parameters', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.NULL, + optional: true, constructed: false }] }, { From 740954d747ac56b76a6e1ae12a057c9548843436 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 29 Mar 2022 20:01:31 -0400 Subject: [PATCH 094/127] Allow optional DigestAlgorithm parameters. RFC 3447 and RFC 8017 allow for optional `DigestAlgorithm` `NULL` parameters for `sha*` algorithms and require `NULL` paramters for `md2` and `md5` algorithms. --- CHANGELOG.md | 7 +++++++ lib/rsa.js | 12 ++++++++++++ tests/unit/rsa.js | 11 ++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2987c54b2..f0d08cca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Forge ChangeLog =============== +## 1.3.1 - 2022-03-xx + +### Fixes +- RFC 3447 and RFC 8017 allow for optional `DigestAlgorithm` `NULL` parameters + for `sha*` algorithms and require `NULL` paramters for `md2` and `md5` + algorithms. + ## 1.3.0 - 2022-03-17 ### Security diff --git a/lib/rsa.js b/lib/rsa.js index 86a7557e0..5c73209f9 100644 --- a/lib/rsa.js +++ b/lib/rsa.js @@ -286,6 +286,8 @@ var digestInfoValidator = { name: 'DigestInfo.DigestAlgorithm.parameters', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.NULL, + // captured only to check existence for md2 and md5 + capture: 'parameters', optional: true, constructed: false }] @@ -1188,6 +1190,16 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { throw error; } + // special check for md2 and md5 that NULL parameters exist + if(oid === forge.oids.md2 || oid === forge.oids.md5) { + if(!('parameters' in capture)) { + throw new Error( + 'ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 ' + + 'DigestInfo value. ' + + 'Missing algorithm identifer NULL parameters.'); + } + } + // compare the given digest to the decrypted one return digest === capture.digest; } diff --git a/tests/unit/rsa.js b/tests/unit/rsa.js index 1cd9e072e..b1c1b64aa 100644 --- a/tests/unit/rsa.js +++ b/tests/unit/rsa.js @@ -845,6 +845,15 @@ var UTIL = require('../../lib/util'); /^Error: ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 DigestInfo value.$/); } + function _checkGoodDigestInfo(publicKey, S, skipTailingGarbage) { + var md = MD.sha256.create(); + md.update(m); + + ASSERT.ok(publicKey.verify(md.digest().getBytes(), S, undefined, { + _parseAllDigestBytes: !skipTailingGarbage + })); + } + it('should check DigestInfo structure', function() { var publicKey = RSA.setPublicKey(N, e); // 0xff bytes stolen from padding @@ -904,7 +913,7 @@ var UTIL = require('../../lib/util'); '0bc1dd3f020cb1091af6b476416da3024ea046b09fbbbc4d2355da9a2bc6ddb9'); _checkBadTailingGarbage(publicKey, S); - _checkBadDigestInfo(publicKey, S, true); + _checkGoodDigestInfo(publicKey, S, true); }); it('should check tailing garbage and DigestInfo [2]', function() { From a33830f61c351e8e3a34309767e8dd0de148376b Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 29 Mar 2022 20:11:45 -0400 Subject: [PATCH 095/127] Update changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0d08cca2..27d0e3a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Forge ChangeLog =============== -## 1.3.1 - 2022-03-xx +## 1.3.1 - 2022-03-29 ### Fixes - RFC 3447 and RFC 8017 allow for optional `DigestAlgorithm` `NULL` parameters From a0a4a4264bedb3296974b9675349c9c190144aeb Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 29 Mar 2022 20:11:45 -0400 Subject: [PATCH 096/127] Release 1.3.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 69d67648a..1a63de157 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.3.1-0", + "version": "1.3.1", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From 2bb97afb5058285ef09bcf1d04d6bd6b87cffd58 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 29 Mar 2022 20:12:23 -0400 Subject: [PATCH 097/127] Start 1.3.2-0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1a63de157..459985a24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.3.1", + "version": "1.3.2-0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From d92bf78474ef82eb9e7829e5a93400f8251d1639 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 19 Apr 2022 19:08:23 -0400 Subject: [PATCH 098/127] Test on Node.js 16.x and 18.x. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a63a29963..431811e09 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [6.x, 8.x, 10.x, 12.x, 14.x] + node-version: [6.x, 8.x, 10.x, 12.x, 14.x, 16.x, 18.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} From 8ceb8acd98562323e4823ccd85e5a8a07a2721a9 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Wed, 22 May 2024 13:32:03 -0400 Subject: [PATCH 099/127] Test on Node.js 20.x and 22.x. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 431811e09..e8d6a0b0a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [6.x, 8.x, 10.x, 12.x, 14.x, 16.x, 18.x] + node-version: [6.x, 8.x, 10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} From 7e3a41177fde85b74f9a06d062789b9983448164 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Wed, 22 May 2024 23:34:06 -0400 Subject: [PATCH 100/127] Fix Node.js versions used during testing. - Use older Node.js to install due to webpack issues. - Use target Node.js version to test. --- .github/workflows/main.yml | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e8d6a0b0a..67556e93d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,12 +10,18 @@ jobs: matrix: node-version: [6.x, 8.x, 10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + # FIXME: Install/build with 16.x until webpack is updated + - name: Use Node.js 16.x + uses: actions/setup-node@v4 + with: + node-version: 16.x + - run: npm install + # FIXME: Run tests with target version until webpack is updated - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 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: @@ -23,12 +29,12 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [14.x] + node-version: [16.x] bundler: [webpack, browserify] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install @@ -41,11 +47,11 @@ jobs: # timeout-minutes: 10 # strategy: # matrix: -# node-version: [14.x] +# node-version: [16.x] # steps: -# - uses: actions/checkout@v2 +# - uses: actions/checkout@v4 # - name: Use Node.js ${{ matrix.node-version }} -# uses: actions/setup-node@v1 +# uses: actions/setup-node@v4 # with: # node-version: ${{ matrix.node-version }} # - run: npm install @@ -56,18 +62,19 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [14.x] + node-version: [16.x] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install - name: Generate coverage report run: npm run coverage-ci - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v4 with: file: ./coverage/lcov.info fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} From 7208bf4dfe7e498a29521be7359156356a79634d Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 13 Nov 2025 18:27:14 -0500 Subject: [PATCH 101/127] Test on Node.js 24.x. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 67556e93d..77be5fef2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [6.x, 8.x, 10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x] + node-version: [6.x, 8.x, 10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x, 24.x] steps: - uses: actions/checkout@v4 # FIXME: Install/build with 16.x until webpack is updated From 1574803ae5880678ac87231086ad1bddd085e60b Mon Sep 17 00:00:00 2001 From: Samanta Navarro Date: Tue, 17 Jun 2025 11:55:49 +0000 Subject: [PATCH 102/127] Fix typos Typos found with codespell. --- README.md | 2 +- flash/SocketPool.as | 2 +- lib/aes.js | 2 +- lib/pbe.js | 2 +- lib/prime.worker.js | 2 +- lib/rsa.js | 12 ++++++------ lib/tls.js | 4 ++-- lib/util.js | 6 +++--- lib/x509.js | 2 +- lib/xhr.js | 2 +- tests/legacy/socketPool.html | 2 +- tests/legacy/tls.html | 2 +- tests/legacy/xhr.html | 2 +- tests/support/task.js | 2 +- tests/unit/asn1.js | 6 +++--- tests/unit/rsa.js | 2 +- tests/websockets/server-webid.js | 2 +- 17 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 6f3279efb..06cff04c9 100644 --- a/README.md +++ b/README.md @@ -2024,7 +2024,7 @@ Contact Donations --------- -Financial support is welcome and helps contribute to futher development: +Financial support is welcome and helps contribute to further development: * For [PayPal][] please send to paypal@digitalbazaar.com. * Something else? Please contact support@digitalbazaar.com. diff --git a/flash/SocketPool.as b/flash/SocketPool.as index f61dfa855..384400fd5 100644 --- a/flash/SocketPool.as +++ b/flash/SocketPool.as @@ -42,7 +42,7 @@ package private var mEventDispatcher:EventDispatcher; /** - * Creates a new, unitialized SocketPool. + * Creates a new, uninitialized SocketPool. * * @throws Error - if no external interface is available to provide * javascript access. diff --git a/lib/aes.js b/lib/aes.js index 3c1ddb29b..5bdd2acc3 100644 --- a/lib/aes.js +++ b/lib/aes.js @@ -325,7 +325,7 @@ var imix; // inverse mix-columns table * The word [a0, a1, a2, a3] is a polynomial a3x^3 + a2x^2 + a1x + a0. * * Addition is performed by XOR'ing like powers of x. Multiplication - * is performed in two steps, the first is an algebriac expansion as + * is performed in two steps, the first is an algebraic expansion as * you would do normally (where addition is XOR). But the result is * a polynomial larger than 3 degrees and thus it cannot fit in a word. So * next the result is modularly reduced by an AES-specific polynomial of diff --git a/lib/pbe.js b/lib/pbe.js index cf8456ba6..f3cfa70dd 100644 --- a/lib/pbe.js +++ b/lib/pbe.js @@ -672,7 +672,7 @@ pki.pbe.generatePkcs12Key = function(password, salt, id, iter, n, md) { D.fillWithByte(id, v); /* 2. Concatenate copies of the salt together to create a string S of length - v * ceil(s / v) bytes (the final copy of the salt may be trunacted + v * ceil(s / v) bytes (the final copy of the salt may be truncated to create S). Note that if the salt is the empty string, then so is S. */ var Slen = v * Math.ceil(s / v); diff --git a/lib/prime.worker.js b/lib/prime.worker.js index ce1355d9a..b145f8aeb 100644 --- a/lib/prime.worker.js +++ b/lib/prime.worker.js @@ -56,7 +56,7 @@ function findPrime(data) { } function isProbablePrime(n) { - // divide by low primes, ignore even checks, etc (n alread aligned properly) + // divide by low primes, ignore even checks, etc (n already aligned properly) var i = 1; while(i < LOW_PRIMES.length) { var m = LOW_PRIMES[i]; diff --git a/lib/rsa.js b/lib/rsa.js index 5c73209f9..b207a6385 100644 --- a/lib/rsa.js +++ b/lib/rsa.js @@ -282,7 +282,7 @@ var digestInfoValidator = { constructed: false, capture: 'algorithmIdentifier' }, { - // NULL paramters + // NULL parameters name: 'DigestInfo.DigestAlgorithm.parameters', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.NULL, @@ -316,7 +316,7 @@ var digestInfoValidator = { * * @param md the message digest object with the hash to sign. * - * @return the encoded message (ready for RSA encrytion) + * @return the encoded message (ready for RSA encryption) */ var emsaPkcs1v15encode = function(md) { // get the oid for the algorithm @@ -498,7 +498,7 @@ var _modPow = function(x, key, pub) { * * The parameter bt controls whether to put padding bytes before the * message passed in. Set bt to either true or false to disable padding - * completely (in order to handle e.g. EMSA-PSS encoding seperately before), + * completely (in order to handle e.g. EMSA-PSS encoding separately before), * signaling whether the encryption operation is a public key operation * (i.e. encrypting data) or not, i.e. private key operation (data signing). * @@ -1173,7 +1173,7 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { } // check hash algorithm identifier // see PKCS1-v1-5DigestAlgorithms in RFC 8017 - // FIXME: add support to vaidator for strict value choices + // FIXME: add support to validator for strict value choices var oid = asn1.derToOid(capture.algorithmIdentifier); if(!(oid === forge.oids.md2 || oid === forge.oids.md5 || @@ -1196,7 +1196,7 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { throw new Error( 'ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 ' + 'DigestInfo value. ' + - 'Missing algorithm identifer NULL parameters.'); + 'Missing algorithm identifier NULL parameters.'); } } @@ -1637,7 +1637,7 @@ function _decodePkcs1_v1_5(em, key, pub, ml) { 1. The encryption block EB cannot be parsed unambiguously. 2. The padding string PS consists of fewer than eight octets - or is inconsisent with the block type BT. + or is inconsistent with the block type BT. 3. The decryption process is a public-key operation and the block type BT is not 00 or 01, or the decryption process is a private-key operation and the block type is not 02. diff --git a/lib/tls.js b/lib/tls.js index fadfd646f..00d45089b 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -758,7 +758,7 @@ tls.handleUnexpected = function(c, record) { */ tls.handleHelloRequest = function(c, record, length) { // ignore renegotiation requests from the server during a handshake, but - // if handshaking, send a warning alert that renegotation is denied + // if handshaking, send a warning alert that renegotiation is denied if(!c.handshaking && c.handshakes > 0) { // send alert warning tls.queue(c, tls.createAlert(c, { @@ -2258,7 +2258,7 @@ hsTable[tls.ConnectionEnd.client] = [ ]; // map server current expect state and handshake type to function -// Note: CAD[CH] does not map to FB because renegotation is prohibited +// Note: CAD[CH] does not map to FB because renegotiation is prohibited var H7 = tls.handleClientHello; var H8 = tls.handleClientKeyExchange; var H9 = tls.handleCertificateVerify; diff --git a/lib/util.js b/lib/util.js index aaede5ad2..db2341ac7 100644 --- a/lib/util.js +++ b/lib/util.js @@ -677,7 +677,7 @@ util.ByteStringBuffer.prototype.clear = function() { }; /** - * Shortens this buffer by triming bytes off of the end of this buffer. + * Shortens this buffer by trimming bytes off of the end of this buffer. * * @param count the number of bytes to trim off. * @@ -1343,7 +1343,7 @@ util.DataBuffer.prototype.clear = function() { }; /** - * Shortens this buffer by triming bytes off of the end of this buffer. + * Shortens this buffer by trimming bytes off of the end of this buffer. * * @param count the number of bytes to trim off. * @@ -2316,7 +2316,7 @@ util.format = function(format) { parts.push(''); } break; - // FIXME: do proper formating for numbers, etc + // FIXME: do proper formatting for numbers, etc //case 'f': //case 'd': case '%': diff --git a/lib/x509.js b/lib/x509.js index 2877810c1..99209ffbf 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -2289,7 +2289,7 @@ function _fillMissingExtensionFields(e, options) { * Convert signature parameters object to ASN.1 * * @param {String} oid Signature algorithm OID - * @param params The signature parametrs object + * @param params The signature parameters object * @return ASN.1 object representing signature parameters */ function _signatureParametersToAsn1(oid, params) { diff --git a/lib/xhr.js b/lib/xhr.js index fa928352b..1527042b6 100644 --- a/lib/xhr.js +++ b/lib/xhr.js @@ -459,7 +459,7 @@ xhrApi.create = function(options) { // set request method to given method // set request URL // set username, password - // set asychronous flag + // set asynchronous flag _state.sendFlag = false; xhr.responseText = ''; xhr.responseXML = null; diff --git a/tests/legacy/socketPool.html b/tests/legacy/socketPool.html index 9edcf41aa..fa44a835a 100644 --- a/tests/legacy/socketPool.html +++ b/tests/legacy/socketPool.html @@ -209,7 +209,7 @@

SocketPool Tests

- +

Could not load the flash SocketPool.

diff --git a/tests/legacy/tls.html b/tests/legacy/tls.html index 8f59ee1fb..a20c3c336 100644 --- a/tests/legacy/tls.html +++ b/tests/legacy/tls.html @@ -339,7 +339,7 @@

TLS Tests

- +

Could not load the flash SocketPool.

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

XmlHttpRequest Tests

- +

Could not load the flash SocketPool.

diff --git a/tests/support/task.js b/tests/support/task.js index 4607ecb12..0d614a207 100644 --- a/tests/support/task.js +++ b/tests/support/task.js @@ -66,7 +66,7 @@ var ERROR = 'error'; * SLEEP: sleep for a period of time * WAKEUP: wakeup early from SLEEPING state * CANCEL: cancel further tasks - * FAIL: a failure occured + * FAIL: a failure occurred */ var STOP = 'stop'; var START = 'start'; diff --git a/tests/unit/asn1.js b/tests/unit/asn1.js index 65a1db510..29ff281c1 100644 --- a/tests/unit/asn1.js +++ b/tests/unit/asn1.js @@ -441,7 +441,7 @@ var UTIL = require('../../lib/util'); der = ASN1.toDer(asn1); if(options.roundtrip) { // byte comparisons for round-trip testing can fail due to - // symantically safe changes such as changing the length encoding. + // semantically safe changes such as changing the length encoding. // test a roundtrip for data where it makes sense. ASSERT.equal( UTIL.bytesToHex(bytes), @@ -1047,7 +1047,7 @@ var UTIL = require('../../lib/util'); // could extend out to any structure size: _add(b, '02 06 FF FF FF FF FF FF'); // the roundtrip issue can exist for long lengths that could - // compress to short lenghts, this could be output as '02 02 01 23': + // compress to short lengths, this could be output as '02 02 01 23': _add(b, '02 81 02 01 23'); // also an issue for indefinite length structures that will // have a known length later: @@ -1278,7 +1278,7 @@ var UTIL = require('../../lib/util'); UTIL.bytesToHex(derOut), UTIL.bytesToHex(_h2b(hout))); } - // optimial + // optimal _test('02 01 01', '02 01 01'); _test('02 01 FF', '02 01 FF'); _test('02 02 00 FF', '02 02 00 FF'); diff --git a/tests/unit/rsa.js b/tests/unit/rsa.js index b1c1b64aa..d68946d8f 100644 --- a/tests/unit/rsa.js +++ b/tests/unit/rsa.js @@ -581,7 +581,7 @@ var UTIL = require('../../lib/util'); /* Second step, use private key decryption to verify successful encryption. The encrypted message differs every time, since it is padded with random data. Therefore just rely on the decryption - routine to work, which is tested seperately against an externally + routine to work, which is tested separately against an externally provided encrypted message. */ key = PKI.privateKeyFromPem(params.privateKeyPem); ASSERT.equal(key.decrypt(data), message); diff --git a/tests/websockets/server-webid.js b/tests/websockets/server-webid.js index 5319372bb..c2d4e2a1b 100644 --- a/tests/websockets/server-webid.js +++ b/tests/websockets/server-webid.js @@ -80,7 +80,7 @@ var getPublicKey = function(data, uri, callback) { var hex = CERT + 'hex'; var decimal = CERT + 'decimal'; - // gets a resource identifer from a node + // gets a resource identifier from a node var getResource = function(node, key) { var rval = null; From 3e0c35ace169cfca529a3e547a7848dc7bf57fdb Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 24 Nov 2025 23:16:22 -0500 Subject: [PATCH 103/127] Fix "ASN.1 OID Integer Truncation" advisory. - [asn1] Improve OID handling. - Error on parsed OID values larger than `2**32 - 1`. - Error on DER OID values larger than `2**53 - 1 `. --- CHANGELOG.md | 17 +++++++++++++++++ lib/asn1.js | 11 ++++++++++- tests/unit/asn1.js | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27d0e3a00..540c00850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ Forge ChangeLog =============== +## 1.3.2 - 2025-11-xx + +### Security +- **MODERATE**: ASN.1 OID Integer Truncation + - An Integer Overflow (CWE-190) vulnerability in node-forge versions 1.3.1 + and below enables remote, unauthenticated attackers to craft ASN.1 + structures containing OIDs with oversized arcs. These arcs may be decoded + as smaller, trusted OIDs due to 32-bit bitwise truncation, enabling the + bypass of downstream OID-based security decisions. + - Reported by Hunter Wodzenski. + - GHSA ID: [GHSA-65ch-62r8-g69g](https://github.com/digitalbazaar/forge/security/advisories/GHSA-65ch-62r8-g69g) + +### Fixed +- [asn1] Improve OID handling. + - Error on parsed OID values larger than `2**32 - 1`. + - Error on DER OID values larger than `2**53 - 1 `. + ## 1.3.1 - 2022-03-29 ### Fixes diff --git a/lib/asn1.js b/lib/asn1.js index 4025f8a9e..3bc061fc5 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -773,6 +773,10 @@ asn1.oidToDer = function(oid) { last = true; valueBytes = []; value = parseInt(values[i], 10); + // TODO: Change bitwise logic to allow larger values. + if(value > 0xffffffff) { + throw new Error('OID value too large; max is 32-bits.'); + } do { b = value & 0x7F; value = value >>> 7; @@ -818,8 +822,13 @@ asn1.derToOid = function(bytes) { // the last byte for each value var value = 0; while(bytes.length() > 0) { + // error if 7b shift would exceed Number.MAX_SAFE_INTEGER + // (Number.MAX_SAFE_INTEGER / 128) + if(value > 0x3fffffffffff) { + throw new Error('OID value too large; max is 53-bits.'); + } b = bytes.getByte(); - value = value << 7; + value = value * 128; // not the last byte for the value if(b & 0x80) { value += b & 0x7F; diff --git a/tests/unit/asn1.js b/tests/unit/asn1.js index 29ff281c1..a99783cb9 100644 --- a/tests/unit/asn1.js +++ b/tests/unit/asn1.js @@ -10,11 +10,50 @@ var UTIL = require('../../lib/util'); ASSERT.equal(ASN1.oidToDer('1.2.840.113549').toHex(), '2a864886f70d'); }); + it('should convert a 32b OID to DER', function() { + ASSERT.equal(ASN1.oidToDer('1.2.4294967295').toHex(), '2a8fffffff7f'); + }); + + it('should not convert a >32b OID to DER', function() { + ASSERT.throws( + function() { + ASN1.oidToDer('1.2.4294967296'); + }, + /OID value too large; max is 32-bits./ + ); + }); + it('should convert an OID from DER', function() { var der = UTIL.hexToBytes('2a864886f70d'); ASSERT.equal(ASN1.derToOid(der), '1.2.840.113549'); }); + it('should convert a 32b OID from DER', function() { + var der = UTIL.hexToBytes('2a8fffffff7f'); + ASSERT.equal(ASN1.derToOid(der), '1.2.4294967295'); + }); + + it('should convert a >32b OID from DER', function() { + var der = UTIL.hexToBytes('2a9080808001'); + ASSERT.equal(ASN1.derToOid(der), '1.2.4294967297'); + }); + + it('should convert a max safe int OID from DER', function() { + var der = UTIL.hexToBytes('2a8fffffffffffff7f'); + ASSERT.equal(ASN1.derToOid(der), '1.2.9007199254740991'); + }); + + it('should not convert a >max safe int OID from DER', function() { + ASSERT.throws( + function() { + // '1.2.9007199254740992' + var der = UTIL.hexToBytes('2a9080808080808000'); + console.log(ASN1.derToOid(der)); + }, + /OID value too large; max is 53-bits./ + ); + }); + it('should convert INTEGER 0 to DER', function() { ASSERT.equal(ASN1.integerToDer(0).toHex(), '00'); }); From 260425c6167a38aae038697132483b5517b26451 Mon Sep 17 00:00:00 2001 From: wodzen Date: Sat, 22 Nov 2025 10:35:50 -0800 Subject: [PATCH 104/127] Add ASN.1 recursion depth limit --- lib/asn1.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/asn1.js b/lib/asn1.js index 3bc061fc5..22e0ec690 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -178,6 +178,11 @@ asn1.Type = { BMPSTRING: 30 }; +/** + * Sets the default maximum recursion depth when parsing ASN.1 structures. + */ +asn1.maxDepth = 256; + /** * Creates a new asn1 object. * @@ -448,6 +453,9 @@ asn1.fromDer = function(bytes, options) { if(!('decodeBitStrings' in options)) { options.decodeBitStrings = true; } + if(!('maxDepth' in options)) { + options.maxDepth = asn1.maxDepth; + } // wrap in buffer if needed if(typeof bytes === 'string') { @@ -476,6 +484,12 @@ asn1.fromDer = function(bytes, options) { * @return the parsed asn1 object. */ function _fromDer(bytes, remaining, depth, options) { + + // check depth limit + if(depth >= options.maxDepth) { + throw new Error('ASN.1 parsing error: Max depth exceeded.'); + } + // temporary storage for consumption calculations var start; From 826516e428626de58a98d221949c5bc0efa6c407 Mon Sep 17 00:00:00 2001 From: wodzen Date: Sat, 22 Nov 2025 11:10:44 -0800 Subject: [PATCH 105/127] Regression test for GHSA-554w-wpv2-vw27 patch --- tests/security/ghsa-554w-wpv2-vw27.js | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tests/security/ghsa-554w-wpv2-vw27.js diff --git a/tests/security/ghsa-554w-wpv2-vw27.js b/tests/security/ghsa-554w-wpv2-vw27.js new file mode 100644 index 000000000..01b1919b2 --- /dev/null +++ b/tests/security/ghsa-554w-wpv2-vw27.js @@ -0,0 +1,65 @@ +/* + * Regression Test for GHSA-554w-wpv2-vw27 + * Verifies that the parser enforces a maximum recursion depth + * instead of crashing with a call stack overflow. + */ +const assert = require('assert'); +const asn1 = require('../../lib/asn1'); +const util = require('../../lib/util'); + +describe('GHSA-554w-wpv2-vw27 Security Patch', () => { + + function createNestedDer(depth) { + let obj = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, '\x00'); + for (let i = 0; i < depth; i++) { + obj = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [obj]); + } + return asn1.toDer(obj).getBytes(); + } + + it('should throw a manageable error when default recursion depth is exceeded', () => { + // create a payload just above the default limit (256) + const DANGEROUS_DEPTH = 257; + const der = createNestedDer(DANGEROUS_DEPTH); + const buf = util.createBuffer(der); + + // assert that it throws the correct error + assert.throws(() => { + asn1.fromDer(buf, { strict: true }); + }, /ASN.1 parsing error: Max depth exceeded./); + }); + + it('should throw a manageable error when optional recursion depth is exceeded', () => { + // create a payload just above the optional defined limit (128) + const DANGEROUS_DEPTH = 257; + const der = createNestedDer(DANGEROUS_DEPTH); + const buf = util.createBuffer(der); + + // assert that it throws the correct error + assert.throws(() => { + asn1.fromDer(buf, { strict: true, maxDepth: 128 }); + }, /ASN.1 parsing error: Max depth exceeded./); + }); + + it('should still parse valid nested structures within default limits', () => { + // verify we didn't break default depth functionality + const SAFE_DEPTH = 20; + const der = createNestedDer(SAFE_DEPTH); + const buf = util.createBuffer(der); + + assert.doesNotThrow(() => { + asn1.fromDer(buf, { strict: true }); + }); + }); + + it('should still parse valid nested structures within optional limits', () => { + // verify we didn't break optional depth functionality + const SAFE_DEPTH = 20; + const der = createNestedDer(SAFE_DEPTH); + const buf = util.createBuffer(der); + + assert.doesNotThrow(() => { + asn1.fromDer(buf, { strict: true, maxDepth: 128 }); + }); + }); +}); \ No newline at end of file From ef3d08383cb0dbf521aaf790d38da74ba23f9033 Mon Sep 17 00:00:00 2001 From: wodzen Date: Sat, 22 Nov 2025 11:13:16 -0800 Subject: [PATCH 106/127] Update regression test depth --- tests/security/ghsa-554w-wpv2-vw27.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/security/ghsa-554w-wpv2-vw27.js b/tests/security/ghsa-554w-wpv2-vw27.js index 01b1919b2..9187e7376 100644 --- a/tests/security/ghsa-554w-wpv2-vw27.js +++ b/tests/security/ghsa-554w-wpv2-vw27.js @@ -31,7 +31,7 @@ describe('GHSA-554w-wpv2-vw27 Security Patch', () => { it('should throw a manageable error when optional recursion depth is exceeded', () => { // create a payload just above the optional defined limit (128) - const DANGEROUS_DEPTH = 257; + const DANGEROUS_DEPTH = 129; const der = createNestedDer(DANGEROUS_DEPTH); const buf = util.createBuffer(der); From b9f9bd4e39b6fbb9c0c7c92d2940b55460f667fd Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 25 Nov 2025 01:25:00 -0500 Subject: [PATCH 107/127] Update changelog. --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 540c00850..8cdda82e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ Forge ChangeLog ## 1.3.2 - 2025-11-xx ### Security +- **HIGH**: ASN.1 Unbounded Recursion + - An Uncontrolled Recursion (CWE-674) vulnerability in node-forge versions + 1.3.1 and below enables remote, unauthenticated attackers to craft deep + ASN.1 structures that trigger unbounded recursive parsing. This leads to a + Denial-of-Service (DoS) via stack exhaustion when parsing untrusted DER + inputs. + - Reported by Hunter Wodzenski. + - GHSA ID: [GHSA-554w-wpv2-vw27](https://github.com/digitalbazaar/forge/security/advisories/GHSA-554w-wpv2-vw27) - **MODERATE**: ASN.1 OID Integer Truncation - An Integer Overflow (CWE-190) vulnerability in node-forge versions 1.3.1 and below enables remote, unauthenticated attackers to craft ASN.1 @@ -14,6 +22,16 @@ Forge ChangeLog - GHSA ID: [GHSA-65ch-62r8-g69g](https://github.com/digitalbazaar/forge/security/advisories/GHSA-65ch-62r8-g69g) ### Fixed +- [asn1] Add `fromDer()` max recursion depth check. + - Add a `asn1.maxDepth` global configurable maximum depth of 256. + - Add a `asn1.fromDer()` per-call `maxDepth` option. + - **NOTE**: The default maximum is assumed to be higher than needed for valid + data. If this assumption is false then this could be a breaking change. + Please file an issue if there are use cases that need a higher maximum. + - **NOTE**: The per-call `maxDepth` parameter has not been exposed up through + all of the API stack due to the complexities involved. Please file an issue + if there are use cases that require this instead of changing the default + maximum. - [asn1] Improve OID handling. - Error on parsed OID values larger than `2**32 - 1`. - Error on DER OID values larger than `2**53 - 1 `. From c0abc87279a89505fc7fcbc9e2fd046a64ae5a59 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 25 Nov 2025 01:35:56 -0500 Subject: [PATCH 108/127] Add jsdoc for maxDepth. --- lib/asn1.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/asn1.js b/lib/asn1.js index 22e0ec690..5d9a66714 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -424,6 +424,8 @@ var _getValueLength = function(bytes, remaining) { * erroneously decode values that happen to be valid ASN.1. This * flag will be deprecated or removed as soon as schema support is * available. (default: true) + * [maxDepth] override asn1.maxDepth recursion limit + * (default: asn1.maxDepth) * * @throws Will throw an error for various malformed input conditions. * From 15dd726ad23493bad499ca07418240aa27d24d2d Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 25 Nov 2025 01:39:43 -0500 Subject: [PATCH 109/127] Update test style. - Use current forge ES5 style. - Remove unneeded doesNotThrow wrapper. --- tests/security/ghsa-554w-wpv2-vw27.js | 68 +++++++++++++-------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/tests/security/ghsa-554w-wpv2-vw27.js b/tests/security/ghsa-554w-wpv2-vw27.js index 9187e7376..2c9f0001b 100644 --- a/tests/security/ghsa-554w-wpv2-vw27.js +++ b/tests/security/ghsa-554w-wpv2-vw27.js @@ -3,63 +3,59 @@ * Verifies that the parser enforces a maximum recursion depth * instead of crashing with a call stack overflow. */ -const assert = require('assert'); -const asn1 = require('../../lib/asn1'); -const util = require('../../lib/util'); +var assert = require('assert'); +var asn1 = require('../../lib/asn1'); +var util = require('../../lib/util'); + +describe('GHSA-554w-wpv2-vw27 Security Patch', function() { -describe('GHSA-554w-wpv2-vw27 Security Patch', () => { - function createNestedDer(depth) { - let obj = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, '\x00'); - for (let i = 0; i < depth; i++) { + var obj = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, '\x00'); + for(var i = 0; i < depth; i++) { obj = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [obj]); } return asn1.toDer(obj).getBytes(); } - it('should throw a manageable error when default recursion depth is exceeded', () => { + it('should throw a manageable error when default recursion depth is exceeded', function() { // create a payload just above the default limit (256) - const DANGEROUS_DEPTH = 257; - const der = createNestedDer(DANGEROUS_DEPTH); - const buf = util.createBuffer(der); + var DANGEROUS_DEPTH = 257; + var der = createNestedDer(DANGEROUS_DEPTH); + var buf = util.createBuffer(der); // assert that it throws the correct error - assert.throws(() => { - asn1.fromDer(buf, { strict: true }); + assert.throws(function() { + asn1.fromDer(buf, {strict: true}); }, /ASN.1 parsing error: Max depth exceeded./); }); - it('should throw a manageable error when optional recursion depth is exceeded', () => { + it('should throw a manageable error when optional recursion depth is exceeded', function() { // create a payload just above the optional defined limit (128) - const DANGEROUS_DEPTH = 129; - const der = createNestedDer(DANGEROUS_DEPTH); - const buf = util.createBuffer(der); + var DANGEROUS_DEPTH = 129; + var der = createNestedDer(DANGEROUS_DEPTH); + var buf = util.createBuffer(der); // assert that it throws the correct error - assert.throws(() => { - asn1.fromDer(buf, { strict: true, maxDepth: 128 }); + assert.throws(function() { + asn1.fromDer(buf, {strict: true, maxDepth: 128}); }, /ASN.1 parsing error: Max depth exceeded./); }); - it('should still parse valid nested structures within default limits', () => { + it('should still parse valid nested structures within default limits', function() { // verify we didn't break default depth functionality - const SAFE_DEPTH = 20; - const der = createNestedDer(SAFE_DEPTH); - const buf = util.createBuffer(der); - - assert.doesNotThrow(() => { - asn1.fromDer(buf, { strict: true }); - }); + var SAFE_DEPTH = 20; + var der = createNestedDer(SAFE_DEPTH); + var buf = util.createBuffer(der); + + asn1.fromDer(buf, {strict: true}); }); - it('should still parse valid nested structures within optional limits', () => { + it('should still parse valid nested structures within optional limits', function() { // verify we didn't break optional depth functionality - const SAFE_DEPTH = 20; - const der = createNestedDer(SAFE_DEPTH); - const buf = util.createBuffer(der); - - assert.doesNotThrow(() => { - asn1.fromDer(buf, { strict: true, maxDepth: 128 }); - }); + var SAFE_DEPTH = 20; + var der = createNestedDer(SAFE_DEPTH); + var buf = util.createBuffer(der); + + asn1.fromDer(buf, {strict: true, maxDepth: 128}); }); -}); \ No newline at end of file +}); From 68ed375713c8a6795219728a8af26dcadc0041d7 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 25 Nov 2025 01:40:56 -0500 Subject: [PATCH 110/127] Add change of default maxDepth test. - Check the maxDepth does not change between tests. - Check the default maxDepth can be changed. --- tests/security/ghsa-554w-wpv2-vw27.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/security/ghsa-554w-wpv2-vw27.js b/tests/security/ghsa-554w-wpv2-vw27.js index 2c9f0001b..7fd743f2c 100644 --- a/tests/security/ghsa-554w-wpv2-vw27.js +++ b/tests/security/ghsa-554w-wpv2-vw27.js @@ -17,6 +17,11 @@ describe('GHSA-554w-wpv2-vw27 Security Patch', function() { return asn1.toDer(obj).getBytes(); } + beforeEach(function() { + // check max depth is the default + assert.equal(asn1.maxDepth, 256); + }); + it('should throw a manageable error when default recursion depth is exceeded', function() { // create a payload just above the default limit (256) var DANGEROUS_DEPTH = 257; @@ -41,6 +46,21 @@ describe('GHSA-554w-wpv2-vw27 Security Patch', function() { }, /ASN.1 parsing error: Max depth exceeded./); }); + it('should still parse valid nested structures with new default limits', function() { + var oldMaxDepth = asn1.maxDepth; + asn1.maxDepth = 258; + + // create a payload just above the default limit (256) + var DANGEROUS_DEPTH = 257; + var der = createNestedDer(DANGEROUS_DEPTH); + var buf = util.createBuffer(der); + + // verify with new default depth + asn1.fromDer(buf, {strict: true}); + + asn1.maxDepth = oldMaxDepth; + }); + it('should still parse valid nested structures within default limits', function() { // verify we didn't break default depth functionality var SAFE_DEPTH = 20; From a05dd812ec2de46ece35a11ab4b46c9d283d1505 Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Thu, 6 Nov 2025 22:05:19 -0500 Subject: [PATCH 111/127] Fix for vulnerbaility CVE-2025-12816 --- lib/asn1.js | 83 +++++++++++++++++++++++++++++++++++++-------------- lib/pkcs12.js | 5 +++- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/lib/asn1.js b/lib/asn1.js index 5d9a66714..d7a5963ab 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -1177,7 +1177,6 @@ asn1.derToInteger = function(bytes) { */ asn1.validate = function(obj, v, capture, errors) { var rval = false; - // ensure tag class and type are the same if specified if((obj.tagClass === v.tagClass || typeof(v.tagClass) === 'undefined') && (obj.type === v.type || typeof(v.type) === 'undefined')) { @@ -1185,30 +1184,70 @@ asn1.validate = function(obj, v, capture, errors) { if(obj.constructed === v.constructed || typeof(v.constructed) === 'undefined') { rval = true; +// Replace existing inner loop with this code +if (v.value && forge.util.isArray(v.value)) { + var j = 0; + for (var i = 0; rval && i < v.value.length; ++i) { + var schemaItem = v.value[i]; + rval = schemaItem.optional || false; + + // current child in the object + var objChild = obj.value[j]; + + // if there is no child left to match + if (!objChild) { + // if optional, ok (rval already true), else fail below + if (!schemaItem.optional) { + rval = false; + if (errors) { + errors.push('[' + v.name + '] missing required element: expected tagClass "' + + schemaItem.tagClass + '", type "' + schemaItem.type + '"'); + } + } + continue; + } - // handle sub values - if(v.value && forge.util.isArray(v.value)) { - var j = 0; - for(var i = 0; rval && i < v.value.length; ++i) { - rval = v.value[i].optional || false; - if(obj.value[j]) { - rval = asn1.validate(obj.value[j], v.value[i], capture, errors); - if(rval) { - ++j; - } else if(v.value[i].optional) { - rval = true; - } - } - if(!rval && errors) { - errors.push( - '[' + v.name + '] ' + - 'Tag class "' + v.tagClass + '", type "' + - v.type + '" expected value length "' + - v.value.length + '", got "' + - obj.value.length + '"'); - } + // If schema explicitly specifies tagClass/type, do a quick structural check + // to avoid unnecessary recursion/side-effects when tags clearly don't match. + var schemaHasTag = (typeof schemaItem.tagClass !== 'undefined' && + typeof schemaItem.type !== 'undefined'); + + if (schemaHasTag && + (objChild.tagClass !== schemaItem.tagClass || objChild.type !== schemaItem.type)) { + // Tags do not match. + if (schemaItem.optional) { + // Skip this schema element (don't consume objChild; don't call recursive validate). + rval = true; + continue; + } else { + // Required schema item mismatched — fail. + rval = false; + if (errors) { + errors.push('[' + v.name + '] Tag mismatch: expected (' + + schemaItem.tagClass + ',' + schemaItem.type + ') got (' + + objChild.tagClass + ',' + objChild.type + ')'); } + break; } + } + + // Tags are compatible (or schema did not declare tags) — dive into recursive validate. + var childRval = asn1.validate(objChild, schemaItem, capture, errors); + if (childRval) { + ++j; // consume this child + rval = true; + } else if (schemaItem.optional) { + // validation failed but element is optional => skip schema item (don't consume child) + rval = true; + } else { + // required item failed + rval = false; + // errors should already be populated by recursive call; keep failing + break; + } + } +} + if(rval && capture) { if(v.capture) { diff --git a/lib/pkcs12.js b/lib/pkcs12.js index cd06c494a..22108e32e 100644 --- a/lib/pkcs12.js +++ b/lib/pkcs12.js @@ -314,7 +314,6 @@ function _getBagsByAttribute(safeContents, attrName, attrValue, bagType) { * @return PKCS#12 PFX object. */ p12.pkcs12FromAsn1 = function(obj, strict, password) { - // handle args if(typeof strict === 'string') { password = strict; strict = true; @@ -457,6 +456,7 @@ p12.pkcs12FromAsn1 = function(obj, strict, password) { macKeyBytes = 16; break; } + if(md === null) { throw new Error('PKCS#12 uses unsupported MAC algorithm: ' + macAlgorithm); } @@ -474,6 +474,9 @@ p12.pkcs12FromAsn1 = function(obj, strict, password) { if(macValue.getBytes() !== capture.macDigest) { throw new Error('PKCS#12 MAC could not be verified. Invalid password?'); } + } else if (Array.isArray(obj.value) && obj.value.length > 2) { + /* This is pfx data that should ahve mac and verify macDigest */ + throw new Error('Invalid PKCS#12: macData field present but MAC was not validated'); } _decodeAuthenticatedSafe(pfx, data.value, strict, password); From e9fdf348f149fa5e96a42fbacc9f13c3b7117253 Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Thu, 6 Nov 2025 22:16:59 -0500 Subject: [PATCH 112/127] Added security tests --- tests/security/cve-2025-12816.js | 77 ++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/security/cve-2025-12816.js diff --git a/tests/security/cve-2025-12816.js b/tests/security/cve-2025-12816.js new file mode 100644 index 000000000..bb9bb0c77 --- /dev/null +++ b/tests/security/cve-2025-12816.js @@ -0,0 +1,77 @@ +'use strict'; + +const assert = require('assert'); +const forge = require('../../lib/forge'); +const md = require('../../lib/md'); +const pkcs12 = require('../../lib/pkcs12'); +const asn1 = require('../../lib/asn1'); +const pki = require('../../lib/pki'); + +/** + * Build a minimal certificate-only PKCS#12 (PFX) with a MAC. + */ +function buildCertOnlyPfxWithMac(password) { + const keys = pki.rsa.generateKeyPair({ bits: 1024, e: 0x10001 }); + const cert = pki.createCertificate(); + cert.publicKey = keys.publicKey; + cert.serialNumber = '01'; + cert.validity.notBefore = new Date(); + cert.validity.notAfter = new Date(); + cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); + cert.setSubject([{ name: 'commonName', value: 'p12-demo' }]); + cert.setIssuer([{ name: 'commonName', value: 'p12-demo' }]); + cert.sign(keys.privateKey, md.sha256.create()); + + return pkcs12.toPkcs12Asn1( + null, + cert, + password, + { useMac: true, count: 2048, saltSize: 8 } + ); +} + +/** + * Replace macData node with arbitrary junk. + */ +function corruptMacData(pfxAsn1) { + const clone = asn1.fromDer(asn1.toDer(pfxAsn1).getBytes()); + // Replace 3rd element (macData) with garbage node + clone.value[2] = asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.OCTETSTRING, + false, + 'JUNK' + ); + return clone; +} + +describe('PKCS#12 MAC corruption security test', function () { + const correctPw = 'correct-horse-battery-staple'; + const wrongPw = 'wrong-password'; + let legit; + + before(function () { + legit = buildCertOnlyPfxWithMac(correctPw); + }); + + it('accepts valid PFX with correct password', function () { + assert.doesNotThrow(() => { + const obj = pkcs12.pkcs12FromAsn1(legit, true, correctPw); + assert(obj, 'pkcs12FromAsn1 should return an object'); + }); + }); + + it('rejects valid PFX with wrong password (MAC mismatch)', function () { + assert.throws(() => { + pkcs12.pkcs12FromAsn1(legit, true, wrongPw); + }, /MAC|password|verify/i); + }); + + it('rejects tampered PFX with corrupted macData', function () { + const tampered = corruptMacData(legit); + assert.throws(() => { + pkcs12.pkcs12FromAsn1(tampered, true, wrongPw); + }, /mac|digest|invalid|malformed/i); + }); +}); + From 7d3fd52cac8e0617f2a0c968aaa72b69d50779cd Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Thu, 6 Nov 2025 22:24:00 -0500 Subject: [PATCH 113/127] Update to CHANGELOG and version information --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cdda82e5..cd1750071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Forge ChangeLog =============== +## 1.3.3 2025-11-06 + +### Fixes +- Fix for vulnerability identified by CVE-2025-12816 PKCS#12 MAC verification bypass + due to missing macData enforcement and improper asn1.validate routine reported by Hunter Wodzenski. + ## 1.3.2 - 2025-11-xx ### Security diff --git a/package.json b/package.json index 459985a24..4f7ad0a33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.3.2-0", + "version": "1.3.3", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From 0c69b481810a2b35e0ead4f000ea22d126bc1cda Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 17 Nov 2025 18:17:26 -0500 Subject: [PATCH 114/127] Fixes for release. - `package.json`: - Reset back to pre-release. - `CHANGELOG.md`: - Set version to 1.3.2. - Formatting. --- CHANGELOG.md | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd1750071..ab451788e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ Forge ChangeLog =============== -## 1.3.3 2025-11-06 +## 1.3.2 - 2025-11-xx -### Fixes -- Fix for vulnerability identified by CVE-2025-12816 PKCS#12 MAC verification bypass - due to missing macData enforcement and improper asn1.validate routine reported by Hunter Wodzenski. +### Fixed +- Fix for vulnerability identified by CVE-2025-12816 PKCS#12 MAC verification + bypass due to missing macData enforcement and improper asn1.validate routine + reported by Hunter Wodzenski. ## 1.3.2 - 2025-11-xx diff --git a/package.json b/package.json index 4f7ad0a33..459985a24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.3.3", + "version": "1.3.2-0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { From faa130851bfbee6f16391ae1e67401ae6a6e1eef Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 17 Nov 2025 18:49:27 -0500 Subject: [PATCH 115/127] Run tests/security/ tests as part of unit tests. --- tests/security/index.js | 2 ++ tests/unit/index.js | 1 + 2 files changed, 3 insertions(+) create mode 100644 tests/security/index.js diff --git a/tests/security/index.js b/tests/security/index.js new file mode 100644 index 000000000..07e8c3424 --- /dev/null +++ b/tests/security/index.js @@ -0,0 +1,2 @@ +// tests related to security, vulnerability reports, etc +require('./cve-2025-12816.js'); diff --git a/tests/unit/index.js b/tests/unit/index.js index c881a4366..dfc441efc 100644 --- a/tests/unit/index.js +++ b/tests/unit/index.js @@ -23,3 +23,4 @@ require('./pkcs7'); require('./pkcs12'); require('./tls'); require('./ssh'); +require('../security'); From 1f9167b7e9c1671131e6b9d63409b3efc9ab67a1 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 17 Nov 2025 18:50:50 -0500 Subject: [PATCH 116/127] Fix style. - Use same ancient ES5 style as the rest of the codebase for consistency. - Minor formatting. --- tests/security/cve-2025-12816.js | 50 +++++++++++++++----------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/tests/security/cve-2025-12816.js b/tests/security/cve-2025-12816.js index bb9bb0c77..8408f78c5 100644 --- a/tests/security/cve-2025-12816.js +++ b/tests/security/cve-2025-12816.js @@ -1,32 +1,31 @@ 'use strict'; -const assert = require('assert'); -const forge = require('../../lib/forge'); -const md = require('../../lib/md'); -const pkcs12 = require('../../lib/pkcs12'); -const asn1 = require('../../lib/asn1'); -const pki = require('../../lib/pki'); +var assert = require('assert'); +var md = require('../../lib/md'); +var pkcs12 = require('../../lib/pkcs12'); +var asn1 = require('../../lib/asn1'); +var pki = require('../../lib/pki'); /** * Build a minimal certificate-only PKCS#12 (PFX) with a MAC. */ function buildCertOnlyPfxWithMac(password) { - const keys = pki.rsa.generateKeyPair({ bits: 1024, e: 0x10001 }); - const cert = pki.createCertificate(); + var keys = pki.rsa.generateKeyPair({bits: 1024, e: 0x10001}); + var cert = pki.createCertificate(); cert.publicKey = keys.publicKey; cert.serialNumber = '01'; cert.validity.notBefore = new Date(); cert.validity.notAfter = new Date(); cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); - cert.setSubject([{ name: 'commonName', value: 'p12-demo' }]); - cert.setIssuer([{ name: 'commonName', value: 'p12-demo' }]); + cert.setSubject([{name: 'commonName', value: 'p12-demo'}]); + cert.setIssuer([{name: 'commonName', value: 'p12-demo'}]); cert.sign(keys.privateKey, md.sha256.create()); return pkcs12.toPkcs12Asn1( null, cert, password, - { useMac: true, count: 2048, saltSize: 8 } + {useMac: true, count: 2048, saltSize: 8} ); } @@ -34,7 +33,7 @@ function buildCertOnlyPfxWithMac(password) { * Replace macData node with arbitrary junk. */ function corruptMacData(pfxAsn1) { - const clone = asn1.fromDer(asn1.toDer(pfxAsn1).getBytes()); + var clone = asn1.fromDer(asn1.toDer(pfxAsn1).getBytes()); // Replace 3rd element (macData) with garbage node clone.value[2] = asn1.create( asn1.Class.UNIVERSAL, @@ -45,33 +44,32 @@ function corruptMacData(pfxAsn1) { return clone; } -describe('PKCS#12 MAC corruption security test', function () { - const correctPw = 'correct-horse-battery-staple'; - const wrongPw = 'wrong-password'; - let legit; +describe('PKCS#12 MAC corruption', function() { + var correctPw = 'correct-horse-battery-staple'; + var wrongPw = 'wrong-password'; + var legit; - before(function () { + before(function() { legit = buildCertOnlyPfxWithMac(correctPw); }); - it('accepts valid PFX with correct password', function () { - assert.doesNotThrow(() => { - const obj = pkcs12.pkcs12FromAsn1(legit, true, correctPw); + it('should accept valid PFX with correct password', function() { + assert.doesNotThrow(function() { + var obj = pkcs12.pkcs12FromAsn1(legit, true, correctPw); assert(obj, 'pkcs12FromAsn1 should return an object'); }); }); - it('rejects valid PFX with wrong password (MAC mismatch)', function () { - assert.throws(() => { + it('should reject valid PFX with wrong password (MAC mismatch)', function() { + assert.throws(function() { pkcs12.pkcs12FromAsn1(legit, true, wrongPw); }, /MAC|password|verify/i); }); - it('rejects tampered PFX with corrupted macData', function () { - const tampered = corruptMacData(legit); - assert.throws(() => { + it('should reject tampered PFX with corrupted macData', function() { + var tampered = corruptMacData(legit); + assert.throws(function() { pkcs12.pkcs12FromAsn1(tampered, true, wrongPw); }, /mac|digest|invalid|malformed/i); }); }); - From 0b59631442f956250e35430100831d71fa1fc489 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 17 Nov 2025 19:04:32 -0500 Subject: [PATCH 117/127] Test for exact error messages. - Ensure tests are catching the expected errors. - Can adjust tests later if error messages change. --- tests/security/cve-2025-12816.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/security/cve-2025-12816.js b/tests/security/cve-2025-12816.js index 8408f78c5..e5ff465ce 100644 --- a/tests/security/cve-2025-12816.js +++ b/tests/security/cve-2025-12816.js @@ -63,13 +63,13 @@ describe('PKCS#12 MAC corruption', function() { it('should reject valid PFX with wrong password (MAC mismatch)', function() { assert.throws(function() { pkcs12.pkcs12FromAsn1(legit, true, wrongPw); - }, /MAC|password|verify/i); + }, /PKCS#12 MAC could not be verified. Invalid password?/); }); it('should reject tampered PFX with corrupted macData', function() { var tampered = corruptMacData(legit); assert.throws(function() { pkcs12.pkcs12FromAsn1(tampered, true, wrongPw); - }, /mac|digest|invalid|malformed/i); + }, /Invalid PKCS#12: macData field present but MAC was not validated/); }); }); From 6607445859637442cf586eaa7fa06e99a2a8ae0b Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 17 Nov 2025 19:13:33 -0500 Subject: [PATCH 118/127] Revert minor changes. - Minimizing security patch changes. --- lib/asn1.js | 2 +- lib/pkcs12.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/asn1.js b/lib/asn1.js index d7a5963ab..464786ed8 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -1177,6 +1177,7 @@ asn1.derToInteger = function(bytes) { */ asn1.validate = function(obj, v, capture, errors) { var rval = false; + // ensure tag class and type are the same if specified if((obj.tagClass === v.tagClass || typeof(v.tagClass) === 'undefined') && (obj.type === v.type || typeof(v.type) === 'undefined')) { @@ -1248,7 +1249,6 @@ if (v.value && forge.util.isArray(v.value)) { } } - if(rval && capture) { if(v.capture) { capture[v.capture] = obj.value; diff --git a/lib/pkcs12.js b/lib/pkcs12.js index 22108e32e..ed4b6cb61 100644 --- a/lib/pkcs12.js +++ b/lib/pkcs12.js @@ -314,6 +314,7 @@ function _getBagsByAttribute(safeContents, attrName, attrValue, bagType) { * @return PKCS#12 PFX object. */ p12.pkcs12FromAsn1 = function(obj, strict, password) { + // handle args if(typeof strict === 'string') { password = strict; strict = true; @@ -456,7 +457,6 @@ p12.pkcs12FromAsn1 = function(obj, strict, password) { macKeyBytes = 16; break; } - if(md === null) { throw new Error('PKCS#12 uses unsupported MAC algorithm: ' + macAlgorithm); } From afbf7d8e0812014da134caa5a064cf55d1f61847 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 17 Nov 2025 19:16:05 -0500 Subject: [PATCH 119/127] Align error message style. --- lib/pkcs12.js | 2 +- tests/security/cve-2025-12816.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pkcs12.js b/lib/pkcs12.js index ed4b6cb61..448196cee 100644 --- a/lib/pkcs12.js +++ b/lib/pkcs12.js @@ -476,7 +476,7 @@ p12.pkcs12FromAsn1 = function(obj, strict, password) { } } else if (Array.isArray(obj.value) && obj.value.length > 2) { /* This is pfx data that should ahve mac and verify macDigest */ - throw new Error('Invalid PKCS#12: macData field present but MAC was not validated'); + throw new Error('Invalid PKCS#12. macData field present but MAC was not validated.'); } _decodeAuthenticatedSafe(pfx, data.value, strict, password); diff --git a/tests/security/cve-2025-12816.js b/tests/security/cve-2025-12816.js index e5ff465ce..bf26f26bb 100644 --- a/tests/security/cve-2025-12816.js +++ b/tests/security/cve-2025-12816.js @@ -70,6 +70,6 @@ describe('PKCS#12 MAC corruption', function() { var tampered = corruptMacData(legit); assert.throws(function() { pkcs12.pkcs12FromAsn1(tampered, true, wrongPw); - }, /Invalid PKCS#12: macData field present but MAC was not validated/); + }, /Invalid PKCS#12. macData field present but MAC was not validated./); }); }); From db6954ba4b4440831a5112dea5d37ef68a28b878 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 17 Nov 2025 19:29:16 -0500 Subject: [PATCH 120/127] Fix style. Fix indentation, only use ascii, and align code style. --- lib/asn1.js | 120 +++++++++++++++++++++++++------------------------- lib/pkcs12.js | 2 +- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/lib/asn1.js b/lib/asn1.js index 464786ed8..5eb21cdf8 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -1185,69 +1185,69 @@ asn1.validate = function(obj, v, capture, errors) { if(obj.constructed === v.constructed || typeof(v.constructed) === 'undefined') { rval = true; -// Replace existing inner loop with this code -if (v.value && forge.util.isArray(v.value)) { - var j = 0; - for (var i = 0; rval && i < v.value.length; ++i) { - var schemaItem = v.value[i]; - rval = schemaItem.optional || false; - - // current child in the object - var objChild = obj.value[j]; - - // if there is no child left to match - if (!objChild) { - // if optional, ok (rval already true), else fail below - if (!schemaItem.optional) { - rval = false; - if (errors) { - errors.push('[' + v.name + '] missing required element: expected tagClass "' + - schemaItem.tagClass + '", type "' + schemaItem.type + '"'); - } - } - continue; - } + // Replace existing inner loop with this code + if(v.value && forge.util.isArray(v.value)) { + var j = 0; + for(var i = 0; rval && i < v.value.length; ++i) { + var schemaItem = v.value[i]; + rval = schemaItem.optional || false; + + // current child in the object + var objChild = obj.value[j]; + + // if there is no child left to match + if(!objChild) { + // if optional, ok (rval already true), else fail below + if(!schemaItem.optional) { + rval = false; + if(errors) { + errors.push('[' + v.name + '] missing required element: expected tagClass "' + + schemaItem.tagClass + '", type "' + schemaItem.type + '"'); + } + } + continue; + } - // If schema explicitly specifies tagClass/type, do a quick structural check - // to avoid unnecessary recursion/side-effects when tags clearly don't match. - var schemaHasTag = (typeof schemaItem.tagClass !== 'undefined' && - typeof schemaItem.type !== 'undefined'); - - if (schemaHasTag && - (objChild.tagClass !== schemaItem.tagClass || objChild.type !== schemaItem.type)) { - // Tags do not match. - if (schemaItem.optional) { - // Skip this schema element (don't consume objChild; don't call recursive validate). - rval = true; - continue; - } else { - // Required schema item mismatched — fail. - rval = false; - if (errors) { - errors.push('[' + v.name + '] Tag mismatch: expected (' + - schemaItem.tagClass + ',' + schemaItem.type + ') got (' + - objChild.tagClass + ',' + objChild.type + ')'); + // If schema explicitly specifies tagClass/type, do a quick structural check + // to avoid unnecessary recursion/side-effects when tags clearly don't match. + var schemaHasTag = (typeof schemaItem.tagClass !== 'undefined' && + typeof schemaItem.type !== 'undefined'); + + if(schemaHasTag && + (objChild.tagClass !== schemaItem.tagClass || objChild.type !== schemaItem.type)) { + // Tags do not match. + if(schemaItem.optional) { + // Skip this schema element (don't consume objChild; don't call recursive validate). + rval = true; + continue; + } else { + // Required schema item mismatched - fail. + rval = false; + if(errors) { + errors.push('[' + v.name + '] Tag mismatch: expected (' + + schemaItem.tagClass + ',' + schemaItem.type + ') got (' + + objChild.tagClass + ',' + objChild.type + ')'); + } + break; + } + } + + // Tags are compatible (or schema did not declare tags) - dive into recursive validate. + var childRval = asn1.validate(objChild, schemaItem, capture, errors); + if(childRval) { + ++j; // consume this child + rval = true; + } else if(schemaItem.optional) { + // validation failed but element is optional => skip schema item (don't consume child) + rval = true; + } else { + // required item failed + rval = false; + // errors should already be populated by recursive call; keep failing + break; + } } - break; } - } - - // Tags are compatible (or schema did not declare tags) — dive into recursive validate. - var childRval = asn1.validate(objChild, schemaItem, capture, errors); - if (childRval) { - ++j; // consume this child - rval = true; - } else if (schemaItem.optional) { - // validation failed but element is optional => skip schema item (don't consume child) - rval = true; - } else { - // required item failed - rval = false; - // errors should already be populated by recursive call; keep failing - break; - } - } -} if(rval && capture) { if(v.capture) { diff --git a/lib/pkcs12.js b/lib/pkcs12.js index 448196cee..f12d069c5 100644 --- a/lib/pkcs12.js +++ b/lib/pkcs12.js @@ -474,7 +474,7 @@ p12.pkcs12FromAsn1 = function(obj, strict, password) { if(macValue.getBytes() !== capture.macDigest) { throw new Error('PKCS#12 MAC could not be verified. Invalid password?'); } - } else if (Array.isArray(obj.value) && obj.value.length > 2) { + } else if(Array.isArray(obj.value) && obj.value.length > 2) { /* This is pfx data that should ahve mac and verify macDigest */ throw new Error('Invalid PKCS#12. macData field present but MAC was not validated.'); } From eb932d94fbd88655f46ac7a94a8e13e7ed8597f7 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 17 Nov 2025 19:30:14 -0500 Subject: [PATCH 121/127] Fix typo. --- lib/pkcs12.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pkcs12.js b/lib/pkcs12.js index f12d069c5..dee8b36ad 100644 --- a/lib/pkcs12.js +++ b/lib/pkcs12.js @@ -475,7 +475,7 @@ p12.pkcs12FromAsn1 = function(obj, strict, password) { throw new Error('PKCS#12 MAC could not be verified. Invalid password?'); } } else if(Array.isArray(obj.value) && obj.value.length > 2) { - /* This is pfx data that should ahve mac and verify macDigest */ + /* This is pfx data that should have mac and verify macDigest */ throw new Error('Invalid PKCS#12. macData field present but MAC was not validated.'); } From 4652de6ddd833392e52d99b37abbbda76817c0b7 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 18 Nov 2025 18:08:37 -0500 Subject: [PATCH 122/127] Cleanups. - Minor formatting. - Simplify boolean logic. - Try to improve error text normalization. --- lib/asn1.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/asn1.js b/lib/asn1.js index 5eb21cdf8..e3cb5007b 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -1185,12 +1185,13 @@ asn1.validate = function(obj, v, capture, errors) { if(obj.constructed === v.constructed || typeof(v.constructed) === 'undefined') { rval = true; - // Replace existing inner loop with this code + + // handle sub values if(v.value && forge.util.isArray(v.value)) { var j = 0; for(var i = 0; rval && i < v.value.length; ++i) { var schemaItem = v.value[i]; - rval = schemaItem.optional || false; + rval = !!schemaItem.optional; // current child in the object var objChild = obj.value[j]; @@ -1201,7 +1202,8 @@ asn1.validate = function(obj, v, capture, errors) { if(!schemaItem.optional) { rval = false; if(errors) { - errors.push('[' + v.name + '] missing required element: expected tagClass "' + + errors.push('[' + v.name + '] ' + + 'Missing required element. Expected tag class "' + schemaItem.tagClass + '", type "' + schemaItem.type + '"'); } } @@ -1224,8 +1226,9 @@ asn1.validate = function(obj, v, capture, errors) { // Required schema item mismatched - fail. rval = false; if(errors) { - errors.push('[' + v.name + '] Tag mismatch: expected (' + - schemaItem.tagClass + ',' + schemaItem.type + ') got (' + + errors.push('[' + v.name + '] ' + + 'Tag mismatch. Expected (' + + schemaItem.tagClass + ',' + schemaItem.type + '), got (' + objChild.tagClass + ',' + objChild.type + ')'); } break; @@ -1235,7 +1238,8 @@ asn1.validate = function(obj, v, capture, errors) { // Tags are compatible (or schema did not declare tags) - dive into recursive validate. var childRval = asn1.validate(objChild, schemaItem, capture, errors); if(childRval) { - ++j; // consume this child + // consume this child + ++j; rval = true; } else if(schemaItem.optional) { // validation failed but element is optional => skip schema item (don't consume child) @@ -1290,7 +1294,8 @@ asn1.validate = function(obj, v, capture, errors) { if(obj.type !== v.type) { errors.push( '[' + v.name + '] ' + - 'Expected type "' + v.type + '", got "' + obj.type + '"'); + 'Expected type "' + v.type + '", got "' + + obj.type + '"'); } } return rval; From a5ce91d03df4dcfc025b74a5b7f50389942d49c9 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 25 Nov 2025 01:57:43 -0500 Subject: [PATCH 123/127] Update changelog formatting. --- CHANGELOG.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab451788e..da4760919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,16 @@ Forge ChangeLog ## 1.3.2 - 2025-11-xx -### Fixed -- Fix for vulnerability identified by CVE-2025-12816 PKCS#12 MAC verification - bypass due to missing macData enforcement and improper asn1.validate routine - reported by Hunter Wodzenski. - -## 1.3.2 - 2025-11-xx - ### Security +- **HIGH**: ASN.1 Validator Desynchronization + - An Interpretation Conflict (CWE-436) vulnerability in node-forge versions + 1.3.1 and below enables remote, unauthenticated attackers to craft ASN.1 + structures to desynchronize schema validations, yielding a semantic + divergence that may bypass downstream cryptographic verifications and + security decisions. + - Reported by Hunter Wodzenski. + - CVE ID: [CVE-2025-12816](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-12816) + - GHSA ID: [GHSA-5gfm-wpxj-wjgq](https://github.com/digitalbazaar/forge/security/advisories/GHSA-5gfm-wpxj-wjgq) - **HIGH**: ASN.1 Unbounded Recursion - An Uncontrolled Recursion (CWE-674) vulnerability in node-forge versions 1.3.1 and below enables remote, unauthenticated attackers to craft deep @@ -29,6 +31,9 @@ Forge ChangeLog - GHSA ID: [GHSA-65ch-62r8-g69g](https://github.com/digitalbazaar/forge/security/advisories/GHSA-65ch-62r8-g69g) ### Fixed +- [asn1] Fix for vulnerability identified by CVE-2025-12816 PKCS#12 MAC + verification bypass due to missing macData enforcement and improper + asn1.validate routine. - [asn1] Add `fromDer()` max recursion depth check. - Add a `asn1.maxDepth` global configurable maximum depth of 256. - Add a `asn1.fromDer()` per-call `maxDepth` option. From d75e08d255559ae401d9368346cacefde306e6df Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 25 Nov 2025 12:29:50 -0500 Subject: [PATCH 124/127] Run new security test. --- tests/security/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/security/index.js b/tests/security/index.js index 07e8c3424..5ce5600a1 100644 --- a/tests/security/index.js +++ b/tests/security/index.js @@ -1,2 +1,3 @@ // tests related to security, vulnerability reports, etc require('./cve-2025-12816.js'); +require('./ghsa-554w-wpv2-vw27.js'); From 0032dd0be8b6fb1b1092ef754d1dde91c10a95ad Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 25 Nov 2025 13:38:44 -0500 Subject: [PATCH 125/127] Fix typos. --- CHANGELOG.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da4760919..37b67b74e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,9 +50,9 @@ Forge ChangeLog ## 1.3.1 - 2022-03-29 -### Fixes +### Fixed - RFC 3447 and RFC 8017 allow for optional `DigestAlgorithm` `NULL` parameters - for `sha*` algorithms and require `NULL` paramters for `md2` and `md5` + for `sha*` algorithms and require `NULL` parameters for `md2` and `md5` algorithms. ## 1.3.0 - 2022-03-17 @@ -164,11 +164,11 @@ Forge ChangeLog 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. + Standard](https://url.spec.whatwg.org/). `URL` is supported by modern + browsers and modern Node.js. This change is needed to address URL parsing + security issues. If `forge.util.parseUrl()` is used directly or through + `forge.xhr` or `forge.http` APIs, and support is needed for environments + without `URL` support, then a polyfill must be used. - **BREAKING**: Remove `forge.task` API. This API was never used, documented, or advertised by the maintainers. If anyone was using this API and wishes to continue development it in other project, please let the maintainers know. From 25982441171dc9815c87d3d886c5c8a1d092b334 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 25 Nov 2025 14:23:34 -0500 Subject: [PATCH 126/127] Update changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37b67b74e..4f91e618e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Forge ChangeLog =============== -## 1.3.2 - 2025-11-xx +## 1.3.2 - 2025-11-25 ### Security - **HIGH**: ASN.1 Validator Desynchronization From 235ad3e70e4fdfdca4fdeb662dfba6588e2c38bd Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 25 Nov 2025 14:23:34 -0500 Subject: [PATCH 127/127] Release 1.3.2. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 459985a24..2a9d83b2f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "1.3.2-0", + "version": "1.3.2", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": {