Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
220 changes: 220 additions & 0 deletions .github/workflows/do-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
name: Do Release

on:
workflow_dispatch:
inputs:
release-version:
description: 'Version to be released (e.g. 0.1.0).'
required: true
next-snapshot-version:
description: 'Version to be set after the release - without the -SNAPSHOT suffix (e.g. 0.2.0).'
required: true

env:
GIT_AUTHOR_NAME: Flash Gordon
GIT_AUTHOR_EMAIL: <>
GIT_COMMITTER_NAME: Terminator the Kitty
GIT_COMMITTER_EMAIL: <>

jobs:
do-release:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.maven_release.outputs.TAG }}
version: ${{ steps.maven_release.outputs.VERSION }}
base-version: ${{ steps.maven_release.outputs.BASE_VERSION }}
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Verify release notes file exists
run: |
VERSION=${{ github.event.inputs.release-version }}
BASE_VERSION="${VERSION%%-*}"
NOTES_FILE="distribution/doc/release-notes/${BASE_VERSION}.md"
if [ ! -f "$NOTES_FILE" ]; then
echo "::error::Release notes file not found: $NOTES_FILE"
echo "Create it before triggering the release workflow."
exit 1
fi
echo "Found release notes: $NOTES_FILE"

- name: Set up Java and credentials
uses: actions/setup-java@v5
with:
java-version: 17
distribution: 'temurin'
server-id: ossrh
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE
cache: 'maven'

- name: Do the Maven release and prepare Linux assets
id: maven_release
run: |
VERSION=${{ github.event.inputs.release-version }}
BASE_VERSION="${VERSION%%-*}"
FULL_VERSION="jsignpdf-pades-$VERSION"
NEXT_VERSION=${{ github.event.inputs.next-snapshot-version }}-SNAPSHOT
TAG=jsignpdf-pades-${VERSION}
set -x
mvn --batch-mode clean install
mvn -P release --batch-mode "-Dtag=${TAG}" release:prepare \
"-DreleaseVersion=${VERSION}" \
"-DdevelopmentVersion=${NEXT_VERSION}"
mvn -P release --batch-mode release:perform \
-DstagingProgressTimeoutMinutes=30 -Dmaven.wagon.rto=7200000 \
-Dmaven.wagon.httpconnectionManager.maxPerRoute=60 -Dmaven.wagon.httpconnectionManager.maxTotal=100
cd distribution/target
ls -R
mkdir upload
mv *.zip upload/
cp "../doc/release-notes/${BASE_VERSION}.md" upload/README.md
echo "TAG=$TAG" >> $GITHUB_OUTPUT
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "BASE_VERSION=$BASE_VERSION" >> $GITHUB_OUTPUT
env:
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}

