Verify JDK downloads with vendor checksums (#1167) · actions/setup-java@27f2c62 · GitHub
Skip to content

Commit 27f2c62

Browse files
brunoborgesCopilot
andauthored
Verify JDK downloads with vendor checksums (#1167)
* Verify JDK downloads with vendor checksums Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Handle missing vendor checksum values Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Preserve checksum error during cleanup failure Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Validate checksum metadata value types Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Clarify checksum documentation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Expand vendor checksum verification Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Accept SHA-256 or SHA-512 for JetBrains checksum sibling JetBrains publishes a single, generically-named ".checksum" sibling whose digest algorithm isn't disclosed by the filename. Older JBR 11 builds (e.g. jbrsdk_nomod-11_0_16-*-b2043.64.tar.gz) publish a SHA-256 digest there, while newer builds publish SHA-512. The JetBrains installer previously assumed SHA-512 unconditionally, so verification failed with "Malformed sha512 checksum metadata ... expected a 128-character hexadecimal digest" for those older builds, breaking the jetbrains 11 e2e job on macOS and Windows. fetchChecksum now accepts a list of candidate algorithms and infers the actual algorithm from the returned digest's length, preferring the strongest match. The JetBrains installer passes ['sha512', 'sha256']; all other callers are unaffected since they already pass a single, vendor-disclosed algorithm. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Use SapMachine archive checksum files Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d
1 parent 19c23b3 commit 27f2c62

39 files changed

Lines changed: 1629 additions & 103 deletions

‎.github/workflows/e2e-versions.yml‎

Lines changed: 19 additions & 0 deletions

‎README.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ For more details, see the full release notes on the [releases page](https://git
9595

9696
- `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.
9797

98+
### Download integrity verification
99+
100+
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`.
101+
102+
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.
103+
104+
Checksums detect corrupted or unexpectedly modified downloads before they are persisted in the runner tool cache.
105+
98106
### Basic Configuration
99107

100108
#### Eclipse Temurin

‎__tests__/checksum.test.ts‎

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import {afterEach, describe, expect, it, jest} from '@jest/globals';
2+
import {createHash} from 'crypto';
3+
import fs from 'fs';
4+
import os from 'os';
5+
import path from 'path';
6+
7+
import {calculateChecksum, verifyChecksum} from '../src/checksum.js';
8+
import type {ChecksumMetadata} from '../src/distributions/base-models.js';
9+
10+
const temporaryPaths: string[] = [];
11+
12+
async function temporaryFile(contents: string): Promise<string> {
13+
const directory = await fs.promises.mkdtemp(
14+
path.join(os.tmpdir(), 'setup-java-checksum-')
15+
);
16+
const file = path.join(directory, 'archive');
17+
await fs.promises.writeFile(file, contents);
18+
temporaryPaths.push(directory);
19+
return file;
20+
}
21+
22+
afterEach(async () => {
23+
await Promise.all(
24+
temporaryPaths
25+
.splice(0)
26+
.map(item => fs.promises.rm(item, {recursive: true, force: true}))
27+
);
28+
jest.restoreAllMocks();
29+
});
30+
31+
describe('verifyChecksum', () => {
32+
it.each(['sha256', 'sha512'] as const)(
33+
'verifies a matching %s digest',
34+
async algorithm => {
35+
const contents = `jdk archive for ${algorithm}`;
36+
const file = await temporaryFile(contents);
37+
const value = createHash(algorithm).update(contents).digest('hex');
38+
39+
await expect(
40+
verifyChecksum(
41+
file,
42+
{algorithm, value: value.toUpperCase()},
43+
{distribution: 'Test', version: '21.0.1'}
44+
)
45+
).resolves.toBeUndefined();
46+
}
47+
);
48+
49+
it('reports mismatch context and both digests', async () => {
50+
const file = await temporaryFile('corrupt archive');
51+
const expected = 'a'.repeat(64);
52+
const actual = await calculateChecksum(file, 'sha256');
53+
54+
await expect(
55+
verifyChecksum(
56+
file,
57+
{algorithm: 'sha256', value: expected},
58+
{distribution: 'Corretto', version: '21.0.8'}
59+
)
60+
).rejects.toThrow(
61+
`Checksum verification failed for Corretto version 21.0.8: sha256 expected ${expected}, actual ${actual}.`
62+
);
63+
});
64+
65+
it('rejects malformed digest metadata before reading the file', async () => {
66+
await expect(
67+
verifyChecksum(
68+
'/missing/archive',
69+
{algorithm: 'sha512', value: 'not-a-digest'},
70+
{distribution: 'Test', version: '17'}
71+
)
72+
).rejects.toThrow(
73+
'Malformed sha512 checksum metadata: expected a 128-character hexadecimal digest.'
74+
);
75+
});
76+
77+
it.each([undefined, null, 123])(
78+
'reports a malformed digest when the value is %p',
79+
async value => {
80+
const checksum = {
81+
algorithm: 'sha256',
82+
value
83+
} as unknown as ChecksumMetadata;
84+
85+
await expect(
86+
verifyChecksum('/missing/archive', checksum, {
87+
distribution: 'Test',
88+
version: '17'
89+
})
90+
).rejects.toThrow(
91+
'Malformed sha256 checksum metadata: expected a 64-character hexadecimal digest.'
92+
);
93+
}
94+
);
95+
96+
it('rejects unsupported algorithms without leaking source query parameters', async () => {
97+
const checksum = {
98+
algorithm: 'md5',
99+
value: 'a'.repeat(32),
100+
source: 'https://vendor.example/checksum.txt?token=secret-value#private'
101+
} as unknown as ChecksumMetadata;
102+
103+
let message = '';
104+
try {
105+
await verifyChecksum('/missing/archive', checksum, {
106+
distribution: 'Test',
107+
version: '17'
108+
});
109+
} catch (error) {
110+
message = (error as Error).message;
111+
}
112+
113+
expect(message).toContain(
114+
"Unsupported checksum algorithm 'md5' from https://vendor.example/checksum.txt"
115+
);
116+
expect(message).not.toContain('secret-value');
117+
expect(message).not.toContain('token=');
118+
expect(message).not.toContain('#private');
119+
});
120+
121+
it('surfaces file read errors', async () => {
122+
await expect(
123+
verifyChecksum(
124+
'/missing/archive',
125+
{algorithm: 'sha256', value: 'a'.repeat(64)},
126+
{distribution: 'Test', version: '17'}
127+
)
128+
).rejects.toMatchObject({code: 'ENOENT'});
129+
});
130+
});

‎__tests__/distributors/adopt-installer.test.ts‎

Lines changed: 8 additions & 0 deletions

0 commit comments

Comments
 (0)