From e1cddb33fdc94a1b4e210d8c22e1d3ffc6a6ad53 Mon Sep 17 00:00:00 2001 From: Luke Malcolm Date: Tue, 5 Mar 2024 23:01:08 +1300 Subject: [PATCH] Add support for Info-Zip password check spec for ZipCrypto. (Uses high byte of header modified time, rather than crc). Updates current tests to handle. --- headers/entryHeader.js | 4 +++- methods/zipcrypto.js | 8 ++++++-- test/methods/zipcrypto.test.js | 22 ++++++++++++++++++---- 3 files changed, 27 insertions(+), 7 deletions(-) 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/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); }); });