Automated SIGN+ Package Installation and Configuration

Configure your Azure DevOps pipeline to install and configure SIGN+ automatically on every run, without manual agent setup.

Overview

Previously, you had to manually install and configure the SIGN+ package on each build agent before any signing job could run. This manual setup created extra work whenever you added new agents, scaled your infrastructure, or moved pipelines to a new pool.

Starting with version 2026.3.0, you can configure your Azure DevOps pipeline to install and configure SIGN+ automatically every time it runs without touching the agent manually. The pipeline 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 build agent. Choose the approach that best fits your team's setup.

Note: The examples in this topic demonstrate signing a Java JAR file with jarsigner on a Linux agent. 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 Secret variables in Azure DevOps never put credentials directly in pipeline files.
  • The name of your SIGN+ signing policy. Your AppViewX administrator can provide this.
  • The Linux utilities jq and unzip available on your build agent, or permission to install them during the pipeline run.

How automated installation works

The automated installation follows the same steps every time. Understanding these steps helps you troubleshoot issues and customize the process for your team.

  1. Get the SIGN+ package. The pipeline either reads the package from the repository, downloads it from the AppViewX API, or downloads it using a secure OAuth token. The exact method depends on the approach you choose.
  2. Check whether SIGN+ is already installed. If the package is already extracted and installed at the configured work directory, the pipeline skips extraction and installation. This saves time on repeated pipeline runs.
  3. Install SIGN+. If this is the first run, the pipeline extracts the package, sets the correct file permissions, and runs the SIGN+ Installer.
  4. Read the signing commands. After installation, SIGN+ generates a JSON configuration file that contains the exact signing and verification commands for your policy. The pipeline reads this file to get the correct command to run.
  5. Sign and verify your artifact. The pipeline runs the signing command, then verifies that the signature was applied correctly.

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 pipeline reads it from there no internet connection to the AppViewX server is needed at pipeline run time.

This is the recommended approach for most teams because it:

  • Keeps pipeline run times short no large file download happens on every build.
  • Removes the runtime dependency on the AppViewX server being reachable.
  • Works reliably even during network disruptions.
Tip: If your organization does not allow large binary files in Git, store the SIGN+ package in a secure artifact storage service (such as Azure Artifacts) and update the SIGN_PLUS_ZIP variable to point to that location.

Repository structure

Your repository must contain these files:

your-repository/
├── azure-pipelines.yml
├── scripts/
│   └── sign-with-signplus.sh
└── <YourPolicyName>_SIGN+_Package.zip   ← SIGN+ package committed here

Step-by-step setup

  1. Download the SIGN+ package for Linux from the AppViewX portal.
  2. Commit the downloaded .zip file to the root of your repository (or a known path).
  3. Create scripts/sign-with-signplus.sh in your repository with the script content shown in The Signing Script.
  4. Copy the pipeline YAML below into your azure-pipelines.yml file. Replace <YourPolicyName>_SIGN+_Package.zip with your actual zip file name, and update SIGNPLUS_POLICY inside the signing script to match your policy name.
  5. In Azure DevOps, create two pipeline variables:
    • SIGNPLUS_CLIENTID your OAuth client ID.
    • SIGNPLUS_CLIENTSECRET your OAuth client secret. Mark this variable as Secret so its value is hidden in logs.
  6. Push to main or master to start a build.

Pipeline configuration azure-pipelines.yml

trigger:
  - main
  - master

pr:
  - main
  - master

