diff --git a/headers/entryHeader.js b/headers/entryHeader.js index 572b9a7..051d389 100644 --- a/headers/entryHeader.js +++ b/headers/entryHeader.js @@ -83,7 +83,9 @@ module.exports = function () { set time(val) { setTime(val); }, - + get timeHighByte() { + return (_time >>> 8) & 0xff; + }, get crc() { return _crc; }, diff --git a/methods/zipcrypto.js b/methods/zipcrypto.js index 701b5ce..79768f4 100644 --- a/methods/zipcrypto.js +++ b/methods/zipcrypto.js @@ -118,8 +118,12 @@ function decrypt(/*Buffer*/ data, /*Object*/ header, /*String, Buffer*/ pwd) { // 2. decrypt salt what is always 12 bytes and is a part of file content const salt = decrypter(data.slice(0, 12)); - // 3. does password meet expectations - if (salt[11] !== header.crc >>> 24) { + // if bit 3 (0x08) of the general-purpose flags field is set, check salt[11] with the high byte of the header time + // 2 byte data block (as per Info-Zip spec), otherwise check with the high byte of the header entry + const verifyByte = ((header.flags & 0x8) === 0x8) ? header.timeHighByte : header.crc >>> 24; + + //3. does password meet expectations + if (salt[11] !== verifyByte) { throw "ADM-ZIP: Wrong Password"; } diff --git a/test/assets/issue-471-infozip-encrypted.zip b/test/assets/issue-471-infozip-encrypted.zip new file mode 100644 index 0000000..7cbbf3e Binary files /dev/null and b/test/assets/issue-471-infozip-encrypted.zip differ diff --git a/test/issue_471/infozip-password.test.js b/test/issue_471/infozip-password.test.js new file mode 100644 index 0000000..38d536f --- /dev/null +++ b/test/issue_471/infozip-password.test.js @@ -0,0 +1,35 @@ +"use strict"; + +// Tests for github issue 471: https://github.com/cthackers/adm-zip/issues/471 + +const assert = require("assert"); +const path = require("path"); +const Zip = require("../../adm-zip"); + +describe("decryption with info-zip spec password check", () => { + + + // test decryption with both password types + it("test decrypted data with password", () => { + // the issue-471-infozip-encrypted.zip file has been generated with Info-Zip Zip 2.32, but the Info-Zip + // standard is used by other zip generators as well. + const infoZip = new Zip(path.join(__dirname, "../assets/issue-471-infozip-encrypted.zip")); + const entries = infoZip.getEntries(); + assert(entries.length === 1, "Good: Test archive contains exactly 1 file"); + + const testFile = entries.filter(function (entry) { + return entry.entryName === "dummy.txt"; + }); + assert(testFile.length === 1, "Good: dummy.txt file exists as archive entry"); + + const readData = entries[0].getData('secret'); + assert(readData.toString('utf8').startsWith('How much wood could a woodchuck chuck'), "Good: buffer matches expectations"); + + // assert that the following call throws an exception + assert.throws(() => { + const readDataBad = entries[0].getData('badpassword'); + }, "Good: error thrown for bad password"); + + }); +}); + diff --git a/test/methods/zipcrypto.test.js b/test/methods/zipcrypto.test.js index 4936a50..de63476 100644 --- a/test/methods/zipcrypto.test.js +++ b/test/methods/zipcrypto.test.js @@ -15,6 +15,9 @@ describe("method - zipcrypto decrypt", () => { md5: "wYHjota6dQNazueWO9/uDg==", pwdok: "secret", pwdbad: "Secret", + flagsencrypted: 0x01, + flagsinfozipencrypted: 0x09, + timeHighByte: 0xD8, // result result: Buffer.from("test", "ascii") }; @@ -40,22 +43,33 @@ describe("method - zipcrypto decrypt", () => { // is error thrown if invalid password was provided it("should throw if invalid password is provided", () => { expect(function badpassword() { - decrypt(source.data, { crc: source.crc }, source.pwdbad); + decrypt(source.data, { crc: source.crc, flags: source.flagsencrypted }, source.pwdbad); }).to.throw(); expect(function okpassword() { - decrypt(source.data, { crc: source.crc }, source.pwdok); + decrypt(source.data, { crc: source.crc, flags: source.flagsencrypted }, source.pwdok); + }).to.not.throw(); + }); + + // is error thrown if invalid password was provided + it("should throw if invalid password is provided for Info-Zip bit 3 flag", () => { + expect(function badpassword() { + decrypt(source.data, { crc: source.crc, flags: source.flagsinfozipencrypted, timeHighByte: source.timeHighByte }, source.pwdbad); + }).to.throw(); + + expect(function okpassword() { + decrypt(source.data, { crc: source.crc, flags: source.flagsinfozipencrypted, timeHighByte: source.timeHighByte }, source.pwdok); }).to.not.throw(); }); // test decryption with both password types it("test decrypted data with password", () => { // test password, string - const result1 = decrypt(source.data, { crc: source.crc }, source.pwdok); + const result1 = decrypt(source.data, { crc: source.crc, flags: source.flagsencrypted }, source.pwdok); expect(result1.compare(source.result)).to.equal(0); // test password, buffer - const result2 = decrypt(source.data, { crc: source.crc }, Buffer.from(source.pwdok, "ascii")); + const result2 = decrypt(source.data, { crc: source.crc, flags: source.flagsencrypted }, Buffer.from(source.pwdok, "ascii")); expect(result2.compare(source.result)).to.equal(0); }); });