Automated SIGN+ Package Installation and Configuration
Configure your GitHub Actions workflow to install and configure SIGN+ automatically on every run, without manual runner setup.
Overview
Previously, you had to manually install and configure the SIGN+ package on each runner before any signing job could run. This manual setup created extra work whenever you added new runners, scaled your infrastructure, or moved workflows to a new environment.
Starting with version 2026.3.0, you can configure your GitHub Actions workflow to install and configure SIGN+ automatically every time it runs without touching the runner manually. The workflow detects whether SIGN+ is already installed; if it is, installation is skipped and the signing step starts immediately.
This topic covers three approaches for getting the SIGN+ package onto your runner. Choose the approach that best fits your team's setup.
jarsigner on a Linux self-hosted runner. Use the generated JSON
configuration file (described in Understanding the JSON Configuration File) to retrieve the correct
commands for other file types such as Android APK, Windows PE, XML, or container
images.Prerequisites
Make sure you have the following before you set up any of the three approaches:
- An active AppViewX account with a configured code-signing policy.
- Your OAuth client ID and client secret (or username and password for basic authentication). Store these as encrypted Secrets in your GitHub repository never put credentials directly in workflow files.
- The name of your SIGN+ signing policy. Your AppViewX administrator can provide this.
- A self-hosted Linux runner with
jq,unzip, Java, and Maven available (or permission to install them during the workflow run).
Where to store secrets in GitHub
In your GitHub repository, go to . Add your credentials under the Secrets tab so their values are always masked in workflow logs.
How automated installation works
The automated installation follows the same steps every time:
- Get the SIGN+ package. The workflow either reads the package from the repository, downloads it from the AppViewX API, or downloads it using a secure OAuth token.
- Check whether SIGN+ is already installed. If the package is already installed at the configured work directory, the workflow skips installation. This saves time on repeated runs.
- Install SIGN+. If this is the first run, the workflow extracts the package, sets the correct permissions, and runs the SIGN+ Installer.
- Read the signing commands. SIGN+ generates a JSON configuration file with the exact signing and verification commands for your policy. The workflow reads this file.
- Sign and verify your artifact. The workflow runs the signing command, then verifies the signature.
Approach 1: Store the SIGN+ package in your repository (Recommended)
In this approach, you commit the SIGN+ package file directly to your Git repository. The workflow reads it from there no internet connection to the AppViewX server is needed at workflow run time.
This is the recommended approach for most teams because it:
- Keeps workflow run times short no large file download on every build.
- Removes the runtime dependency on the AppViewX server being reachable.
- Works reliably even during network disruptions.
SIGN_PLUS_ZIP variable to point to that location.Repository structure
your-repository/
├── .github/
│ └── workflows/
│ └── main_ci-sign.yml ← workflow file
├── scripts/
│ └── sign-with-signplus.sh ← signing script
├── src/
├── pom.xml
└── <YourPolicyName>_SIGN+_Package.zip ← SIGN+ package committed here
Step-by-step setup
- Download the SIGN+ package for Linux from the AppViewX portal.
- Commit the downloaded .zip file to the root of your repository.
- Create scripts/sign-with-signplus.sh using the content in The Signing Script.
- Create .github/workflows/main_ci-sign.yml using the workflow YAML
below. Replace
<YourPolicyName>_SIGN+_Package.zipwith your actual zip file name, and updateSIGNPLUS_POLICYin the signing script to match your policy name. - Add two repository secrets in GitHub at :
SIGNPLUS_CLIENTIDyour OAuth client ID.SIGNPLUS_CLIENTSECRETyour OAuth client secret.
- Push to
mainormasterto start a workflow run.
Workflow file .github/workflows/main_ci-sign.yml
# GitHub Secrets required (Settings → Security and quality → Secrets and variables → Actions):
# SIGNPLUS_CLIENTID OAuth client ID
# SIGNPLUS_CLIENTSECRET OAuth client secret
#
# Runner: self-hosted Linux. Requires Java, Maven, jq, unzip.
name: CI Sign (OAuth zip)
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:
jobs:
build:
name: Build Java
runs-on: Linux
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Verify Java and Maven
run: |
set -e
java -version
mvn -version
- name: Maven clean test package
run: mvn -B clean test package
- name: Upload unsigned JAR
uses: actions/upload-artifact@v4
with:
name: unsigned-jar
path: target/simple-poc-1.0.0.jar
if-no-files-found: error
sign:
name: Sign JAR with SIGN+
runs-on: Linux
needs: build
steps:
- name: Checkout scripts and SIGN+ zip
uses: actions/checkout@v4
- name: Download unsigned JAR
uses: actions/download-artifact@v4
with:
name: unsigned-jar
path: ${{ runner.temp }}/unsigned-jar
- name: Ensure jq and unzip
run: |
set -e
if ! command -v jq >/dev/null 2>&1; then
sudo apt-get update -qq
sudo apt-get install -y -qq jq unzip
fi
jq --version
- name: SIGN+ install, sign, verify
env:
SIGNPLUS_CLIENTID: ${{ secrets.SIGNPLUS_CLIENTID }}
SIGNPLUS_CLIENTSECRET: ${{ secrets.SIGNPLUS_CLIENTSECRET }}
SIGN_PLUS_ZIP: ${{ github.workspace }}/<YourPolicyName>_SIGN+_Package.zip
INPUT_JAR: ${{ runner.temp }}/unsigned-jar/simple-poc-1.0.0.jar
OUTPUT_JAR: ${{ runner.temp }}/signed/simple-poc-1.0.0_signed.jar
SIGN_PLUS_WORK_DIR: ${{ runner.temp }}/signplus
run: |
set -euo pipefail
chmod +x scripts/sign-with-signplus.sh
mkdir -p "${{ runner.temp }}/signed"
scripts/sign-with-signplus.sh
- name: Upload signed JAR
uses: actions/upload-artifact@v4
with:
name: signed-jar
path: ${{ runner.temp }}/signed/simple-poc-1.0.0_signed.jar
if-no-files-found: error
The signing script
Create scripts/sign-with-signplus.sh in your repository. This script installs SIGN+ (if not already installed) and runs the signing and verification commands.
Required environment variables
| Variable | Description |
|---|---|
SIGNPLUS_CLIENTID |
Your OAuth client ID from AppViewX. |
SIGNPLUS_CLIENTSECRET |
Your OAuth client secret. Store as a GitHub repository secret. |
SIGN_PLUS_ZIP |
Full path to the SIGN+ package .zip file. |
INPUT_JAR |
Full path to the unsigned artifact to sign. |
OUTPUT_JAR |
Full path where the signed artifact is saved. |
SIGN_PLUS_WORK_DIR |
(Optional) Directory where SIGN+ is installed and cached between runs. Defaults
to ~/signplus-package. |
#!/usr/bin/env bash
# Installs SIGN+ (idempotent) and signs/verifies a single JAR using jarsigner.
set -euo pipefail
if [[ -z "${SIGNPLUS_CLIENTID:-}" || -z "${SIGNPLUS_CLIENTSECRET:-}" ]]; then
echo "[ERROR] SIGNPLUS_CLIENTID and/or SIGNPLUS_CLIENTSECRET is empty."
exit 1
fi
: "${SIGNPLUS_CLIENTID:?SIGNPLUS_CLIENTID is required}"
: "${SIGNPLUS_CLIENTSECRET:?SIGNPLUS_CLIENTSECRET is required}"
: "${SIGN_PLUS_ZIP:?SIGN_PLUS_ZIP is required}"
: "${INPUT_JAR:?INPUT_JAR is required}"
: "${OUTPUT_JAR:?OUTPUT_JAR is required}"
if [[ ! -f "$SIGN_PLUS_ZIP" ]]; then
echo "[ERROR] SIGN+ zip not found: $SIGN_PLUS_ZIP"; exit 1
fi
if [[ ! -f "$INPUT_JAR" ]]; then
echo "[ERROR] Input JAR not found: $INPUT_JAR"; exit 1
fi
SIGNPLUS_AUTHTYPE="oAuth"
SIGNPLUS_POLICY="AzurePolicy" # Update to match your policy name
WORK_DIR="${SIGN_PLUS_WORK_DIR:-$HOME/signplus-package}"
SIGN_PLUS_PACKAGE_DIR="$WORK_DIR/$(basename "$SIGN_PLUS_ZIP" .zip)"
mkdir -p "$WORK_DIR"
# Install SIGN+ (skipped if already installed)
if [[ -f "$SIGN_PLUS_PACKAGE_DIR/SIGN+_Installer" ]]; then
echo "[INFO] SIGN+ already installed — skipping."
else
echo "[STEP 1] Extracting SIGN+ package..."
unzip -o "$SIGN_PLUS_ZIP" -d "$SIGN_PLUS_PACKAGE_DIR"
echo "[STEP 2] Setting installer permissions..."
chmod +x "$SIGN_PLUS_PACKAGE_DIR/SIGN+_Installer"
echo "[STEP 3] Installing SIGN+..."
cd "$SIGN_PLUS_PACKAGE_DIR"
./SIGN+_Installer Install \
--authtype "$SIGNPLUS_AUTHTYPE" \
--username "$SIGNPLUS_CLIENTID" \
--password "$SIGNPLUS_CLIENTSECRET" \
--overwriteInstallation
fi
cd "$SIGN_PLUS_PACKAGE_DIR"
echo "[STEP 4] Current SIGN+ configuration:"
./SIGN+_Installer Print
JSON_FILE="$SIGN_PLUS_PACKAGE_DIR/README_${SIGNPLUS_POLICY}.json"
if [[ ! -f "$JSON_FILE" ]]; then
echo "[ERROR] JSON config not found: $JSON_FILE"
find "$SIGN_PLUS_PACKAGE_DIR" -maxdepth 2 -type f -print
exit 1
fi
echo "[STEP 5] Reading signing commands from $JSON_FILE..."
RAW_SIGN_CMD=$(jq -r '.pkcs11.sign.jarsigner' "$JSON_FILE")
RAW_VERIFY_CMD=$(jq -r '.pkcs11.verify.jarsigner' "$JSON_FILE")
if [[ "$RAW_SIGN_CMD" == "null" || -z "$RAW_SIGN_CMD" ]]; then
echo "[ERROR] jarsigner sign command not found in JSON"; exit 1
fi
mkdir -p "$(dirname "$OUTPUT_JAR")"
FINAL_SIGN_CMD="${RAW_SIGN_CMD//<input_file_path>/$INPUT_JAR}"
FINAL_SIGN_CMD="${FINAL_SIGN_CMD//<output_file_path>/$OUTPUT_JAR}"
FINAL_VERIFY_CMD="${RAW_VERIFY_CMD//<signed_file_path>/$OUTPUT_JAR}"
echo "[STEP 6] Signing artifact..."
eval "$FINAL_SIGN_CMD"
echo "[STEP 7] Verifying signature..."
eval "$FINAL_VERIFY_CMD"
if [[ ! -f "$OUTPUT_JAR" ]]; then
echo "[ERROR] Signed artifact was not created: $OUTPUT_JAR"; exit 1
fi
echo "Signed artifact ready: $OUTPUT_JAR"
Approach 2: Download the SIGN+ package at runtime
The download script caches the package after the first download. Subsequent runs on the same runner reuse the cached file.
Two variants are available:
- Variant A Basic download: Downloads the default SIGN+ package for your operating system. Use this when your signing policy does not require a specific certificate thumbprint at download time.
- Variant B Policy-specific download: Downloads a package pre-configured for a specific signing policy and certificate. Use this when you need to target a specific policy in AppViewX.
Repository structure for Approach 2
your-repository/
├── .github/
│ └── workflows/
│ └── ci-sign.yml
├── scripts/
│ ├── download-signplus-api.sh ← downloads the SIGN+ package
│ └── sign-with-signplus.sh ← installs SIGN+ and signs the artifact
├── src/
└── pom.xml
Variant A Basic package download workflow (basic-package-curl_ci-sign.yml)
# GitHub Secrets required:
# SIGNPLUS_USERNAME
# SIGNPLUS_PASSWORD
#
# Runner: self-hosted Linux. Requires Java, Maven, jq, unzip, curl.
# Network access to SIGNPLUS_API_HOST is required.
name: CI Sign (basic API package)
on:
push:
branches: [main, master]
workflow_dispatch:
env:
SIGNPLUS_API_HOST: "<your-appviewx-server>"
SIGNPLUS_API_PORT: "<api-port>"
SIGNPLUS_PACKAGE_TYPE: Linux
SIGNPLUS_AUTH_TYPE: "oAuth"
SIGNPLUS_POLICY_NAME: "<your-policy-name>"
jobs:
build:
name: Build Java
runs-on: Linux
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Maven clean test package
run: mvn -B clean test package
- name: Upload unsigned JAR
uses: actions/upload-artifact@v4
with:
name: unsigned-jar
path: target/simple-poc-1.0.0.jar
if-no-files-found: error
sign:
name: Sign JAR (API download)
runs-on: Linux
needs: build
steps:
- name: Checkout scripts
uses: actions/checkout@v4
- name: Download unsigned JAR
uses: actions/download-artifact@v4
with:
name: unsigned-jar
path: ${{ runner.temp }}/unsigned-jar
- name: Ensure jq, unzip, curl
run: |
set -e
if ! command -v jq >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then
sudo apt-get update -qq
sudo apt-get install -y -qq jq unzip curl
fi
- name: Download SIGN+ package via API
env:
SIGNPLUS_USERNAME: ${{ secrets.SIGNPLUS_USERNAME }}
SIGNPLUS_PASSWORD: ${{ secrets.SIGNPLUS_PASSWORD }}
SIGNPLUS_API_HOST: ${{ env.SIGNPLUS_API_HOST }}
SIGNPLUS_API_PORT: ${{ env.SIGNPLUS_API_PORT }}
SIGNPLUS_PACKAGE_TYPE: ${{ env.SIGNPLUS_PACKAGE_TYPE }}
OUTPUT_FILE: ${{ runner.temp }}/signplus_package.zip
run: |
set -euo pipefail
chmod +x scripts/download-signplus-api.sh
scripts/download-signplus-api.sh
echo "SIGN_PLUS_ZIP=${OUTPUT_FILE}" >> "$GITHUB_ENV"
- name: Install SIGN+, sign and verify
env:
SIGNPLUS_CLIENTID: ${{ secrets.SIGNPLUS_USERNAME }}
SIGNPLUS_CLIENTSECRET: ${{ secrets.SIGNPLUS_PASSWORD }}
SIGNPLUS_POLICY_NAME: ${{ env.SIGNPLUS_POLICY_NAME }}
INPUT_JAR: ${{ runner.temp }}/unsigned-jar/simple-poc-1.0.0.jar
OUTPUT_JAR: ${{ runner.temp }}/signed/simple-poc-1.0.0_signed.jar
SIGN_PLUS_WORK_DIR: ${{ runner.temp }}/signplus
run: |
set -euo pipefail
mkdir -p "${{ runner.temp }}/signed"
chmod +x scripts/sign-with-signplus.sh
scripts/sign-with-signplus.sh
- name: Upload signed JAR
uses: actions/upload-artifact@v4
with:
name: signed-jar
path: ${{ runner.temp }}/signed/simple-poc-1.0.0_signed.jar
if-no-files-found: error
Variant A Download script (scripts/download-signplus-api.sh)
#!/usr/bin/env bash
# Downloads the SIGN+ package from the AppViewX API (basic, no policy filter).
set -euo pipefail
: "${SIGNPLUS_USERNAME:?SIGNPLUS_USERNAME is required}"
: "${SIGNPLUS_PASSWORD:?SIGNPLUS_PASSWORD is required}"
: "${SIGNPLUS_API_HOST:?SIGNPLUS_API_HOST is required}"
: "${SIGNPLUS_API_PORT:?SIGNPLUS_API_PORT is required}"
: "${OUTPUT_FILE:?OUTPUT_FILE is required}"
PACKAGE_TYPE="${SIGNPLUS_PACKAGE_TYPE:-Linux}"
API_URL="https://${SIGNPLUS_API_HOST}:${SIGNPLUS_API_PORT}/avxapi/code-signing-download-csp-pkcs11-dll-sign?gwsource=external"
# Skip if package is already cached and valid
if [[ -f "$OUTPUT_FILE" ]] && unzip -t "$OUTPUT_FILE" >/dev/null 2>&1; then
echo "[INFO] SIGN+ package already cached — skipping download."
exit 0
fi
mkdir -p "$(dirname "$OUTPUT_FILE")"
JSON_PAYLOAD=$(cat <<EOF
{
"payload": {
"packageType": "${PACKAGE_TYPE}"
}
}
EOF
)
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
HTTP_STATUS=$(curl -k --location "${API_URL}" \
--header "Content-Type: application/json" \
--header "username: ${SIGNPLUS_USERNAME}" \
--header "password: ${SIGNPLUS_PASSWORD}" \
--data "${JSON_PAYLOAD}" \
--output "${TMPFILE}" \
-w '%{http_code}')
if [[ "${HTTP_STATUS}" -eq 200 ]]; then
FILE_CONTENT=$(jq -r '.response.fileContent // empty' "${TMPFILE}")
if [[ -z "${FILE_CONTENT}" ]]; then
echo "[ERROR] Response did not contain .response.fileContent"
exit 1
fi
echo "${FILE_CONTENT}" | base64 -d > "${OUTPUT_FILE}"
unzip -t "${OUTPUT_FILE}" >/dev/null
echo "[INFO] Package downloaded and verified: ${OUTPUT_FILE}"
else
echo "[ERROR] HTTP ${HTTP_STATUS} — download failed."
exit 1
fi
Variant B Policy-specific download workflow (signplus-zip-curl_ci-sign.yml)
Use Variant B when you need a package pre-configured for a specific signing policy and
certificate thumbprint. Set SIGNPLUS_POLICY_VALUE as a repository variable
in GitHub at .
# GitHub Secrets required:
# SIGNPLUS_USERNAME
# SIGNPLUS_PASSWORD
#
# GitHub Variables (Settings → Secrets and variables → Actions → Variables tab):
# SIGNPLUS_POLICY_VALUE (optional — overrides the default below)
name: CI Sign (policy API package)
on:
push:
branches: [main, master]
workflow_dispatch:
env:
SIGNPLUS_API_HOST: "<your-appviewx-server>"
SIGNPLUS_API_PORT: "<api-port>"
SIGNPLUS_PAYLOAD_USER_NAME: "<appviewx-username>"
SIGNPLUS_PACKAGE_TYPE: Linux
SIGNPLUS_AUTH_TYPE: basicAuth
SIGNPLUS_POLICY_NAME: "<your-policy-name>"
SIGNPLUS_POLICY_VALUE_DEFAULT: "<commonName>=<serialNumber>"
jobs:
build:
name: Build Java
runs-on: Linux
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Maven clean test package
run: mvn -B clean test package
- name: Upload unsigned JAR
uses: actions/upload-artifact@v4
with:
name: unsigned-jar
path: target/simple-poc-1.0.0.jar
if-no-files-found: error
sign:
name: Sign JAR (policy API package)
runs-on: Linux
needs: build
steps:
- name: Checkout scripts
uses: actions/checkout@v4
- name: Download unsigned JAR
uses: actions/download-artifact@v4
with:
name: unsigned-jar
path: ${{ runner.temp }}/unsigned-jar
- name: Ensure jq, unzip, curl
run: |
set -e
if ! command -v jq >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then
sudo apt-get update -qq
sudo apt-get install -y -qq jq unzip curl
fi
- name: Download SIGN+ package via API
env:
SIGNPLUS_USERNAME: ${{ secrets.SIGNPLUS_USERNAME }}
SIGNPLUS_PASSWORD: ${{ secrets.SIGNPLUS_PASSWORD }}
SIGNPLUS_API_HOST: ${{ env.SIGNPLUS_API_HOST }}
SIGNPLUS_API_PORT: ${{ env.SIGNPLUS_API_PORT }}
SIGNPLUS_PAYLOAD_USER_NAME: ${{ env.SIGNPLUS_PAYLOAD_USER_NAME }}
SIGNPLUS_PACKAGE_TYPE: ${{ env.SIGNPLUS_PACKAGE_TYPE }}
SIGNPLUS_AUTH_TYPE: ${{ env.SIGNPLUS_AUTH_TYPE }}
SIGNPLUS_POLICY_NAME: ${{ env.SIGNPLUS_POLICY_NAME }}
SIGNPLUS_POLICY_VALUE: ${{ vars.SIGNPLUS_POLICY_VALUE || env.SIGNPLUS_POLICY_VALUE_DEFAULT }}
OUTPUT_FILE: ${{ runner.temp }}/signplus_package.zip
run: |
set -euo pipefail
chmod +x scripts/download-signplus-api.sh
scripts/download-signplus-api.sh
echo "SIGN_PLUS_ZIP=${OUTPUT_FILE}" >> "$GITHUB_ENV"
- name: Install SIGN+, sign and verify
env:
SIGNPLUS_CLIENTID: ${{ secrets.SIGNPLUS_USERNAME }}
SIGNPLUS_CLIENTSECRET: ${{ secrets.SIGNPLUS_PASSWORD }}
SIGNPLUS_POLICY_NAME: ${{ env.SIGNPLUS_POLICY_NAME }}
INPUT_JAR: ${{ runner.temp }}/unsigned-jar/simple-poc-1.0.0.jar
OUTPUT_JAR: ${{ runner.temp }}/signed/simple-poc-1.0.0_signed.jar
SIGN_PLUS_WORK_DIR: ${{ runner.temp }}/signplus
run: |
set -euo pipefail
mkdir -p "${{ runner.temp }}/signed"
chmod +x scripts/sign-with-signplus.sh
scripts/sign-with-signplus.sh
- name: Upload signed JAR
uses: actions/upload-artifact@v4
with:
name: signed-jar
path: ${{ runner.temp }}/signed/simple-poc-1.0.0_signed.jar
if-no-files-found: error
Variant B Policy-specific download script (scripts/download-signplus-api.sh)
#!/usr/bin/env bash
# Downloads the SIGN+ package for a specific signing policy.
set -euo pipefail
: "${SIGNPLUS_USERNAME:?}"
: "${SIGNPLUS_PASSWORD:?}"
: "${SIGNPLUS_API_HOST:?}"
: "${SIGNPLUS_API_PORT:?}"
: "${SIGNPLUS_POLICY_NAME:?}"
: "${SIGNPLUS_POLICY_VALUE:?}"
: "${OUTPUT_FILE:?}"
PAYLOAD_USER_NAME="${SIGNPLUS_PAYLOAD_USER_NAME:-$SIGNPLUS_USERNAME}"
PACKAGE_TYPE="${SIGNPLUS_PACKAGE_TYPE:-Linux}"
AUTH_TYPE="${SIGNPLUS_AUTH_TYPE:-basicAuth}"
API_URL="https://${SIGNPLUS_API_HOST}:${SIGNPLUS_API_PORT}/avxapi/code-signing-download-csp-pkcs11-dll-sign?gwsource=external"
if [[ -f "$OUTPUT_FILE" ]] && unzip -t "$OUTPUT_FILE" >/dev/null 2>&1; then
echo "[INFO] Package cached at $OUTPUT_FILE — skipping download."
exit 0
fi
mkdir -p "$(dirname "$OUTPUT_FILE")"
JSON_PAYLOAD=$(cat <<EOF
{
"payload": {
"userName": "${PAYLOAD_USER_NAME}",
"packageType": "${PACKAGE_TYPE}",
"authType": "${AUTH_TYPE}",
"signingPolicy": {
"${SIGNPLUS_POLICY_NAME}": "${SIGNPLUS_POLICY_VALUE}"
}
}
}
EOF
)
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
HTTP_STATUS=$(curl -k --location "${API_URL}" \
--header "Content-Type: application/json" \
--header "username: ${SIGNPLUS_USERNAME}" \
--header "password: ${SIGNPLUS_PASSWORD}" \
--data "${JSON_PAYLOAD}" \
--output "${TMPFILE}" \
-w '%{http_code}')
if [[ "${HTTP_STATUS}" -eq 200 ]]; then
FILE_CONTENT=$(jq -r '.response.fileContent // empty' "${TMPFILE}")
if [[ -z "${FILE_CONTENT}" ]]; then
echo "[ERROR] Response body missing .response.fileContent"; exit 1
fi
echo "${FILE_CONTENT}" | base64 -d > "${OUTPUT_FILE}"
unzip -t "${OUTPUT_FILE}" >/dev/null
echo "[INFO] Package downloaded and verified: ${OUTPUT_FILE}"
else
echo "[ERROR] HTTP ${HTTP_STATUS}"; exit 1
fi
API request payload reference
| Field | Required | Description |
|---|---|---|
packageType |
Yes | The operating system of your runner. Accepted values:
Linux or Windows. |
userName |
No | The AppViewX username to associate with the package. Defaults to
SIGNPLUS_USERNAME if omitted. |
authType |
Variant B only | The authentication type configured in AppViewX. Accepted values:
basicAuth or oAuth. |
signingPolicy |
Variant B only | A key-value pair that identifies the signing policy and certificate:
{ "<PolicyName>": "<username>=<certificateThumbprint>"
}. Ask your AppViewX administrator for the certificate
thumbprint. |
connectionType |
No | Set to Custom if signing requests must go through a load
balancer or custom URL rather than the default AppViewX server address. |
loadBalancerURL |
No | The full URL of your load balancer, for example
https://example.com:443. Include this when
connectionType is Custom. |
Step-by-step setup (Approach 2)
- Create scripts/download-signplus-api.sh using Variant A or Variant B.
- Create scripts/sign-with-signplus.sh using the content in The Signing Script.
- Create the workflow file and update
SIGNPLUS_API_HOST,SIGNPLUS_API_PORT, andSIGNPLUS_POLICY_NAME. - Add repository secrets in GitHub:
SIGNPLUS_USERNAMEyour AppViewX username.SIGNPLUS_PASSWORDyour AppViewX password.
SIGNPLUS_POLICY_VALUEas a repository variable under the Variables tab. - Push to trigger the workflow.
Approach 3: Use an OAuth service account token
Use this approach when your organization authenticates with AppViewX using OAuth service accounts. The workflow first retrieves a short-lived access token from AppViewX, then uses that token to download the SIGN+ package.
Step 1 Get a bearer token
A bearer token is a temporary access credential. Call the
acctmgmt_get_service_token API to receive one:
TOKEN_RESPONSE=$(curl -k --location \
"https://<your-appviewx-server>:<port>/avxapi/acctmgmt_get_service_token" \
--header "Content-Type: application/json" \
--data '{
"payload": {
"clientId": "<service-account-client-id>",
"clientSecret": "<service-account-client-secret>"
}
}')
BEARER_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.response.token')
Step 2 Download the SIGN+ package using the token
HTTP_STATUS=$(curl -k --location \
"https://<your-appviewx-server>:<port>/avxapi/code-signing-download-csp-pkcs11-dll-sign?gwsource=external" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${BEARER_TOKEN}" \
--data '{"payload":{"packageType":"Linux"}}' \
--output "${TMPFILE}" \
-w '%{http_code}')
if [[ "${HTTP_STATUS}" -eq 200 ]]; then
FILE_CONTENT=$(jq -r '.response.fileContent // empty' "${TMPFILE}")
echo "${FILE_CONTENT}" | base64 -d > "${OUTPUT_FILE}"
unzip -t "${OUTPUT_FILE}" >/dev/null
echo "[INFO] Package downloaded and verified."
fi
After you download the package, run the signing script described in The Signing Script.
Understanding the JSON configuration file
When SIGN+ installs, it generates a file named README_<PolicyName>.json in the installation directory. This file contains all the signing and verification commands for your policy pre-configured and ready to run.
Each command contains placeholder tokens that the signing script replaces at runtime:
| Placeholder | Replaced with |
|---|---|
<input_file_path> |
Path to the unsigned artifact. |
<output_file_path> |
Destination path for the signed artifact. |
<signed_file_path> |
Path to the signed artifact (used in verification commands). |
Supported signing tools
| Tool | JSON path | Typical use |
|---|---|---|
| jarsigner | .pkcs11.sign.jarsigner |
Java JAR files. |
| jsign | .pkcs11.sign.jsign |
Windows PE, MSI, JAR, and other formats. |
| apksigner | .pkcs11.sign.apksigner |
Android APK files. |
| xmlsectool | .pkcs11.sign.xmlsectool |
XML documents. |
| OpenSSL (dgst) | .pkcs11.sign.openssl_dgst |
Raw file signing. |
| OpenSSL (CMS) | .pkcs11.sign.openssl_cms |
CMS / PKCS#7 detached signatures. |
| osslsigncode | .pkcs11.sign.osslsigncode |
Windows Authenticode signing from a Linux runner. |
| cosign | .pkcs11.sign.cosign |
OCI container images and blobs. |
| Maven | .pkcs11.sign.maven_pom_snippet |
Maven build integration. |
| Gradle | .pkcs11.sign.gradle_snippet |
Gradle build integration. |
| esptool | .pkcs11.sign.esptool |
ESP32 / ESP8266 firmware signing. |
Choose the right approach
| Approach | Best for | Requires |
|---|---|---|
| 1 Bundle in repository | Most teams. Fastest, most reliable, no network dependency. | Permission to commit a binary file to Git (or access to an artifact store). |
| 2 Download via API | Teams that cannot store binaries in Git. First download is cached. | Network access to the AppViewX server from the runner. |
| 3 OAuth service token | Organizations that use OAuth service accounts for AppViewX authentication. | Network access to the AppViewX server and an OAuth service account with download permissions. |
Important notes and customization
The workflow files, scripts, and commands in this topic are reference implementations. Review and adapt them before using them in a production environment:
- Policy name: Update
SIGNPLUS_POLICYin the signing script to match the exact policy name configured in AppViewX for your environment. - Authentication type: Select
oAuthorbasicAuthbased on how your AppViewX environment is configured. - Signing tool: The examples use jarsigner for Java JAR files. For other artifact types, read the corresponding key from the generated JSON file and use the same placeholder substitution pattern.
- Platform: The scripts run on Linux using Bash. For Windows runners, use
PowerShell with
ConvertFrom-Jsonto read the JSON file. - TLS certificate verification: The download scripts use the
-kflag in curl to skip TLS verification. For production environments, replace-kwith--cacert /path/to/your/ca-bundle.crtto verify the AppViewX server certificate. - Secret management: Always store credentials as encrypted repository Secrets in GitHub so their values are masked in workflow logs. GitHub automatically redacts secret values from logs, but never print them intentionally.