stages:
  # Stage 1: Build
  - stage: Build
    displayName: Build Java
    jobs:
      - job: MavenBuild
        displayName: Maven package
        pool:
          name: Default
        steps:
          - checkout: self
            displayName: Checkout source

          - script: mvn -B clean test package
            displayName: Maven clean test package
            workingDirectory: sample-java-app

          - task: PublishPipelineArtifact@1
            displayName: Publish unsigned JAR
            inputs:
              targetPath: sample-java-app/target/sample-java-app-1.0.0.jar
              artifact: unsigned-jar
              publishLocation: pipeline

  # Stage 2: Sign
  - stage: Sign
    displayName: Sign JAR with SIGN+
    dependsOn: Build
    jobs:
      - job: SignJar
        displayName: Install SIGN+ and sign
        pool:
          name: Default
        steps:
          - checkout: self
            displayName: Checkout source and SIGN+ package

          - task: DownloadPipelineArtifact@2
            displayName: Download unsigned JAR
            inputs:
              buildType: current
              artifactName: unsigned-jar
              targetPath: $(Pipeline.Workspace)/unsigned-jar

          - script: |
              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
            displayName: Ensure jq and unzip

          - script: |
              set -euo pipefail
              chmod +x scripts/sign-with-signplus.sh

              export SIGN_PLUS_ZIP="$(Build.SourcesDirectory)/<YourPolicyName>_SIGN+_Package.zip"
              export INPUT_JAR="$(Pipeline.Workspace)/unsigned-jar/sample-java-app-1.0.0.jar"
              export OUTPUT_JAR="$(Pipeline.Workspace)/signed/sample-java-app-1.0.0_signed.jar"
              export SIGN_PLUS_WORK_DIR="$(Agent.TempDirectory)/signplus"

              mkdir -p "$(Pipeline.Workspace)/signed"
              scripts/sign-with-signplus.sh
            displayName: SIGN+ install, sign, verify
            env:
              SIGNPLUS_CLIENTID: $(SIGNPLUS_CLIENTID)
              SIGNPLUS_CLIENTSECRET: $(SIGNPLUS_CLIENTSECRET)

          - task: PublishPipelineArtifact@1
            displayName: Publish signed JAR
            inputs:
              targetPath: $(Pipeline.Workspace)/signed/sample-java-app-1.0.0_signed.jar
              artifact: signed-jar
              publishLocation: pipeline

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. The script reads the correct commands directly from the JSON configuration file that SIGN+ generates after installation.

Required environment variables

Variable Description
SIGNPLUS_CLIENTID Your OAuth client ID from AppViewX.
SIGNPLUS_CLIENTSECRET Your OAuth client secret. Mark as Secret in Azure DevOps.
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 pipeline runs. Defaults to ~/signplus-package. Reusing the same directory avoids reinstalling SIGN+ on every run.
#!/usr/bin/env bash
# Installs SIGN+ (idempotent) and signs/verifies a single JAR using jarsigner.
set -euo pipefail

# Validate required variables
if [[ "${SIGNPLUS_CLIENTID:-}" == '$(SIGNPLUS_CLIENTID)' || \
      "${SIGNPLUS_CLIENTSECRET:-}" == '$(SIGNPLUS_CLIENTSECRET)' ]]; then
  echo "[ERROR] Pipeline variables were not expanded."
  echo "        Define SIGNPLUS_CLIENTID and SIGNPLUS_CLIENTSECRET in Azure DevOps."
  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}"

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

# Read signing commands from the generated JSON file
JSON_FILE="$SIGN_PLUS_PACKAGE_DIR/README_${SIGNPLUS_POLICY}.json"
if [[ ! -f "$JSON_FILE" ]]; then
  echo "[ERROR] JSON config not found: $JSON_FILE"
  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"

echo "Signed artifact ready: $OUTPUT_JAR"

Approach 2: Download the SIGN+ package at runtime

CAUTION: The SIGN+ package is a large file. Downloading it from the AppViewX server on every pipeline run adds time to each build and creates a dependency on the AppViewX server being reachable. Use this approach only when storing the package in the repository or on the agent is not possible.

The download script caches the package after the first download. Subsequent runs on the same agent reuse the cached file and skip the download.

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 your AppViewX environment has multiple policies and you need to target a specific one.

Variant A Basic package download script

#!/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="{\"payload\":{\"packageType\":\"${PACKAGE_TYPE}\"}}"

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 script

Use this variant when you need a package pre-configured for a specific signing policy and certificate thumbprint. Ask your AppViewX administrator for the certificate thumbprint value for your policy.

#!/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 build agent. 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.

Pipeline configuration azure-pipelines.yml (Approach 2)

trigger:
  - main

variables:
  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>"

