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:
Bruno Borges
2026-07-29 04:43:56 -04:00
committed by GitHub
parent 19c23b379e
commit 27f2c62824
39 changed files with 1629 additions and 103 deletions
+83
View File
@@ -0,0 +1,83 @@
import {createHash, timingSafeEqual} from 'crypto';
import {createReadStream} from 'fs';
import {pipeline} from 'stream/promises';
import {ChecksumMetadata} from './distributions/base-models.js';
export interface ChecksumVerificationContext {
distribution: string;
version: string;
}
function sanitizedSource(source: string | undefined): string {
if (!source) {
return '';
}
try {
const url = new URL(source);
return ` from ${url.origin}${url.pathname}`;
} catch {
return ' from an invalid checksum source';
}
}
// Length, in hex characters, of a digest produced by each supported algorithm.
// Exported so callers (e.g. fetchChecksum) can infer which algorithm a vendor
// actually used when it doesn't disclose it via the checksum URL/filename.
export function expectedDigestLength(
algorithm: ChecksumMetadata['algorithm']
): number {
return algorithm === 'sha256' ? 64 : algorithm === 'sha512' ? 128 : 0;
}
function normalizeExpectedDigest(checksum: ChecksumMetadata): string {
const algorithm = checksum.algorithm;
const digest =
typeof checksum.value === 'string'
? checksum.value.trim().toLowerCase()
: '';
const expectedLength = expectedDigestLength(algorithm);
if (expectedLength === 0) {
throw new Error(
`Unsupported checksum algorithm '${String(algorithm)}'${sanitizedSource(checksum.source)}. Supported algorithms are sha256 and sha512.`
);
}
if (!new RegExp(`^[a-f0-9]{${expectedLength}}$`).test(digest)) {
throw new Error(
`Malformed ${algorithm} checksum metadata${sanitizedSource(checksum.source)}: expected a ${expectedLength}-character hexadecimal digest.`
);
}
return digest;
}
export async function calculateChecksum(
filePath: string,
algorithm: ChecksumMetadata['algorithm']
): Promise<string> {
const hash = createHash(algorithm);
await pipeline(createReadStream(filePath), hash);
return hash.digest('hex');
}
export async function verifyChecksum(
filePath: string,
checksum: ChecksumMetadata,
context: ChecksumVerificationContext
): Promise<void> {
const expected = normalizeExpectedDigest(checksum);
const actual = await calculateChecksum(filePath, checksum.algorithm);
const matches = timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(actual, 'hex')
);
if (!matches) {
throw new Error(
`Checksum verification failed for ${context.distribution} version ${context.version}: ${checksum.algorithm} expected ${expected}, actual ${actual}.`
);
}
}
+7 -2
View File
@@ -105,7 +105,12 @@ export class AdoptDistribution extends JavaBase {
.map(item => {
return {
version: item.version_data.semver,
url: item.binaries[0].package.link
url: item.binaries[0].package.link,
checksum: {
algorithm: 'sha256',
value: item.binaries[0].package.checksum,
source: item.binaries[0].package.checksum_link
}
} as JavaDownloadRelease;
});
@@ -133,7 +138,7 @@ export class AdoptDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+98
View File
@@ -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(
+9
View File
@@ -14,8 +14,17 @@ export interface JavaInstallerResults {
path: string;
}
export type ChecksumAlgorithm = 'sha256' | 'sha512';
export interface ChecksumMetadata {
algorithm: ChecksumAlgorithm;
value: string;
source?: string;
}
export interface JavaDownloadRelease {
version: string;
url: string;
signatureUrl?: string;
checksum?: ChecksumMetadata;
}
+12 -6
View File
@@ -19,6 +19,9 @@ import {
ICorrettoAvailableVersions
} from './models.js';
const CORRETTO_VERSIONS_URL =
'https://corretto.github.io/corretto-downloads/latest_links/indexmap_with_checksum.json';
export class CorrettoDistribution extends JavaBase {
constructor(installerOptions: JavaInstallerOptions) {
super('Corretto', installerOptions);
@@ -30,7 +33,7 @@ export class CorrettoDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
@@ -86,7 +89,12 @@ export class CorrettoDistribution extends JavaBase {
.map(item => {
return {
version: convertVersionToSemver(item.correttoVersion),
url: item.downloadLink
url: item.downloadLink,
checksum: {
algorithm: 'sha256',
value: item.checksum_sha256,
source: CORRETTO_VERSIONS_URL
}
} as JavaDownloadRelease;
});
@@ -110,16 +118,14 @@ export class CorrettoDistribution extends JavaBase {
console.time('Retrieving available versions for Corretto took'); // eslint-disable-line no-console
}
const availableVersionsUrl =
'https://corretto.github.io/corretto-downloads/latest_links/indexmap_with_checksum.json';
const fetchCurrentVersions =
await this.http.getJson<ICorrettoAllAvailableVersions>(
availableVersionsUrl
CORRETTO_VERSIONS_URL
);
const fetchedCurrentVersions = fetchCurrentVersions.result;
if (!fetchedCurrentVersions) {
throw Error(
`Could not fetch latest corretto versions from ${availableVersionsUrl}`
`Could not fetch latest corretto versions from ${CORRETTO_VERSIONS_URL}`
);
}
+8 -2
View File
@@ -46,7 +46,13 @@ export class DragonwellDistribution extends JavaBase {
.map(item => {
return {
version: item.jdk_version,
url: item.download_link
url: item.download_link,
checksum: item.checksum
? {
algorithm: 'sha256',
value: item.checksum
}
: undefined
} as JavaDownloadRelease;
});
@@ -102,7 +108,7 @@ export class DragonwellDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+23 -4
View File
@@ -43,6 +43,7 @@ type OsVersions = 'linux' | 'macos' | 'windows';
interface GraalVMCommunityAsset {
name: string;
browser_download_url: string;
digest?: string;
}
interface GraalVMCommunityRelease {
@@ -66,7 +67,7 @@ export class GraalVMDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
@@ -145,7 +146,11 @@ export class GraalVMDistribution extends JavaBase {
const response = await this.http.head(fileUrl);
this.handleHttpResponse(response, range);
return {url: fileUrl, version: range};
return {
url: fileUrl,
version: range,
checksum: await this.fetchChecksum(`${fileUrl}.sha256`, 'sha256')
};
}
protected validateVersionRange(range: string): void {
@@ -284,7 +289,8 @@ export class GraalVMDistribution extends JavaBase {
return {
url: downloadUrl,
version: latestVersion.version
version: latestVersion.version,
checksum: await this.fetchChecksum(`${downloadUrl}.sha256`, 'sha256')
};
}
@@ -456,9 +462,22 @@ export class GraalVMCommunityDistribution extends GraalVMDistribution {
for (const asset of release.assets ?? []) {
const version = this.extractAssetVersion(asset.name, assetSuffix);
if (version) {
const digest = asset.digest?.match(/^sha256:([a-f0-9]{64})$/i)?.[1];
if (!digest) {
core.debug(
`No authoritative sha256 digest is available for ${asset.name}; skipping checksum verification for this asset.`
);
}
versions.set(version, {
version,
url: asset.browser_download_url
url: asset.browser_download_url,
checksum: digest
? {
algorithm: 'sha256',
value: digest,
source: GRAALVM_COMMUNITY_RELEASES_URL
}
: undefined
});
}
}
+12 -2
View File
@@ -50,7 +50,17 @@ export class JetBrainsDistribution extends JavaBase {
throw this.createVersionNotFoundError(range, availableVersionStrings);
}
return resolvedFullVersion;
return {
...resolvedFullVersion,
// JetBrains' `.checksum` sibling doesn't disclose its algorithm via the
// filename, and older JBR builds (e.g. JBR 11) publish a SHA-256 digest
// there while newer builds publish SHA-512. Accept either, preferring
// the stronger SHA-512 when the digest length is ambiguous.
checksum: await this.fetchChecksum(
`${resolvedFullVersion.url}.checksum`,
['sha512', 'sha256']
)
};
}
protected async downloadTool(
@@ -60,7 +70,7 @@ export class JetBrainsDistribution extends JavaBase {
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
const javaArchivePath = await tc.downloadTool(javaRelease.url);
const javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extractedJavaPath = await extractJdkFile(javaArchivePath, 'tar.gz');
+15 -8
View File
@@ -19,6 +19,9 @@ import {
renameWinArchive
} from '../../util.js';
const KONA_RELEASES_URL =
'https://tencent.github.io/konajdk/releases/kona-v1.json';
export class KonaDistribution extends JavaBase {
constructor(installerOptions: JavaInstallerOptions) {
super('Kona', installerOptions);
@@ -30,7 +33,7 @@ export class KonaDistribution extends JavaBase {
core.info(
`Downloading Kona JDK ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
const javaArchivePath = await tc.downloadTool(javaRelease.url);
const javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
@@ -74,7 +77,14 @@ export class KonaDistribution extends JavaBase {
.map(item => {
return {
version: item.version,
url: item.downloadUrl
url: item.downloadUrl,
checksum: item.checksum
? {
algorithm: 'sha256',
value: item.checksum,
source: KONA_RELEASES_URL
}
: undefined
} as JavaDownloadRelease;
})
.sort((a, b) => -semver.compareBuild(a.version, b.version));
@@ -115,16 +125,13 @@ export class KonaDistribution extends JavaBase {
}
private async fetchReleaseInfo(): Promise<IKonaReleaseInfo | null> {
const releasesInfoUrl =
'https://tencent.github.io/konajdk/releases/kona-v1.json';
try {
core.debug(`Fetching Kona release info from URL: ${releasesInfoUrl}`);
return (await this.http.getJson<IKonaReleaseInfo>(releasesInfoUrl))
core.debug(`Fetching Kona release info from URL: ${KONA_RELEASES_URL}`);
return (await this.http.getJson<IKonaReleaseInfo>(KONA_RELEASES_URL))
.result;
} catch (err) {
core.debug(
`Fetching Kona release info from the URL: ${releasesInfoUrl} failed with the error: ${
`Fetching Kona release info from the URL: ${KONA_RELEASES_URL} failed with the error: ${
(err as Error).message
}`
);
+1 -1
View File
@@ -32,7 +32,7 @@ export class LibericaNikDistributions extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+1 -1
View File
@@ -32,7 +32,7 @@ export class LibericaDistributions extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+6 -2
View File
@@ -31,7 +31,7 @@ export class MicrosoftDistributions extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
if (this.verifySignature) {
if (!javaRelease.signatureUrl) {
@@ -114,7 +114,11 @@ export class MicrosoftDistributions extends JavaBase {
return {
url: file.download_url,
signatureUrl,
version: foundRelease.version
version: foundRelease.version,
checksum: await this.fetchChecksum(
`${file.download_url}.sha256sum.txt`,
'sha256'
)
};
}
+6 -2
View File
@@ -50,7 +50,11 @@ export class OpenJdkDistribution extends JavaBase {
);
}
return matchingReleases[0];
const release = matchingReleases[0];
return {
...release,
checksum: await this.fetchChecksum(`${release.url}.sha256`, 'sha256')
};
}
protected async downloadTool(
@@ -59,7 +63,7 @@ export class OpenJdkDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = javaRelease.url.endsWith('.zip') ? 'zip' : 'tar.gz';
+6 -2
View File
@@ -32,7 +32,7 @@ export class OracleDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
@@ -112,7 +112,11 @@ export class OracleDistribution extends JavaBase {
const response = await this.http.head(url);
if (response.message.statusCode === HttpCodes.OK) {
return {url, version: range};
return {
url,
version: range,
checksum: await this.fetchChecksum(`${url}.sha256`, 'sha256')
};
}
if (response.message.statusCode !== HttpCodes.NotFound) {
+9 -2
View File
@@ -56,7 +56,14 @@ export class SapMachineDistribution extends JavaBase {
}
const resolvedVersion = matchedVersions[0];
return resolvedVersion;
const checksumUrl = resolvedVersion.url.replace(
/\.(?:tar\.gz|zip)$/,
'.sha256.txt'
);
return {
...resolvedVersion,
checksum: await this.fetchChecksum(checksumUrl, 'sha256')
};
}
private async getAvailableVersions(): Promise<ISapMachineVersions[]> {
@@ -104,7 +111,7 @@ export class SapMachineDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+7 -2
View File
@@ -69,7 +69,12 @@ export class SemeruDistribution extends JavaBase {
: item.version_data.semver.replace('-beta+', '+');
return {
version: formattedVersion,
url: item.binaries[0].package.link
url: item.binaries[0].package.link,
checksum: {
algorithm: 'sha256',
value: item.binaries[0].package.checksum,
source: item.binaries[0].package.checksum_link
}
} as JavaDownloadRelease;
});
@@ -104,7 +109,7 @@ export class SemeruDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+7 -2
View File
@@ -69,7 +69,12 @@ export class TemurinDistribution extends JavaBase {
return {
version: formattedVersion,
url: item.binaries[0].package.link,
signatureUrl: item.binaries[0].package.signature_link
signatureUrl: item.binaries[0].package.signature_link,
checksum: {
algorithm: 'sha256',
value: item.binaries[0].package.checksum,
source: item.binaries[0].package.checksum_link
}
} as JavaDownloadRelease;
});
@@ -132,7 +137,7 @@ export class TemurinDistribution extends JavaBase {
}
private async downloadPackage(release: JavaDownloadRelease): Promise<string> {
const archivePath = await tc.downloadTool(release.url);
const archivePath = await this.downloadAndVerify(release);
if (this.verifySignature) {
if (!release.signatureUrl) {
+41 -10
View File
@@ -6,7 +6,7 @@ import fs from 'fs';
import semver from 'semver';
import {JavaBase} from '../base-installer.js';
import {IZuluVersions} from './models.js';
import {IZuluPackageDetails, IZuluVersions} from './models.js';
import {
extractJdkFile,
getDownloadArchiveExtension,
@@ -20,6 +20,15 @@ import {
JavaInstallerResults
} from '../base-models.js';
// The Azul Metadata API only reports the sha256 checksum on the
// package-details endpoint, keyed by package_uuid, so the resolved candidate
// must retain its UUID after sorting until the single follow-up request is made.
interface ZuluResolvedRelease {
version: string;
url: string;
packageUuid: string;
}
export class ZuluDistribution extends JavaBase {
constructor(installerOptions: JavaInstallerOptions) {
super('Zulu', installerOptions);
@@ -40,7 +49,8 @@ export class ZuluDistribution extends JavaBase {
return {
version: convertVersionToSemver(javaVersion),
url: item.download_url,
zuluVersion: convertVersionToSemver(item.distro_version)
zuluVersion: convertVersionToSemver(item.distro_version),
packageUuid: item.package_uuid
};
});
@@ -54,12 +64,11 @@ export class ZuluDistribution extends JavaBase {
-semver.compareBuild(a.zuluVersion, b.zuluVersion)
);
})
.map(item => {
return {
version: item.version,
url: item.url
} as JavaDownloadRelease;
});
.map((item): ZuluResolvedRelease => ({
version: item.version,
url: item.url,
packageUuid: item.packageUuid
}));
const resolvedFullVersion =
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
@@ -70,7 +79,29 @@ export class ZuluDistribution extends JavaBase {
throw this.createVersionNotFoundError(version, availableVersionStrings);
}
return resolvedFullVersion;
const packageDetailsUrl = `https://api.azul.com/metadata/v1/zulu/packages/${resolvedFullVersion.packageUuid}`;
const packageDetails = (
await this.http.getJson<IZuluPackageDetails>(packageDetailsUrl)
).result;
const digest = packageDetails?.sha256_hash?.match(/^[a-f0-9]{64}$/i)?.[0];
if (!digest) {
core.debug(
`No authoritative sha256 checksum is available for Zulu version ${resolvedFullVersion.version} from ${packageDetailsUrl}; skipping checksum verification.`
);
}
return {
version: resolvedFullVersion.version,
url: resolvedFullVersion.url,
checksum: digest
? {
algorithm: 'sha256',
value: digest,
source: packageDetailsUrl
}
: undefined
};
}
protected async downloadTool(
@@ -79,7 +110,7 @@ export class ZuluDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+4
View File
@@ -10,3 +10,7 @@ export interface IZuluVersions {
latest: boolean;
availability_type: string;
}
export interface IZuluPackageDetails {
sha256_hash?: string;
}