Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1436e90
Add verify-signature plumbing and Temurin verification support
Copilot Jun 17, 2026
39460f2
Rebuild dist after signature verification changes
Copilot Jun 17, 2026
70770c9
Refine signature verification errors and regenerate dist
Copilot Jun 17, 2026
b2f4bcb
refactor: make gpg.ts generic, move Adoptium-specific constant to tem…
Copilot Jun 17, 2026
fc8acd0
fix: mock renameWinArchive in temurin tests and add signature e2e job
Copilot Jun 17, 2026
c607d20
Merge pull request #5 from johnoliver/copilot/fix-github-actions-job-…
johnoliver Jun 17, 2026
a3589a9
refactor: bundle Adoptium public key, replace keyserver lookup with l…
Copilot Jun 24, 2026
33264d6
feat: add verify-signature-public-key input to allow custom GPG key o…
Copilot Jun 24, 2026
40f7b5c
refactor: extract Adoptium public key to adoptium-key.ts; tighten gpg…
Copilot Jun 24, 2026
3f3ac23
Add verify-signature plumbing and Temurin verification support
johnoliver Jun 24, 2026
c2ac82f
Potential fix for pull request finding
johnoliver Jun 24, 2026
03daa99
Potential fix for pull request finding
johnoliver Jun 24, 2026
db1f1b8
Add Microsoft signature verification support
Copilot Jun 25, 2026
2b15efd
Regenerate dist bundles for Microsoft signature checks
Copilot Jun 25, 2026
5dab176
Harden Microsoft signature URL handling
Copilot Jun 25, 2026
2c76c5e
Merge branch 'copilot/include-signature-verification' into signature-4
johnoliver Jun 25, 2026
776fcf9
Add setup-java-microsoft-signature-verification e2e job
Copilot Jun 25, 2026
2aeafef
Merge remote-tracking branch 'origin/copilot/include-signature-verifi…
johnoliver Jun 25, 2026
45cdfed
chore: regenerate dist files
Copilot Jun 25, 2026
673ccf9
Fix e2e-versions: remove duplicate job, update signature jobs to chec…
Copilot Jun 25, 2026
e2b8899
Fix Prettier formatting in test files
Copilot Jun 25, 2026
730e373
fix: mock renameWinArchive in microsoft-installer tests to fix Window…
Copilot Jun 25, 2026
8012407
fix: use --homedir flag instead of GNUPGHOME env var for Windows GPG …
Copilot Jun 25, 2026
2c98690
fix: convert Windows paths to POSIX format for MSYS2 GPG on Windows
Copilot Jun 25, 2026
165ecdd
Fix gpg test formatting
Copilot Jun 25, 2026
118154c
Merge branch 'main' into signature-4
brunoborges Jun 26, 2026
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
23 changes: 23 additions & 0 deletions .github/workflows/e2e-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,29 @@ jobs:
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
shell: bash

setup-java-temurin-signature-verification:
name: temurin ${{ matrix.version }} signature verification - ${{ matrix.os }}
needs: setup-java-major-minor-versions
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
version: ['21', '17']
steps:
- name: Checkout
uses: actions/checkout@v6
- name: setup-java with signature verification
uses: ./
id: setup-java
with:
java-version: ${{ matrix.version }}
distribution: temurin
verify-signature: true
- name: Verify Java
run: bash __tests__/verify-java.sh "${{ matrix.version }}" "${{ steps.setup-java.outputs.path }}"
shell: bash

