131 lines
3.8 KiB
TypeScript
131 lines
3.8 KiB
TypeScript
import {afterEach, describe, expect, it, jest} from '@jest/globals';
|
|||
|
|
import {createHash} from 'crypto';
|
||
|
|
import fs from 'fs';
|
||
|
|
import os from 'os';
|
||
|
|
import path from 'path';
|
||
|
|
|
||
|
|
import {calculateChecksum, verifyChecksum} from '../src/checksum.js';
|
||
|
|
import type {ChecksumMetadata} from '../src/distributions/base-models.js';
|
||
|
|
|
||
|
|
const temporaryPaths: string[] = [];
|
||
|
|
|
||
|
|
async function temporaryFile(contents: string): Promise<string> {
|
||
|
|
const directory = await fs.promises.mkdtemp(
|
||
|
|
path.join(os.tmpdir(), 'setup-java-checksum-')
|
||
|
|
);
|
||
|
|
const file = path.join(directory, 'archive');
|
||
|
|
await fs.promises.writeFile(file, contents);
|
||
|
|
temporaryPaths.push(directory);
|
||
|
|
return file;
|
||
|
|
}
|
||
|
|
|
||
|
|
afterEach(async () => {
|
||
|
|
await Promise.all(
|
||
|
|
temporaryPaths
|
||
|
|
.splice(0)
|
||
|
|
.map(item => fs.promises.rm(item, {recursive: true, force: true}))
|
||
|
|
);
|
||
|
|
jest.restoreAllMocks();
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('verifyChecksum', () => {
|
||
|
|
it.each(['sha256', 'sha512'] as const)(
|
||
|
|
'verifies a matching %s digest',
|
||
|
|
async algorithm => {
|
||
|
|
const contents = `jdk archive for ${algorithm}`;
|
||
|
|
const file = await temporaryFile(contents);
|
||
|
|
const value = createHash(algorithm).update(contents).digest('hex');
|
||
|
|
|
||
|
|
await expect(
|
||
|
|
verifyChecksum(
|
||
|
|
file,
|
||
|
|
{algorithm, value: value.toUpperCase()},
|
||
|
|
{distribution: 'Test', version: '21.0.1'}
|
||
|
|
)
|
||
|
|
).resolves.toBeUndefined();
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
it('reports mismatch context and both digests', async () => {
|
||
|
|
const file = await temporaryFile('corrupt archive');
|
||
|
|
const expected = 'a'.repeat(64);
|
||
|
|
const actual = await calculateChecksum(file, 'sha256');
|
||
|
|
|
||
|
|
await expect(
|
||
|
|
verifyChecksum(
|
||
|
|
file,
|
||
|
|
{algorithm: 'sha256', value: expected},
|
||
|
|
{distribution: 'Corretto', version: '21.0.8'}
|
||
|
|
)
|
||
|
|
).rejects.toThrow(
|
||
|
|
`Checksum verification failed for Corretto version 21.0.8: sha256 expected ${expected}, actual ${actual}.`
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects malformed digest metadata before reading the file', async () => {
|
||
|
|
await expect(
|
||
|
|
verifyChecksum(
|
||
|
|
'/missing/archive',
|
||
|
|
{algorithm: 'sha512', value: 'not-a-digest'},
|
||
|
|
{distribution: 'Test', version: '17'}
|
||
|
|
)
|
||
|
|
).rejects.toThrow(
|
||
|
|
'Malformed sha512 checksum metadata: expected a 128-character hexadecimal digest.'
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it.each([undefined, null, 123])(
|
||
|
|
'reports a malformed digest when the value is %p',
|
||
|
|
async value => {
|
||
|
|
const checksum = {
|
||
|
|
algorithm: 'sha256',
|
||
|
|
value
|
||
|
|
} as unknown as ChecksumMetadata;
|
||
|
|
|
||
|
|
await expect(
|
||
|
|
verifyChecksum('/missing/archive', checksum, {
|
||
|
|
distribution: 'Test',
|
||
|
|
version: '17'
|
||
|
|
})
|
||
|
|
).rejects.toThrow(
|
||
|
|
'Malformed sha256 checksum metadata: expected a 64-character hexadecimal digest.'
|
||
|
|
);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
it('rejects unsupported algorithms without leaking source query parameters', async () => {
|
||
|
|
const checksum = {
|
||
|
|
algorithm: 'md5',
|
||
|
|
value: 'a'.repeat(32),
|
||
|
|
source: 'https://vendor.example/checksum.txt?token=secret-value#private'
|
||
|
|
} as unknown as ChecksumMetadata;
|
||
|
|
|
||
|
|
let message = '';
|
||
|
|
try {
|
||
|
|
await verifyChecksum('/missing/archive', checksum, {
|
||
|
|
distribution: 'Test',
|
||
|
|
version: '17'
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
message = (error as Error).message;
|
||
|
|
}
|
||
|
|
|
||
|
|
expect(message).toContain(
|
||
|
|
"Unsupported checksum algorithm 'md5' from https://vendor.example/checksum.txt"
|
||
|
|
);
|
||
|
|
expect(message).not.toContain('secret-value');
|
||
|
|
expect(message).not.toContain('token=');
|
||
|
|
expect(message).not.toContain('#private');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('surfaces file read errors', async () => {
|
||
|
|
await expect(
|
||
|
|
verifyChecksum(
|
||
|
|
'/missing/archive',
|
||
|
|
{algorithm: 'sha256', value: 'a'.repeat(64)},
|
||
|
|
{distribution: 'Test', version: '17'}
|
||
|
|
)
|
||
|
|
).rejects.toMatchObject({code: 'ENOENT'});
|
||
|
|
});
|
||
|
|
});
|