Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/e2e-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,25 @@ jobs:
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
shell: bash

setup-java-checksum-verification:
name: Corretto checksum verification - ubuntu-latest
runs-on: ubuntu-latest
steps:
- *checkout_step
- name: setup-java with forced download
uses: ./
id: setup-java
with:
java-version: '21'
distribution: corretto
force-download: true
- name: Verify Java
env:
JAVA_VERSION: '21'
JAVA_PATH: ${{ steps.setup-java.outputs.path }}
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
shell: bash

setup-java-alpine-linux:
name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-${{ contains(matrix.os, 'macos') && !contains(matrix.os, 'intel') && 'arm64' || 'x64' }}) - alpine-linux - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ For more details, see the full release notes on the [releases page](https://git

- `show-download-progress`: Set to `true` to keep Maven artifact download and transfer progress in build logs. Default value: `false`. By default, the action adds `-ntp` (`--no-transfer-progress`) to `MAVEN_ARGS`. This input has no effect on non-Maven builds. See [Maven transfer progress](docs/advanced-usage.md#maven-transfer-progress-download-logs) for more details.

### Download integrity verification

When a selected distribution publishes an authoritative checksum for an archive, `setup-java` automatically verifies each downloaded JDK, JRE, or JMOD archive before extraction and caching. No input is required. Automatic checksum verification is currently available for `temurin`, `semeru`, `adopt`, `corretto`, `dragonwell`, `kona`, `sapmachine`, `graalvm`, `graalvm-community`, `zulu`, `oracle`, `oracle-openjdk`, `microsoft`, and `jetbrains`.

Distributions or individual releases without an authoritative checksum continue to install normally, with the omission reported only in debug logs. Archives resolved directly from the runner tool cache are not downloaded again and therefore are not reverified.

Checksums detect corrupted or unexpectedly modified downloads before they are persisted in the runner tool cache.

### Basic Configuration

#### Eclipse Temurin
Expand Down
130 changes: 130 additions & 0 deletions __tests__/checksum.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
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'});
});
});
8 changes: 8 additions & 0 deletions __tests__/distributors/adopt-installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ describe('findPackageForDownload', () => {
distribution['getAvailableVersions'] = async () => manifestData as any;
const resolvedVersion = await distribution['findPackageForDownload'](input);
expect(resolvedVersion.version).toBe(expected);
const vendorPackage = (manifestData as any[]).find(
item => item.version_data.semver === expected
).binaries[0].package;
expect(resolvedVersion.checksum).toEqual({
algorithm: 'sha256',
value: vendorPackage.checksum,
source: vendorPackage.checksum_link
});
});

it('version is found but binaries list is empty', async () => {
Expand Down
Loading
Loading