setup-java-ea-versions-sapmachine:
name: sapmachine ${{ matrix.version }} (jdk-x64) - ${{ matrix.os }}
needs: setup-java-major-minor-versions
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ For more details, see the full release notes on the [releases page](https://git

- `check-latest`: Setting this option makes the action to check for the latest available version for the version spec.

- `verify-signature`: Verifies downloaded Java package signatures when supported by the selected distribution. Currently supported for `temurin`. If set to `true` for unsupported distributions, the action fails.

Comment thread
johnoliver marked this conversation as resolved.
Outdated
Comment thread
johnoliver marked this conversation as resolved.
Outdated
- `cache`: Quick [setup caching](#caching-packages-dependencies) for the dependencies managed through one of the predefined package managers. It can be one of "maven", "gradle" or "sbt".

- `cache-dependency-path`: The path to a dependency file: pom.xml, build.gradle, build.sbt, etc. This option can be used with the `cache` option. If this option is omitted, the action searches for the dependency file in the entire repository. This option supports wildcards and a list of file names for caching multiple dependencies.
Expand Down
297 changes: 198 additions & 99 deletions __tests__/data/temurin.json

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions __tests__/distributors/base-installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,24 @@ describe('setupJava', () => {
}
);

it('should fail when verify-signature is enabled for unsupported distributions', async () => {
mockJavaBase = new EmptyJavaBase({
version: '11',
architecture: 'x86',
packageType: 'jdk',
checkLatest: false,
verifySignature: true
});

await expect(mockJavaBase.setupJava()).rejects.toThrow(
"Input 'verify-signature' is not supported for distribution 'Empty'."
);
expect(spyTcFindAllVersions).not.toHaveBeenCalled();
expect(spyCoreAddPath).not.toHaveBeenCalled();
expect(spyCoreExportVariable).not.toHaveBeenCalled();
expect(spyCoreSetOutput).not.toHaveBeenCalled();
});

it.each([
[
{
Expand Down
114 changes: 113 additions & 1 deletion __tests__/distributors/temurin-installer.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import {HttpClient} from '@actions/http-client';
import * as tc from '@actions/tool-cache';
import fs from 'fs';
import os from 'os';
import {
TemurinDistribution,
TemurinImplementation
TemurinImplementation,
ADOPTIUM_PUBLIC_KEY
} from '../../src/distributions/temurin/installer';
import {JavaInstallerOptions} from '../../src/distributions/base-models';
import * as util from '../../src/util';
import * as gpg from '../../src/gpg';

import manifestData from '../data/temurin.json';
import * as core from '@actions/core';
Expand Down Expand Up @@ -231,6 +236,7 @@ describe('findPackageForDownload', () => {
distribution['getAvailableVersions'] = async () => manifestData as any;
const resolvedVersion = await distribution['findPackageForDownload'](input);
expect(resolvedVersion.version).toBe(expected);
expect(resolvedVersion.signatureUrl).toBeDefined();
});

it('version is found but binaries list is empty', async () => {
Expand Down Expand Up @@ -281,3 +287,109 @@ describe('findPackageForDownload', () => {
);
});
});

describe('downloadTool', () => {
let spyDownloadTool: jest.SpyInstance;
let spyVerifySignature: jest.SpyInstance;
let spyExtractJdkFile: jest.SpyInstance;
let spyCacheDir: jest.SpyInstance;
let spyReadDirSync: jest.SpyInstance;
let spyRenameWinArchive: jest.SpyInstance;

beforeEach(() => {
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool.mockResolvedValue('/tmp/jdk.tar.gz');
spyVerifySignature = jest.spyOn(gpg, 'verifyPackageSignature');
spyVerifySignature.mockResolvedValue(undefined);
spyExtractJdkFile = jest.spyOn(util, 'extractJdkFile');
spyExtractJdkFile.mockResolvedValue('/tmp/extracted');
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockResolvedValue('/tmp/toolcache');
spyReadDirSync = jest.spyOn(fs, 'readdirSync');
spyReadDirSync.mockReturnValue(['jdk-17'] as any);
spyRenameWinArchive = jest.spyOn(util, 'renameWinArchive');
spyRenameWinArchive.mockReturnValue('/tmp/jdk.tar.gz.zip');
});

afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
});

it('verifies signature when enabled', async () => {
const distribution = new TemurinDistribution(
{
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false,
verifySignature: true
},
TemurinImplementation.Hotspot
);

await distribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz',
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
});

expect(spyVerifySignature).toHaveBeenCalledWith(
'/tmp/jdk.tar.gz',
'https://example.com/jdk.tar.gz.sig',
ADOPTIUM_PUBLIC_KEY
);
});

it('fails when signature is missing and verification is enabled', async () => {
const distribution = new TemurinDistribution(
{
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false,
verifySignature: true
},
TemurinImplementation.Hotspot
);

await expect(
distribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz'
})
).rejects.toThrow(
"Input 'verify-signature' is enabled, but no signature URL was found"
);
expect(spyVerifySignature).not.toHaveBeenCalled();
});

it('uses custom public key when verifySignaturePublicKey is provided', async () => {
const customKey =
'-----BEGIN PGP PUBLIC KEY BLOCK-----\ncustom\n-----END PGP PUBLIC KEY BLOCK-----';
const distribution = new TemurinDistribution(
{
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false,
verifySignature: true,
verifySignaturePublicKey: customKey
},
TemurinImplementation.Hotspot
);

await distribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz',
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
});

expect(spyVerifySignature).toHaveBeenCalledWith(
'/tmp/jdk.tar.gz',
'https://example.com/jdk.tar.gz.sig',
customKey
);
});
});
35 changes: 35 additions & 0 deletions __tests__/gpg.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as path from 'path';
import * as io from '@actions/io';
import * as exec from '@actions/exec';
import * as tc from '@actions/tool-cache';
import * as gpg from '../src/gpg';

jest.mock('@actions/exec', () => {
Expand All @@ -9,6 +10,12 @@ jest.mock('@actions/exec', () => {
};
});

jest.mock('@actions/tool-cache', () => {
return {
downloadTool: jest.fn()
};
});

const tempDir = path.join(__dirname, 'runner', 'temp');
process.env['RUNNER_TEMP'] = tempDir;

Expand Down Expand Up @@ -51,5 +58,33 @@ describe('gpg tests', () => {
expect.anything()
);
});

