diff --git a/src/config/config.js b/src/config/config.js index 78bb1f96..131205d3 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -40,6 +40,7 @@ export default { aead_protect: false, // use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption integrity_protect: true, // use integrity protection for symmetric encryption ignore_mdc_error: false, // fail on decrypt if message is not integrity protected + checksum_required: false, // do not throw error when armor is missing a checksum rsa_blinding: true, use_native: true, // use native node.js crypto and Web Crypto apis (if available) zero_copy: false, // use transferable objects between the Web Worker and main thread diff --git a/src/encoding/armor.js b/src/encoding/armor.js index 9dcbe891..bc7abea4 100644 --- a/src/encoding/armor.js +++ b/src/encoding/armor.js @@ -191,8 +191,7 @@ function createcrc24(input) { /** * Splits a message into two parts, the headers and the body. This is an internal function * @param {String} text OpenPGP armored message part - * @returns {(Boolean|Object)} Either false in case of an error - * or an object with attribute "headers" containing the headers and + * @returns {Object} An object with attribute "headers" containing the headers * and an attribute "body" containing the body. */ function splitHeaders(text) { @@ -234,19 +233,19 @@ function verifyHeaders(headers) { /** * Splits a message into two parts, the body and the checksum. This is an internal function * @param {String} text OpenPGP armored message part - * @returns {(Boolean|Object)} Either false in case of an error - * or an object with attribute "body" containing the body + * @returns {Object} An object with attribute "body" containing the body * and an attribute "checksum" containing the checksum. */ function splitChecksum(text) { + text = text.trim(); var body = text; var checksum = ""; var lastEquals = text.lastIndexOf("="); - if (lastEquals >= 0) { + if (lastEquals >= 0 && lastEquals !== text.length - 1) { // '=' as the last char means no checksum body = text.slice(0, lastEquals); - checksum = text.slice(lastEquals + 1); + checksum = text.slice(lastEquals + 1).substr(0, 4); } return { body: body, checksum: checksum }; @@ -268,6 +267,7 @@ function dearmor(text) { var type = getType(text); + text = text.trim() + "\n"; var splittext = text.split(reSplit); // IE has a bug in split with a re. If the pattern matches the beginning of the @@ -309,12 +309,9 @@ function dearmor(text) { checksum = sig_sum.checksum; } - checksum = checksum.substr(0, 4); - - if (!verifyCheckSum(result.data, checksum)) { - throw new Error("Ascii armor integrity check on message failed: '" + - checksum + - "' should be '" + + if (!verifyCheckSum(result.data, checksum) && (checksum || config.checksum_required)) { + // will NOT throw error if checksum is empty AND checksum is not required (GPG compatibility) + throw new Error("Ascii armor integrity check on message failed: '" + checksum + "' should be '" + getCheckSum(result.data) + "'"); } diff --git a/test/general/armor.js b/test/general/armor.js index f5ceb2e3..073c35a5 100644 --- a/test/general/armor.js +++ b/test/general/armor.js @@ -131,7 +131,7 @@ describe("ASCII armor", function() { expect(msg).to.throw(Error, /Unknown ASCII armor type/); }); - it('Armor checksum validation', function () { + it('Armor checksum validation - mismatch', function () { var privKey = ['-----BEGIN PGP PRIVATE KEY BLOCK-----', 'Version: OpenPGP.js v0.3.0', @@ -152,9 +152,140 @@ describe("ASCII armor", function() { '=wJN@', '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); - var result = openpgp.key.readArmored(privKey); - expect(result.err).to.exist; - expect(result.err[0].message).to.match(/Ascii armor integrity check on message failed/); + // try with default config + var result_1 = openpgp.key.readArmored(privKey); + expect(result_1.err).to.exist; + expect(result_1.err[0].message).to.match(/Ascii armor integrity check on message failed/); + + // try opposite config + openpgp.config.checksum_required = !openpgp.config.checksum_required; + var result_2 = openpgp.key.readArmored(privKey); + expect(result_2.err).to.exist; + expect(result_2.err[0].message).to.match(/Ascii armor integrity check on message failed/); + + // back to default + openpgp.config.checksum_required = !openpgp.config.checksum_required; + }); + + it('Armor checksum validation - valid', function () { + var privKey = + ['-----BEGIN PGP PRIVATE KEY BLOCK-----', + 'Version: OpenPGP.js v0.3.0', + 'Comment: http://openpgpjs.org', + '', + 'xbYEUubX7gEBANDWhzoP+Tr/IyRSv++vl5jBesQIPTYGQBdzF4YDnGEBABEB', + 'AAH+CQMIfzdw4/PKNl5gVXdtfDFdSIN8yJT2rbeg3+SsWexXZNNdRaONWaiB', + 'Z5cG9Q6+BoXKsEshIdcYOgwsAgRxlPpRA34Vvmg2QBk7PhdrkbK7aqENsJ1w', + 'dIlLD6p9GmLE20yVff58/fMiUtPRgsD83SpKTAX6EM1ulpkuQQNjmrVc5qc8', + '7AMdF80JdW5kZWZpbmVkwj8EEAEIABMFAlLm1+4JEBD8MASZrpALAhsDAAAs', + 'QgD8CUrwv7Hrp/INR0/UvAvzS52VztREQwQWTJMrgTNHBGjHtgRS5tfuAQEA', + 'nys9SaSgR+l6iZc/M8hGIUmbuahE2/+mtw+/l0RO+WcAEQEAAf4JAwjr39Yi', + 'FzjxImDN1IoYVsonA9M+BtIIJHafuQUHjyEr1paJJK5xS6KlyGgpMTXTD6y/', + 'qxS3ZSPPzHGRrs2CmkVEiPmurn9Ed05tb0y9OnJkWtuh3z9VVq9d8zHzuENa', + 'bUfli+P/v+dRaZ+1rSOxUFbFYbFB5XK/A9b/OPFrv+mb4KrtLxugwj8EGAEI', + 'ABMFAlLm1+4JEBD8MASZrpALAhsMAAC3IgD8DnLGbMnpLtrX72RCkPW1ffLq', + '71vlXMJNXvoCeuejiRw=', + '=wJNM', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); + + // try with default config + var result_1 = openpgp.key.readArmored(privKey); + expect(result_1.err).to.not.exist; + + // try opposite config + openpgp.config.checksum_required = !openpgp.config.checksum_required; + var result_2 = openpgp.key.readArmored(privKey); + expect(result_2.err).to.not.exist; + + // back to default + openpgp.config.checksum_required = !openpgp.config.checksum_required; + }); + + it('Armor checksum validation - missing', function () { + var privKeyNoCheckSum = + ['-----BEGIN PGP PRIVATE KEY BLOCK-----', + 'Version: OpenPGP.js v0.3.0', + 'Comment: http://openpgpjs.org', + '', + 'xbYEUubX7gEBANDWhzoP+Tr/IyRSv++vl5jBesQIPTYGQBdzF4YDnGEBABEB', + 'AAH+CQMIfzdw4/PKNl5gVXdtfDFdSIN8yJT2rbeg3+SsWexXZNNdRaONWaiB', + 'Z5cG9Q6+BoXKsEshIdcYOgwsAgRxlPpRA34Vvmg2QBk7PhdrkbK7aqENsJ1w', + 'dIlLD6p9GmLE20yVff58/fMiUtPRgsD83SpKTAX6EM1ulpkuQQNjmrVc5qc8', + '7AMdF80JdW5kZWZpbmVkwj8EEAEIABMFAlLm1+4JEBD8MASZrpALAhsDAAAs', + 'QgD8CUrwv7Hrp/INR0/UvAvzS52VztREQwQWTJMrgTNHBGjHtgRS5tfuAQEA', + 'nys9SaSgR+l6iZc/M8hGIUmbuahE2/+mtw+/l0RO+WcAEQEAAf4JAwjr39Yi', + 'FzjxImDN1IoYVsonA9M+BtIIJHafuQUHjyEr1paJJK5xS6KlyGgpMTXTD6y/', + 'qxS3ZSPPzHGRrs2CmkVEiPmurn9Ed05tb0y9OnJkWtuh3z9VVq9d8zHzuENa', + 'bUfli+P/v+dRaZ+1rSOxUFbFYbFB5XK/A9b/OPFrv+mb4KrtLxugwj8EGAEI', + 'ABMFAlLm1+4JEBD8MASZrpALAhsMAAC3IgD8DnLGbMnpLtrX72RCkPW1ffLq', + '71vlXMJNXvoCeuejiRw=', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); + + // try with default config + var result_1 = openpgp.key.readArmored(privKeyNoCheckSum); + if(openpgp.config.checksum_required) { + expect(result_1.err).to.exist; + expect(result_1.err[0].message).to.match(/Ascii armor integrity check on message failed/); + } else { + expect(result_1.err).to.not.exist; + } + + // try opposite config + openpgp.config.checksum_required = !openpgp.config.checksum_required; + var result_2 = openpgp.key.readArmored(privKeyNoCheckSum); + if(openpgp.config.checksum_required) { + expect(result_2.err).to.exist; + expect(result_2.err[0].message).to.match(/Ascii armor integrity check on message failed/); + } else { + expect(result_2.err).to.not.exist; + } + + // back to default + openpgp.config.checksum_required = !openpgp.config.checksum_required; + }); + + it('Armor checksum validation - missing - trailing newline', function () { + var privKeyNoCheckSumWithTrailingNewline = + ['-----BEGIN PGP PRIVATE KEY BLOCK-----', + 'Version: OpenPGP.js v0.3.0', + 'Comment: http://openpgpjs.org', + '', + 'xbYEUubX7gEBANDWhzoP+Tr/IyRSv++vl5jBesQIPTYGQBdzF4YDnGEBABEB', + 'AAH+CQMIfzdw4/PKNl5gVXdtfDFdSIN8yJT2rbeg3+SsWexXZNNdRaONWaiB', + 'Z5cG9Q6+BoXKsEshIdcYOgwsAgRxlPpRA34Vvmg2QBk7PhdrkbK7aqENsJ1w', + 'dIlLD6p9GmLE20yVff58/fMiUtPRgsD83SpKTAX6EM1ulpkuQQNjmrVc5qc8', + '7AMdF80JdW5kZWZpbmVkwj8EEAEIABMFAlLm1+4JEBD8MASZrpALAhsDAAAs', + 'QgD8CUrwv7Hrp/INR0/UvAvzS52VztREQwQWTJMrgTNHBGjHtgRS5tfuAQEA', + 'nys9SaSgR+l6iZc/M8hGIUmbuahE2/+mtw+/l0RO+WcAEQEAAf4JAwjr39Yi', + 'FzjxImDN1IoYVsonA9M+BtIIJHafuQUHjyEr1paJJK5xS6KlyGgpMTXTD6y/', + 'qxS3ZSPPzHGRrs2CmkVEiPmurn9Ed05tb0y9OnJkWtuh3z9VVq9d8zHzuENa', + 'bUfli+P/v+dRaZ+1rSOxUFbFYbFB5XK/A9b/OPFrv+mb4KrtLxugwj8EGAEI', + 'ABMFAlLm1+4JEBD8MASZrpALAhsMAAC3IgD8DnLGbMnpLtrX72RCkPW1ffLq', + '71vlXMJNXvoCeuejiRw=', + '-----END PGP PRIVATE KEY BLOCK-----', + ''].join('\n'); + + // try with default config + var result_1 = openpgp.key.readArmored(privKeyNoCheckSumWithTrailingNewline); + if(openpgp.config.checksum_required) { + expect(result_1.err).to.exist; + expect(result_1.err[0].message).to.match(/Ascii armor integrity check on message failed/); + } else { + expect(result_1.err).to.not.exist; + } + + // try opposite config + openpgp.config.checksum_required = !openpgp.config.checksum_required; + var result_2 = openpgp.key.readArmored(privKeyNoCheckSumWithTrailingNewline); + if(openpgp.config.checksum_required) { + expect(result_2.err).to.exist; + expect(result_2.err[0].message).to.match(/Ascii armor integrity check on message failed/); + } else { + expect(result_2.err).to.not.exist; + } + + // back to default + openpgp.config.checksum_required = !openpgp.config.checksum_required; }); it('Accept header with trailing whitespace', function () {