mirror of
https://github.com/actions/setup-java.git
synced 2026-07-31 21:36:17 +00:00
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
This commit is contained in:
@@ -10,6 +10,8 @@ import {
|
||||
isVersionSatisfies
|
||||
} from '../util.js';
|
||||
import {
|
||||
ChecksumAlgorithm,
|
||||
ChecksumMetadata,
|
||||
JavaDownloadRelease,
|
||||
JavaInstallerOptions,
|
||||
JavaInstallerResults
|
||||
@@ -17,6 +19,7 @@ import {
|
||||
import {MACOS_JAVA_CONTENT_POSTFIX} from '../constants.js';
|
||||
import {RetryingHttpClient} from '../retrying-http-client.js';
|
||||
import os from 'os';
|
||||
import {expectedDigestLength, verifyChecksum} from '../checksum.js';
|
||||
|
||||
export abstract class JavaBase {
|
||||
protected http: httpm.HttpClient;
|
||||
@@ -61,6 +64,101 @@ export abstract class JavaBase {
|
||||
range: string
|
||||
): Promise<JavaDownloadRelease>;
|
||||
|
||||
protected async downloadAndVerify(
|
||||
javaRelease: JavaDownloadRelease
|
||||
): Promise<string> {
|
||||
const archivePath = await tc.downloadTool(javaRelease.url);
|
||||
const checksum = javaRelease.checksum;
|
||||
if (!checksum || !checksum.value?.trim()) {
|
||||
core.debug(
|
||||
`No authoritative checksum is available for ${this.distribution} version ${javaRelease.version}; skipping checksum verification.`
|
||||
);
|
||||
return archivePath;
|
||||
}
|
||||
|
||||
try {
|
||||
await verifyChecksum(archivePath, checksum, {
|
||||
distribution: this.distribution,
|
||||
version: javaRelease.version
|
||||
});
|
||||
core.debug(
|
||||
`Verified ${checksum.algorithm} checksum for ${this.distribution} version ${javaRelease.version}.`
|
||||
);
|
||||
return archivePath;
|
||||
} catch (error) {
|
||||
let cleanupError: unknown;
|
||||
let cleanupFailed = false;
|
||||
try {
|
||||
await fs.promises.rm(archivePath, {force: true});
|
||||
} catch (caughtCleanupError) {
|
||||
cleanupError = caughtCleanupError;
|
||||
cleanupFailed = true;
|
||||
}
|
||||
if (cleanupFailed) {
|
||||
throw new Error(
|
||||
`${(error as Error).message} Failed to remove the downloaded archive after verification failure: ${(cleanupError as Error).message}`,
|
||||
{cause: error}
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
protected async fetchChecksum(
|
||||
checksumUrl: string,
|
||||
algorithm: ChecksumAlgorithm | ChecksumAlgorithm[]
|
||||
): Promise<ChecksumMetadata | undefined> {
|
||||
// Some vendors (e.g. JetBrains) publish a single, generically-named
|
||||
// checksum sibling (`.checksum`) whose digest algorithm isn't disclosed
|
||||
// by the URL and has changed across releases. Accepting a list of
|
||||
// candidate algorithms lets callers pass every algorithm the vendor is
|
||||
// known to use; the actual algorithm is then inferred from the length of
|
||||
// the returned digest.
|
||||
const algorithms = Array.isArray(algorithm) ? algorithm : [algorithm];
|
||||
const algorithmLabel = algorithms.join(' or ');
|
||||
|
||||
const response = await this.http.get(checksumUrl);
|
||||
const statusCode = response.message.statusCode;
|
||||
const source = (() => {
|
||||
try {
|
||||
const url = new URL(checksumUrl);
|
||||
return `${url.origin}${url.pathname}`;
|
||||
} catch {
|
||||
return 'an invalid checksum URL';
|
||||
}
|
||||
})();
|
||||
|
||||
if (statusCode === httpm.HttpCodes.NotFound) {
|
||||
core.debug(
|
||||
`No authoritative ${algorithmLabel} checksum is available for ${this.distribution} from ${source}; skipping checksum verification.`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (statusCode !== httpm.HttpCodes.OK) {
|
||||
throw new Error(
|
||||
`Failed to fetch the authoritative ${algorithmLabel} checksum for ${this.distribution} from ${source} (HTTP ${statusCode}).`
|
||||
);
|
||||
}
|
||||
|
||||
const body = await response.readBody();
|
||||
const value = body.trim().split(/\s+/, 1)[0] ?? '';
|
||||
if (!value) {
|
||||
throw new Error(
|
||||
`Received an empty authoritative ${algorithmLabel} checksum for ${this.distribution} from ${source}.`
|
||||
);
|
||||
}
|
||||
|
||||
// Prefer the strongest algorithm whose digest length matches what was
|
||||
// actually returned; fall back to the first candidate (preserving prior
|
||||
// behavior/error messages) when the digest doesn't match any of them.
|
||||
const resolvedAlgorithm =
|
||||
algorithms.find(algo => value.length === expectedDigestLength(algo)) ??
|
||||
algorithms[0];
|
||||
|
||||
return {algorithm: resolvedAlgorithm, value, source: checksumUrl};
|
||||
}
|
||||
|
||||
public async setupJava(): Promise<JavaInstallerResults> {
|
||||
if (this.verifySignature && !this.supportsSignatureVerification()) {
|
||||
throw new Error(
|
||||
|
||||
Reference in New Issue
Block a user