- name: Stage Linux release assets for the publish job
uses: actions/upload-artifact@v7
with:
name: jsignpdf-pades-linux-assets
path: distribution/target/upload/*
if-no-files-found: error
retention-days: 1

windows-installers:
needs: do-release
runs-on: windows-latest
steps:
- name: Checkout release tag
uses: actions/checkout@v6
with:
ref: ${{ needs.do-release.outputs.tag }}

- name: Set up JDK 17
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'

- name: Build with Maven
run: mvn -B package -DskipTests

- name: Prepare jpackage input
shell: bash
run: |
VERSION=${{ needs.do-release.outputs.version }}
mkdir staging
cp jsignpdf-pades/target/jsignpdf-pades-${VERSION}.jar staging/jsignpdf-pades.jar
cp validator/target/jsignpdf-pades-validator-${VERSION}.jar staging/jsignpdf-pades-validator.jar

- name: Create app-image
run: >
jpackage --type app-image
--input staging
--main-jar jsignpdf-pades.jar
--main-class com.github.intoolswetrust.jsignpdf.pades.Main
--name jsignpdf-pades
--dest jpackage-out
--app-version ${{ needs.do-release.outputs.version }}
--win-console
--java-options "--add-exports jdk.crypto.cryptoki/sun.security.pkcs11=ALL-UNNAMED"
--java-options "--add-exports jdk.crypto.cryptoki/sun.security.pkcs11.wrapper=ALL-UNNAMED"
--java-options "--add-exports java.base/sun.security.action=ALL-UNNAMED"
--java-options "--add-exports java.base/sun.security.rsa=ALL-UNNAMED"
--java-options "--add-opens java.base/java.security=ALL-UNNAMED"
--java-options "--add-opens java.base/sun.security.util=ALL-UNNAMED"
--add-launcher jsignpdf-pades-validator=distribution/jpackage/validator-launcher.properties

- name: Create MSI installer
run: >
jpackage --type msi
--app-image jpackage-out/jsignpdf-pades
--name jsignpdf-pades
--app-version ${{ needs.do-release.outputs.version }}
--dest windows-out

- name: Create EXE installer
run: >
jpackage --type exe
--app-image jpackage-out/jsignpdf-pades
--name jsignpdf-pades
--app-version ${{ needs.do-release.outputs.version }}
--dest windows-out

- name: Stage Windows installers for the publish job
uses: actions/upload-artifact@v7
with:
name: jsignpdf-pades-windows-assets
path: windows-out/*
if-no-files-found: error
retention-days: 1

publish-release:
needs: [do-release, windows-installers]
runs-on: ubuntu-latest
permissions:
contents: write
env:
TAG: ${{ needs.do-release.outputs.tag }}
VERSION: ${{ needs.do-release.outputs.version }}
BASE_VERSION: ${{ needs.do-release.outputs.base-version }}
FULL_VERSION: jsignpdf-pades-${{ needs.do-release.outputs.version }}
steps:
- name: Checkout release tag
uses: actions/checkout@v6
with:
ref: ${{ needs.do-release.outputs.tag }}

- name: Download Linux assets
uses: actions/download-artifact@v8
with:
name: jsignpdf-pades-linux-assets
path: release-assets/jsignpdf-pades-${{ needs.do-release.outputs.version }}

- name: Download Windows assets
uses: actions/download-artifact@v8
with:
name: jsignpdf-pades-windows-assets
path: release-assets/jsignpdf-pades-${{ needs.do-release.outputs.version }}

- name: List staged assets
run: ls -R release-assets

- name: Create GitHub release with all assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
NOTES_FILE="distribution/doc/release-notes/${BASE_VERSION}.md"
if [ ! -f "$NOTES_FILE" ]; then
echo "::error::Release notes file not found at $NOTES_FILE"
exit 1
fi
if gh release view "$TAG" >/dev/null 2>&1; then
gh release delete "$TAG" --yes --cleanup-tag=false
fi
shopt -s nullglob
assets=(release-assets/"$FULL_VERSION"/*)
if [ ${#assets[@]} -eq 0 ]; then
echo "::error::No assets found under release-assets/$FULL_VERSION/"
exit 1
fi
prerelease_flag=()
shopt -s nocasematch
if [[ "$VERSION" == *ALPHA* || "$VERSION" == *BETA* || "$VERSION" == *RC* || "$VERSION" == *MILESTONE* ]]; then
prerelease_flag=(--prerelease)
fi
shopt -u nocasematch
gh release create "$TAG" \
--title "$TAG" \
--notes-file "$NOTES_FILE" \
"${prerelease_flag[@]}" \
"${assets[@]}"
82 changes: 0 additions & 82 deletions .github/workflows/win-installers.yaml

This file was deleted.

19 changes: 16 additions & 3 deletions distribution/bin/jsignpdf-pades-validator.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
#!/bin/bash
set -euo pipefail

DIRNAME=$(dirname "$(readlink -e "$0")")
DIR=$(cd "$DIRNAME" || exit 112; pwd)
# Follow symlinks until we get the real file.
SOURCE="$0"
while [ -L "$SOURCE" ]; do
DIR="$(cd -P "$(dirname "$SOURCE")" >/dev/null 2>&1 && pwd -P)"
TARGET="$(readlink "$SOURCE")"
case "$TARGET" in
/*) SOURCE="$TARGET" ;;
*) SOURCE="$DIR/$TARGET" ;;
esac
done
DIR="$(cd -P "$(dirname "$SOURCE")" >/dev/null 2>&1 && pwd -P)"

[ "$OSTYPE" = "cygwin" ] && DIR="$( cygpath -m "$DIR" )"
[ "${OSTYPE:-}" = "cygwin" ] && DIR="$( cygpath -m "$DIR" )"

JAVA_HOME="${JAVA_HOME-}"
JAVA_OPTS="${JAVA_OPTS-}"

JAVA=java
if [ -n "$JAVA_HOME" ]; then
Expand Down
19 changes: 16 additions & 3 deletions distribution/bin/jsignpdf-pades.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
#!/bin/bash
set -euo pipefail

DIRNAME=$(dirname "$(readlink -e "$0")")
DIR=$(cd "$DIRNAME" || exit 112; pwd)
# Follow symlinks until we get the real file.
SOURCE="$0"
while [ -L "$SOURCE" ]; do
DIR="$(cd -P "$(dirname "$SOURCE")" >/dev/null 2>&1 && pwd -P)"
TARGET="$(readlink "$SOURCE")"
case "$TARGET" in
/*) SOURCE="$TARGET" ;;
*) SOURCE="$DIR/$TARGET" ;;
esac
done
DIR="$(cd -P "$(dirname "$SOURCE")" >/dev/null 2>&1 && pwd -P)"

[ "$OSTYPE" = "cygwin" ] && DIR="$( cygpath -m "$DIR" )"
[ "${OSTYPE:-}" = "cygwin" ] && DIR="$( cygpath -m "$DIR" )"

JAVA_HOME="${JAVA_HOME-}"
JAVA_OPTS="${JAVA_OPTS-}"

JAVA=java
if [ -n "$JAVA_HOME" ]; then
Expand Down
39 changes: 39 additions & 0 deletions distribution/doc/release-notes/0.1.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Version 0.1.0

First public release of **jsignpdf-pades** — a Java CLI for PAdES PDF signing and validation, built on the EU DSS library and Apache PDFBox.

## Modules

- `jsignpdf-pades` — signing CLI and library
- `jsignpdf-pades-validator` — validation CLI and library
- `jsignpdf-pades-common` — shared trust configuration

## Signing

- PAdES levels: BASELINE_B, BASELINE_T, BASELINE_LT, BASELINE_LTA
- Keystores: PKCS#12, JKS, PKCS#11 (smart cards / HSM via `jsign-pkcs11`)
- Digest algorithms: SHA-1, SHA-256, SHA-384, SHA-512, RIPEMD-160
- DocMDP certification levels (4 levels) and counter-signatures (append mode)
- Visible signatures: text with placeholders, custom fonts, background image, image-only mode, blank-page insertion, configurable rectangle and page
- Timestamp Authority (TSA) support with Basic Auth, mutual TLS, policy OID, and configurable hash algorithm
- PDF encryption (encrypt-before-sign) with owner/user passwords and granular permission flags
- Signing of password-protected PDFs

## Validation

- ETSI reports in TEXT, XML, ETSI TS 119 102-2, and JSON formats
- Quiet mode with exit codes (0/1/2) suitable for CI/CD
- Optional offline mode that skips online OCSP/CRL checks

## Trust configuration

- EU List of Trusted Lists (LOTL) with optional custom LOTL URLs
- Trusted certificate files and URLs (repeatable)
- Custom truststores (JKS / PKCS#12)
- OCSP and CRL revocation checking with AIA chain building

## Distribution

- Cross-platform ZIP with launcher scripts for Linux/macOS and Windows
- Windows MSI and EXE installers built via `jpackage`
- Java 17 or later required