describe('verifyPackageSignature', () => {
it('imports bundled key and verifies package', async () => {
const publicKeyContent = '-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest\n-----END PGP PUBLIC KEY BLOCK-----';
(tc.downloadTool as jest.Mock).mockResolvedValue('/tmp/jdk.tar.gz.sig');
await gpg.verifyPackageSignature(
'/tmp/jdk.tar.gz',
'https://example.com/jdk.tar.gz.sig',
publicKeyContent
);

expect(tc.downloadTool).toHaveBeenCalledWith(
'https://example.com/jdk.tar.gz.sig'
);
expect(exec.exec).toHaveBeenNthCalledWith(
1,
'gpg',
['--batch', '--import', expect.stringContaining('public-key.asc')],
expect.objectContaining({silent: true})
);
expect(exec.exec).toHaveBeenNthCalledWith(
2,
'gpg',
['--batch', '--verify', '/tmp/jdk.tar.gz.sig', '/tmp/jdk.tar.gz'],
expect.objectContaining({silent: true})
);
});
});
});
});
7 changes: 7 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ inputs:
description: 'Set this option if you want the action to check for the latest available version that satisfies the version spec'
required: false
default: false
verify-signature:
description: 'Verify downloaded Java package signatures when supported by the selected distribution'
required: false
default: false
verify-signature-public-key:
description: 'ASCII-armored GPG public key used to verify the downloaded package signature. Overrides the default bundled key for the selected distribution.'
required: false
server-id:
description: 'ID of the distributionManagement repository in the pom.xml
file. Default is `github`'
Expand Down
32 changes: 30 additions & 2 deletions dist/cleanup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52241,7 +52241,7 @@ else {
"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = exports.INPUT_MVN_TOOLCHAIN_VENDOR = exports.INPUT_MVN_TOOLCHAIN_ID = exports.MVN_TOOLCHAINS_FILE = exports.MVN_SETTINGS_FILE = exports.M2_DIR = exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE_DEPENDENCY_PATH = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION_FILE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0;
exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = exports.INPUT_MVN_TOOLCHAIN_VENDOR = exports.INPUT_MVN_TOOLCHAIN_ID = exports.MVN_TOOLCHAINS_FILE = exports.MVN_SETTINGS_FILE = exports.M2_DIR = exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE_DEPENDENCY_PATH = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = exports.INPUT_VERIFY_SIGNATURE = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION_FILE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0;
exports.MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home';
exports.INPUT_JAVA_VERSION = 'java-version';
exports.INPUT_JAVA_VERSION_FILE = 'java-version-file';
Expand All @@ -52250,6 +52250,8 @@ exports.INPUT_JAVA_PACKAGE = 'java-package';
exports.INPUT_DISTRIBUTION = 'distribution';
exports.INPUT_JDK_FILE = 'jdkFile';
exports.INPUT_CHECK_LATEST = 'check-latest';
exports.INPUT_VERIFY_SIGNATURE = 'verify-signature';
exports.INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key';
exports.INPUT_SERVER_ID = 'server-id';
exports.INPUT_SERVER_USERNAME = 'server-username';
exports.INPUT_SERVER_PASSWORD = 'server-password';
Expand Down Expand Up @@ -52311,11 +52313,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.deleteKey = exports.importKey = exports.PRIVATE_KEY_FILE = void 0;
exports.verifyPackageSignature = exports.deleteKey = exports.importKey = exports.PRIVATE_KEY_FILE = void 0;
const fs = __importStar(__nccwpck_require__(79896));
const path = __importStar(__nccwpck_require__(16928));
const io = __importStar(__nccwpck_require__(94994));
const exec = __importStar(__nccwpck_require__(95236));
const tc = __importStar(__nccwpck_require__(33472));
const util = __importStar(__nccwpck_require__(54527));
exports.PRIVATE_KEY_FILE = path.join(util.getTempDir(), 'private-key.asc');
const PRIVATE_KEY_FINGERPRINT_REGEX = /\w{40}/;
Expand Down Expand Up @@ -52355,6 +52358,31 @@ function deleteKey(keyFingerprint) {
});
}
exports.deleteKey = deleteKey;
function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) {
return __awaiter(this, void 0, void 0, function* () {
const signaturePath = yield tc.downloadTool(signatureUrl);
let gpgHome;
try {
gpgHome = fs.mkdtempSync(path.join(util.getTempDir(), 'verify-signature-gpg-home-'));
}
catch (error) {
throw new Error(`Failed to create temporary GPG home directory for signature verification: ${error.message}`);
}
const env = Object.assign(Object.assign({}, process.env), { GNUPGHOME: gpgHome });
try {
const publicKeyFile = path.join(gpgHome, 'public-key.asc');
fs.writeFileSync(publicKeyFile, publicKeyContent, { encoding: 'utf-8' });
const options = { silent: true, env };
yield exec.exec('gpg', ['--batch', '--import', publicKeyFile], options);
yield exec.exec('gpg', ['--batch', '--verify', signaturePath, archivePath], options);
}
finally {
yield io.rmRF(signaturePath);
yield io.rmRF(gpgHome);
}
});
}
exports.verifyPackageSignature = verifyPackageSignature;


/***/ }),
Expand Down
Loading