stages:
  # Stage 1: Build
  - stage: Build
    displayName: Build Java
    jobs:
      - job: MavenBuild
        pool:
          name: Default
        steps:
          - checkout: self
          - script: mvn -B clean test package
            workingDirectory: sample-java-app
          - task: PublishPipelineArtifact@1
            inputs:
              targetPath: sample-java-app/target/sample-java-app-1.0.0.jar
              artifact: unsigned-jar
              publishLocation: pipeline

  # Stage 2: Sign
  - stage: Sign
    displayName: Sign JAR (API download)
    dependsOn: Build
    jobs:
      - job: SignJar
        pool:
          name: Default
        steps:
          - checkout: self

          - task: DownloadPipelineArtifact@2
            inputs:
              buildType: current
              artifactName: unsigned-jar
              targetPath: $(Pipeline.Workspace)/unsigned-jar

          - script: |
              set -e
              if ! command -v jq >/dev/null 2>&1; then
                sudo apt-get update -qq && sudo apt-get install -y -qq jq unzip curl
              fi
            displayName: Ensure jq, unzip, curl

          - script: |
              set -euo pipefail
              chmod +x scripts/download-signplus-api.sh scripts/sign-with-signplus.sh

              export OUTPUT_FILE="$(Agent.TempDirectory)/signplus_package.zip"
              scripts/download-signplus-api.sh

              echo "##vso[task.setvariable variable=SIGN_PLUS_ZIP]${OUTPUT_FILE}"
            displayName: Download SIGN+ package
            env:
              SIGNPLUS_USERNAME: $(SIGNPLUS_USERNAME)
              SIGNPLUS_PASSWORD: $(SIGNPLUS_PASSWORD)
              SIGNPLUS_API_HOST: $(SIGNPLUS_API_HOST)
              SIGNPLUS_API_PORT: $(SIGNPLUS_API_PORT)
              SIGNPLUS_PACKAGE_TYPE: $(SIGNPLUS_PACKAGE_TYPE)

          - script: |
              set -euo pipefail
              export INPUT_JAR="$(Pipeline.Workspace)/unsigned-jar/sample-java-app-1.0.0.jar"
              export OUTPUT_JAR="$(Pipeline.Workspace)/signed/sample-java-app-1.0.0_signed.jar"
              export SIGN_PLUS_WORK_DIR="$(Agent.TempDirectory)/signplus"
              mkdir -p "$(Pipeline.Workspace)/signed"
              scripts/sign-with-signplus.sh
            displayName: Install SIGN+, sign and verify
            env:
              SIGNPLUS_CLIENTID: $(SIGNPLUS_USERNAME)
              SIGNPLUS_CLIENTSECRET: $(SIGNPLUS_PASSWORD)
              SIGN_PLUS_ZIP: $(SIGN_PLUS_ZIP)

          - task: PublishPipelineArtifact@1
            inputs:
              targetPath: $(Pipeline.Workspace)/signed/sample-java-app-1.0.0_signed.jar
              artifact: signed-jar
              publishLocation: pipeline

Step-by-step setup (Approach 2)

  1. Create scripts/download-signplus-api.sh using Variant A or Variant B.
  2. Create scripts/sign-with-signplus.sh using the content in The Signing Script.
  3. Copy the pipeline YAML above and update:
    • SIGNPLUS_API_HOST your AppViewX server hostname or IP.
    • SIGNPLUS_API_PORT the API port (typically 31443).
    • SIGNPLUS_POLICY_NAME your signing policy name.
  4. Create Secret pipeline variables in Azure DevOps:
    • SIGNPLUS_USERNAME your AppViewX username.
    • SIGNPLUS_PASSWORD your AppViewX password. Mark as Secret.
  5. Push to trigger the pipeline.

Approach 3: Use an OAuth service account token

Use this approach when your organization authenticates with AppViewX using OAuth service accounts. The pipeline 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 with your service account credentials 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')
Important: Store the client ID and client secret as Secret pipeline variables in Azure DevOps. Never put them directly in pipeline files or scripts.

Step 2 Download the SIGN+ package using the token

Pass the token in the Authorization header when calling the download API:

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.

Note: Token lifetime depends on your AppViewX configuration. If tokens expire quickly, request a new token at the start of each pipeline run.

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 agent.
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.

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 for future runs. Network access to the AppViewX server from the build agent.
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 pipeline 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_POLICY in the signing script to match the exact policy name configured in AppViewX for your environment.
  • Authentication type: Select oAuth or basicAuth based on how your AppViewX environment is configured.
  • Signing tool: The examples use jarsigner for Java JAR files. For other artifact types such as Android APK files, Windows executables, or container images, read the corresponding key from the generated JSON file and use the same placeholder substitution pattern.
  • Platform: The scripts in this topic run on Linux using Bash. For Windows agents, use PowerShell with ConvertFrom-Json to read the JSON file and apply the same placeholder substitution.
  • TLS certificate verification: The download scripts use the -k flag in curl to skip TLS verification. This is acceptable for internal lab environments with self-signed certificates. For production environments, replace -k with --cacert /path/to/your/ca-bundle.crt to verify the AppViewX server certificate.
  • Secret management: Always mark credentials (SIGNPLUS_CLIENTSECRET, SIGNPLUS_PASSWORD) as Secret variables in Azure DevOps so their values are masked in pipeline logs and not accessible to other jobs.