diff --git a/lib/utils/pkcs7-padding.js b/lib/utils/pkcs7-padding.js --- a/lib/utils/pkcs7-padding.js +++ b/lib/utils/pkcs7-padding.js @@ -16,6 +16,56 @@ import invariant from 'invariant'; +export type PaddingConfiguration = { + +blockSizeBytes: number, + +superblockSizeBlocks: number, +}; + +/** + * The padding configuration for 10KB superblocks. + * The block size is 250 bytes, and the superblock size is 40 blocks. + */ +const PKCS7_10KB: PaddingConfiguration = { + blockSizeBytes: 250, + superblockSizeBlocks: 40, +}; + +/** + * Pads the input using the extended PKCS#7 padding (superblock padding). + * The input is first padded using the standard PKCS#7 padding, and then + * padded to the nearest multiple of superblocks (blocks of blocks). + * + * @param {Uint8Array} data - The input to be padded. + * @param {PaddingConfiguration} paddingConfiguration - The padding + * configuration. Defaults to multiple of 10KB. See {@link PKCS7_10KB}. + * @returns {Uint8Array} The padded input. + */ +function pad( + data: Uint8Array, + { blockSizeBytes, superblockSizeBlocks }: PaddingConfiguration = PKCS7_10KB, +): Uint8Array { + const pkcs7Padded = pkcs7pad(data, blockSizeBytes); + return superblockPad(pkcs7Padded, blockSizeBytes, superblockSizeBlocks); +} + +/** + * Unpads the input using the extended PKCS#7 padding (superblock padding). + * The input is first unpadded on the block basis, and then unpadded using + * the standard PKCS#7 padding. + * + * @param {Uint8Array} data - The input to be unpadded. + * @param {PaddingConfiguration} paddingConfiguration - The padding + * configuration. Defaults to multiple of 10KB. See {@link PKCS7_10KB}. + * @returns {Uint8Array} The unpadded input. + */ +function unpad( + data: Uint8Array, + { blockSizeBytes }: PaddingConfiguration = PKCS7_10KB, +): Uint8Array { + const blockUnpadded = superblockUnpad(data, blockSizeBytes); + return pkcs7unpad(blockUnpadded); +} + /** * PKCS#7 padding function for `Uint8Array` data. * @@ -119,4 +169,12 @@ return unpaddedData; } -export { pkcs7pad, pkcs7unpad, superblockPad, superblockUnpad }; +export { PKCS7_10KB, pad, unpad }; + +// exported for testing purposes only +export const testing = { + pkcs7pad, + pkcs7unpad, + superblockPad, + superblockUnpad, +}; diff --git a/lib/utils/pkcs7-padding.test.js b/lib/utils/pkcs7-padding.test.js --- a/lib/utils/pkcs7-padding.test.js +++ b/lib/utils/pkcs7-padding.test.js @@ -1,11 +1,9 @@ // @flow -import { - pkcs7pad, - pkcs7unpad, - superblockPad, - superblockUnpad, -} from './pkcs7-padding.js'; +import type { PaddingConfiguration } from './pkcs7-padding'; +import { pad, unpad, testing } from './pkcs7-padding.js'; + +const { pkcs7pad, pkcs7unpad, superblockPad, superblockUnpad } = testing; describe('PKCS#7 Padding', () => { it('should pad data to a multiple of blockSize bytes', () => { @@ -155,6 +153,66 @@ }); }); +describe('padding integration tests (pad and unpad)', () => { + it('should pad data to a multiple of superblockSize blocks', () => { + const config: PaddingConfiguration = { + blockSizeBytes: 16, + superblockSizeBlocks: 4, + }; + + // 20 bytes of data - expected 4 blocks total (1 superblock): + // - block 1 (16 bytes) = 16 bytes of data + // - block 2 (16 bytes) = 4 bytes of data (remaining 12 bytes of padding) + // - block 3 (16 bytes) = full padding (filled with 2s) + // - block 4 (16 bytes) = full padding (filled with 2s) + const dataLengthBytes = 20; + const expectedPkcs7Padding = 12; + const expectedBlockPadding = 2; + const expectedPaddedLength = 4 * 16; + + const data = generateRandomData(dataLengthBytes); + const padded = pad(data, config); + + expect(padded.length % expectedPaddedLength).toBe(0); + expect(padded[padded.length - 1]).toBe(expectedBlockPadding); + expect(padded[2 * 16 - 1]).toBe(expectedPkcs7Padding); + }); + + it('pad should add a full superblock if pkcs7-padded input is a multiple of superblockSize blocks', () => { + const config: PaddingConfiguration = { + blockSizeBytes: 16, + superblockSizeBlocks: 4, + }; + + // 5 bytes less so pkcs7 padding is 5 bytes and pads equally to superblock + // size + const pkcs7paddingLength = 5; + const dataLengthBytes = 4 * 16 - pkcs7paddingLength; + + const expectedPaddedLength = 8 * 16; + const expectedBlockPadding = 4; + + const data = generateRandomData(dataLengthBytes); + const padded = pad(data, config); + + expect(padded.length % expectedPaddedLength).toBe(0); + expect(padded[padded.length - 1]).toBe(expectedBlockPadding); + }); + + it('pad and unpad should be inverses', () => { + const config: PaddingConfiguration = { + blockSizeBytes: 16, + superblockSizeBlocks: 4, + }; + + const data = generateRandomData(100); + const padded = pad(data, config); + const unpadded = unpad(padded, config); + expect(unpadded.length).toBe(data.length); + expect(unpadded).toEqual(data); + }); +}); + function generateRandomData(length: number): Uint8Array { const data = new Uint8Array(length); for (let i = 0; i < length; i++) {