diff --git a/Dockerfile b/Dockerfile index 08ef7664..ea813367 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,6 @@ COPY scripts /scripts COPY pipeline /pipeline COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ #COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/ -COPY build/libs/*.jar app.jar ARG VERSION_TAG @@ -19,6 +18,10 @@ ENV DOCKER_ENABLE_SECURITY=false \ PGID=1000 \ UMASK=022 +# Create non-root user first +RUN addgroup -S stirlingpdfgroup && \ + adduser -S stirlingpdfuser -G stirlingpdfgroup + # JDK for app RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ @@ -31,8 +34,6 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et bash \ curl \ qpdf \ - shadow \ - su-exec \ openssl \ openssl-dev \ openjdk21-jre \ @@ -50,15 +51,16 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et # uno unoconv and HTML pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \ mv /usr/share/tessdata /usr/share/tessdata-original && \ - mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ fc-cache -f -v && \ - chmod +x /scripts/* && \ - chmod +x /scripts/init.sh && \ # User permissions - addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ - chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \ - chown stirlingpdfuser:stirlingpdfgroup /app.jar && \ - tesseract --list-langs + mkdir -p ${HOME} /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /scripts /usr/share/fonts/custom && \ + chown -R stirlingpdfuser:stirlingpdfgroup ${HOME} /configs /logs /customFiles /pipeline /scripts /usr/share/fonts/custom && \ + chmod -R 755 ${HOME} /configs /customFiles /pipeline /scripts /usr/share/fonts/custom && \ + tesseract --list-langs && \ + chmod -R 777 /logs + +COPY build/libs/*.jar app.jar +RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar EXPOSE 8080/tcp diff --git a/Dockerfile-fat b/Dockerfile-fat index d34c7daa..63d7a364 100644 --- a/Dockerfile-fat +++ b/Dockerfile-fat @@ -1,4 +1,4 @@ -# Build the application +# Build stage FROM gradle:8.11-jdk17 AS build # Set the working directory @@ -7,18 +7,20 @@ WORKDIR /app # Copy the entire project to the working directory COPY . . -# Build the application with DOCKER_ENABLE_SECURITY=false +# Build the application RUN DOCKER_ENABLE_SECURITY=true \ -./gradlew clean build + ./gradlew clean build # Main stage FROM alpine:3.20.3 -# Copy necessary files +# Create non-root user first +RUN addgroup -S stirlingpdfgroup && \ + adduser -S stirlingpdfuser -G stirlingpdfgroup + COPY scripts /scripts COPY pipeline /pipeline COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ -COPY --from=build /app/build/libs/*.jar app.jar ARG VERSION_TAG @@ -33,51 +35,45 @@ ENV DOCKER_ENABLE_SECURITY=false \ FAT_DOCKER=true \ INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false +# Create necessary directories with correct permissions +RUN mkdir -p ${HOME} /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /scripts /usr/share/fonts/custom && \ + chown -R stirlingpdfuser:stirlingpdfgroup ${HOME} /configs /logs /customFiles /pipeline /scripts /usr/share/fonts/custom && \ + chmod -R 755 ${HOME} /configs /logs /customFiles /pipeline /scripts /usr/share/fonts/custom -# JDK for app -RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ - echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ - echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \ +# JDK and other dependencies +RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories && \ + echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories && \ + echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \ apk upgrade --no-cache -a && \ apk add --no-cache \ - ca-certificates \ - tzdata \ - tini \ - bash \ - curl \ - shadow \ - su-exec \ - openssl \ - openssl-dev \ - openjdk21-jre \ -# Doc conversion - libreoffice \ -# pdftohtml - poppler-utils \ -# OCR MY PDF (unpaper for descew and other advanced featues) - qpdf \ - tesseract-ocr-data-eng \ - font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \ -# CV - py3-opencv \ -# python3/pip - python3 \ + ca-certificates \ + tzdata \ + tini \ + bash \ + curl \ + openssl \ + openssl-dev \ + openjdk21-jre \ + libreoffice \ + poppler-utils \ + qpdf \ + tesseract-ocr-data-eng \ + tesseract-ocr-data-fra \ + font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \ + py3-opencv \ + python3 \ py3-pip && \ -# uno unoconv and HTML pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \ mv /usr/share/tessdata /usr/share/tessdata-original && \ - mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ - fc-cache -f -v && \ - chmod +x /scripts/* && \ - chmod +x /scripts/init.sh && \ -# User permissions - addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ - chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \ - chown stirlingpdfuser:stirlingpdfgroup /app.jar && \ - tesseract --list-langs + mkdir -p /usr/share/tessdata && \ + chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tessdata /usr/share/fonts/opentype/noto && \ + fc-cache -f -v + +COPY build/libs/*.jar app.jar +RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar EXPOSE 8080/tcp -# Set user and run command + ENTRYPOINT ["tini", "--", "/scripts/init.sh"] -CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] +CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] \ No newline at end of file diff --git a/Dockerfile-ultra-lite b/Dockerfile-ultra-lite index c1bdd80d..c791164b 100644 --- a/Dockerfile-ultra-lite +++ b/Dockerfile-ultra-lite @@ -17,8 +17,11 @@ COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh COPY scripts/installFonts.sh /scripts/installFonts.sh COPY pipeline /pipeline -COPY build/libs/*.jar app.jar +# Create non-root user first +RUN addgroup -S stirlingpdfgroup && \ + adduser -S stirlingpdfuser -G stirlingpdfgroup + # Set up necessary directories and permissions RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ @@ -30,18 +33,15 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et tini \ bash \ curl \ - shadow \ - su-exec \ openjdk21-jre && \ # User permissions - mkdir -p /configs /logs /customFiles /usr/share/fonts/opentype/noto && \ - chmod +x /scripts/*.sh && \ - addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ - chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \ - chown stirlingpdfuser:stirlingpdfgroup /app.jar + mkdir -p ${HOME} /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /scripts /usr/share/fonts/custom && \ + chown -R stirlingpdfuser:stirlingpdfgroup ${HOME} /configs /logs /customFiles /pipeline /scripts /usr/share/fonts/custom && \ + chmod -R 755 ${HOME} /configs /customFiles /pipeline /scripts /usr/share/fonts/custom && \ + chmod -R 777 /logs -# Set environment variables -ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI +COPY build/libs/*.jar app.jar +RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar EXPOSE 8080/tcp diff --git a/exampleYmlFiles/docker-compose-latest-fat-security.yml b/exampleYmlFiles/docker-compose-latest-fat-security.yml index e46bd0e9..375e23c2 100644 --- a/exampleYmlFiles/docker-compose-latest-fat-security.yml +++ b/exampleYmlFiles/docker-compose-latest-fat-security.yml @@ -14,14 +14,17 @@ services: ports: - 8080:8080 volumes: - - /stirling/latest/data:/usr/share/tessdata:rw - - /stirling/latest/config:/configs:rw - - /stirling/latest/logs:/logs:rw + - ./stirling/latest/data:/usr/share/tessdata:rw + - ./stirling/latest/config:/configs:rw + - ./stirling/latest/logs:/logs:rw + user: "stirlingpdfuser" environment: DOCKER_ENABLE_SECURITY: "true" SECURITY_ENABLELOGIN: "false" PUID: 1002 PGID: 1002 + LANGS: "ALL" + TESSERACT_LANGS: "eng,fra,deu,spa,ita" UMASK: "022" SYSTEM_DEFAULTLOCALE: en-US UI_APPNAME: Stirling-PDF diff --git a/exampleYmlFiles/docker-compose-latest-security-with-sso.yml b/exampleYmlFiles/docker-compose-latest-security-with-sso.yml index 9d30986c..94eef3f0 100644 --- a/exampleYmlFiles/docker-compose-latest-security-with-sso.yml +++ b/exampleYmlFiles/docker-compose-latest-security-with-sso.yml @@ -14,9 +14,9 @@ services: ports: - "8080:8080" volumes: - - /stirling/latest/data:/usr/share/tessdata:rw - - /stirling/latest/config:/configs:rw - - /stirling/latest/logs:/logs:rw + - ./stirling/latest/data:/usr/share/tessdata:rw + - ./stirling/latest/config:/configs:rw + - ./stirling/latest/logs:/logs:rw environment: DOCKER_ENABLE_SECURITY: "true" SECURITY_ENABLELOGIN: "true" diff --git a/exampleYmlFiles/docker-compose-latest-security.yml b/exampleYmlFiles/docker-compose-latest-security.yml index d29c185d..5f8c977d 100644 --- a/exampleYmlFiles/docker-compose-latest-security.yml +++ b/exampleYmlFiles/docker-compose-latest-security.yml @@ -14,9 +14,9 @@ services: ports: - "8080:8080" volumes: - - /stirling/latest/data:/usr/share/tessdata:rw - - /stirling/latest/config:/configs:rw - - /stirling/latest/logs:/logs:rw + - ./stirling/latest/data:/usr/share/tessdata:rw + - ./stirling/latest/config:/configs:rw + - ./stirling/latest/logs:/logs:rw environment: DOCKER_ENABLE_SECURITY: "true" SECURITY_ENABLELOGIN: "true" diff --git a/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml b/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml index f357e0b9..6be46e7e 100644 --- a/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml +++ b/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml @@ -14,9 +14,9 @@ services: ports: - "8080:8080" volumes: - - /stirling/latest/data:/usr/share/tessdata:rw - - /stirling/latest/config:/configs:rw - - /stirling/latest/logs:/logs:rw + - ./stirling/latest/data:/usr/share/tessdata:rw + - ./stirling/latest/config:/configs:rw + - ./stirling/latest/logs:/logs:rw environment: DOCKER_ENABLE_SECURITY: "true" SECURITY_ENABLELOGIN: "true" diff --git a/exampleYmlFiles/docker-compose-latest-ultra-lite.yml b/exampleYmlFiles/docker-compose-latest-ultra-lite.yml index 53d7bcb8..0a6ce475 100644 --- a/exampleYmlFiles/docker-compose-latest-ultra-lite.yml +++ b/exampleYmlFiles/docker-compose-latest-ultra-lite.yml @@ -14,8 +14,8 @@ services: ports: - "8080:8080" volumes: - - /stirling/latest/config:/configs:rw - - /stirling/latest/logs:/logs:rw + - ./stirling/latest/config:/configs:rw + - ./stirling/latest/logs:/logs:rw environment: DOCKER_ENABLE_SECURITY: "false" SECURITY_ENABLELOGIN: "false" diff --git a/exampleYmlFiles/docker-compose-latest.yml b/exampleYmlFiles/docker-compose-latest.yml index 8419b072..a49f2396 100644 --- a/exampleYmlFiles/docker-compose-latest.yml +++ b/exampleYmlFiles/docker-compose-latest.yml @@ -14,9 +14,9 @@ services: ports: - "8080:8080" volumes: - - /stirling/latest/data:/usr/share/tessdata:rw - - /stirling/latest/config:/configs:rw - - /stirling/latest/logs:/logs:rw + - ./stirling/latest/data:/usr/share/tessdata:rw + - ./stirling/latest/config:/configs:rw + - ./stirling/latest/logs:/logs:rw environment: DOCKER_ENABLE_SECURITY: "false" SECURITY_ENABLELOGIN: "false" diff --git a/scripts/init-without-ocr.sh b/scripts/init-without-ocr.sh index 49218f76..042cc97e 100644 --- a/scripts/init-without-ocr.sh +++ b/scripts/init-without-ocr.sh @@ -1,37 +1,11 @@ #!/bin/bash -# Update the user and group IDs as per environment variables -if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then - usermod -o -u "$PUID" stirlingpdfuser || true -fi - - -if [ ! -z "$PGID" ] && [ "$PGID" != "$(getent group stirlingpdfgroup | cut -d: -f3)" ]; then - groupmod -o -g "$PGID" stirlingpdfgroup || true -fi -umask "$UMASK" || true - -if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" && "$FAT_DOCKER" != "true" ]]; then - echo "issue with calibre in current version, feature currently disabled on Stirling-PDF" - #apk add --no-cache calibre@testing -fi - if [[ "$FAT_DOCKER" != "true" ]]; then - /scripts/download-security-jar.sh + /scripts/download-security-jar.sh fi if [[ -n "$LANGS" ]]; then - /scripts/installFonts.sh $LANGS + /scripts/installFonts.sh $LANGS fi -echo "Setting permissions and ownership for necessary directories..." -# Attempt to change ownership of directories and files -if chown -R stirlingpdfuser:stirlingpdfgroup $HOME /logs /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /app.jar; then - chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /app.jar || true - # If chown succeeds, execute the command as stirlingpdfuser - exec su-exec stirlingpdfuser "$@" -else - # If chown fails, execute the command without changing the user context - echo "[WARN] Chown failed, running as host user" - exec "$@" -fi +exec "$@" \ No newline at end of file diff --git a/scripts/init.sh b/scripts/init.sh index b0e2a095..8ecba962 100644 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -1,31 +1,39 @@ #!/bin/bash - # Copy the original tesseract-ocr files to the volume directory without overwriting existing files echo "Copying original files without overwriting existing files" -mkdir -p /usr/share/tessdata -cp -rn /usr/share/tessdata-original/* /usr/share/tessdata +cp -rn /usr/share/tessdata-original/* /usr/share/tessdata 2>/dev/null || true +# Copy additional tessdata if available if [ -d /usr/share/tesseract-ocr/4.00/tessdata ]; then - cp -r /usr/share/tesseract-ocr/4.00/tessdata/* /usr/share/tessdata || true; + cp -rn /usr/share/tesseract-ocr/4.00/tessdata/* /usr/share/tessdata 2>/dev/null || true fi if [ -d /usr/share/tesseract-ocr/5/tessdata ]; then - cp -r /usr/share/tesseract-ocr/5/tessdata/* /usr/share/tessdata || true; + cp -rn /usr/share/tesseract-ocr/5/tessdata/* /usr/share/tessdata 2>/dev/null || true fi # Check if TESSERACT_LANGS environment variable is set and is not empty if [[ -n "$TESSERACT_LANGS" ]]; then - # Convert comma-separated values to a space-separated list - LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ') - pattern='^[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?$' - # Install each language pack - for LANG in $LANGS; do - if [[ $LANG =~ $pattern ]]; then - apk add --no-cache "tesseract-ocr-data-$LANG" - else - echo "Skipping invalid language code" - fi - done + # Convert comma-separated values to a space-separated list + TES_LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ') + pattern='^[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?$' + + # Log available languages + echo "Currently installed languages:" + tesseract --list-langs + + echo "Requested additional languages: $TES_LANGS" + + # Instead of apk add, download language files from a known source + for LANG in $TES_LANGS; do + if [[ $LANG =~ $pattern ]]; then + # Download to user-writable directory + wget -P /usr/share/tessdata/ "https://github.com/tesseract-ocr/tessdata/raw/main/${LANG}.traineddata" || \ + echo "Failed to download language pack for ${LANG}" + else + echo "Skipping invalid language code" + fi + done fi /scripts/init-without-ocr.sh "$@" \ No newline at end of file diff --git a/scripts/installFonts.sh b/scripts/installFonts.sh index d7eb7af6..bed6c770 100644 --- a/scripts/installFonts.sh +++ b/scripts/installFonts.sh @@ -1,67 +1,156 @@ #!/bin/bash LANGS=$1 +FONT_DIR="$HOME/.local/share/fonts" +TEMP_DIR=$(mktemp -d) -# Function to install a font package -install_font() { - echo "Installing font package: $1" - if ! apk add "$1" --no-cache; then - echo "Failed to install $1" - fi +# Create fonts directory if it doesn't exist +mkdir -p "$FONT_DIR" + +# Function to get latest GitHub release +get_latest_release() { + local repo=$1 + local api_url="https://api.github.com/repos/$repo/releases/latest" + curl --silent "$api_url" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' } -# Install common fonts used across many languages -#common_fonts=( -# font-terminus -# font-dejavu -# font-noto -# font-noto-cjk -# font-awesome -# font-noto-extra -#) -# -#for font in "${common_fonts[@]}"; do -# install_font $font -#done +# Function to download and install a font +install_font() { + local font_name=$1 + echo "Installing font package: $font_name" + + # Map font package names to actual font URLs and installation methods + case $font_name in + "font-dejavu") + local version=$(get_latest_release "dejavu-fonts/dejavu-fonts") + version=${version#version_} # Remove 'version_' prefix + local url="https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_${version}/dejavu-fonts-ttf-${version}.tar.bz2" + wget -q "$url" -P "$TEMP_DIR" && \ + tar xjf "$TEMP_DIR/dejavu-fonts-ttf-${version}.tar.bz2" -C "$TEMP_DIR" && \ + find "$TEMP_DIR" -name "*.ttf" -exec cp {} "$FONT_DIR/" \; + ;; + + "font-noto") + # Base Noto Sans and Serif + wget -q "https://noto-website-2.storage.googleapis.com/pkgs/NotoSans-hinted.zip" -P "$TEMP_DIR" && \ + wget -q "https://noto-website-2.storage.googleapis.com/pkgs/NotoSerif-hinted.zip" -P "$TEMP_DIR" && \ + unzip -q "$TEMP_DIR/NotoSans-hinted.zip" -d "$TEMP_DIR/noto-sans" && \ + unzip -q "$TEMP_DIR/NotoSerif-hinted.zip" -d "$TEMP_DIR/noto-serif" && \ + cp "$TEMP_DIR/noto-sans"/*.ttf "$FONT_DIR/" && \ + cp "$TEMP_DIR/noto-serif"/*.ttf "$FONT_DIR/" + ;; + + "font-noto-cjk") + # Noto CJK + wget -q "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/Japanese/NotoSansCJKjp-Regular.otf" -P "$FONT_DIR" + wget -q "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/Korean/NotoSansCJKkr-Regular.otf" -P "$FONT_DIR" + wget -q "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf" -P "$FONT_DIR" + wget -q "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/TraditionalChinese/NotoSansCJKtc-Regular.otf" -P "$FONT_DIR" + ;; + + "font-noto-arabic") + wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoNaskhArabic/NotoNaskhArabic-Regular.ttf" -P "$FONT_DIR" + wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoKufiArabic/NotoKufiArabic-Regular.ttf" -P "$FONT_DIR" + ;; + + "font-noto-devanagari") + wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSansDevanagari/NotoSansDevanagari-Regular.ttf" -P "$FONT_DIR" + wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSerifDevanagari/NotoSerifDevanagari-Regular.ttf" -P "$FONT_DIR" + ;; + + "font-noto-thai") + wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSansThai/NotoSansThai-Regular.ttf" -P "$FONT_DIR" + wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSerifThai/NotoSerifThai-Regular.ttf" -P "$FONT_DIR" + ;; + + "font-noto-hebrew") + wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSansHebrew/NotoSansHebrew-Regular.ttf" -P "$FONT_DIR" + ;; + + "font-awesome") + local version=$(get_latest_release "FortAwesome/Font-Awesome") + wget -q "https://use.fontawesome.com/releases/v${version}/fontawesome-free-${version}-desktop.zip" -P "$TEMP_DIR" && \ + unzip -q "$TEMP_DIR/fontawesome-free-${version}-desktop.zip" -d "$TEMP_DIR" && \ + cp "$TEMP_DIR/fontawesome-free-${version}-desktop/otfs"/*.otf "$FONT_DIR/" + ;; + + "font-source-code-pro") + local version=$(get_latest_release "adobe-fonts/source-code-pro") + wget -q "https://github.com/adobe-fonts/source-code-pro/releases/download/${version}/TTF-source-code-pro-${version}.zip" -P "$TEMP_DIR" && \ + unzip -q "$TEMP_DIR/TTF-source-code-pro-${version}.zip" -d "$TEMP_DIR/source-code-pro" && \ + cp "$TEMP_DIR/source-code-pro"/*.ttf "$FONT_DIR/" + ;; + + "font-vollkorn") + wget -q "https://github.com/FAlthausen/Vollkorn-Typeface/raw/main/fonts/TTF/Vollkorn-Regular.ttf" -P "$FONT_DIR" + ;; + + "font-liberation") + wget -q "https://github.com/liberationfonts/liberation-fonts/files/7261482/liberation-fonts-ttf-2.1.5.tar.gz" -P "$TEMP_DIR" && \ + tar xzf "$TEMP_DIR/liberation-fonts-ttf-2.1.5.tar.gz" -C "$TEMP_DIR" && \ + cp "$TEMP_DIR/liberation-fonts-ttf-2.1.5"/*.ttf "$FONT_DIR/" + ;; + esac + + echo "Completed installation attempt for $font_name" +} -# Map languages to specific font packages +# Enhanced language-specific font mappings declare -A language_fonts=( ["ar_AR"]="font-noto-arabic" - ["zh_CN"]="font-isas-misc" - ["zh_TW"]="font-isas-misc" - ["ja_JP"]="font-noto font-noto-thai font-noto-tibetan font-ipa font-sony-misc font-jis-misc" - ["ru_RU"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic" - ["sr_LATN_RS"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic" - ["uk_UA"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic" - ["ko_KR"]="font-noto font-noto-thai font-noto-tibetan" + ["zh_CN"]="font-noto-cjk" + ["zh_TW"]="font-noto-cjk" + ["ja_JP"]="font-noto font-noto-cjk" + ["ru_RU"]="font-noto font-liberation font-vollkorn" + ["sr_LATN_RS"]="font-noto font-liberation" + ["uk_UA"]="font-noto font-liberation" + ["ko_KR"]="font-noto font-noto-cjk" ["el_GR"]="font-noto" ["hi_IN"]="font-noto-devanagari" - ["bg_BG"]="font-vollkorn font-misc-cyrillic" - ["GENERAL"]="font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra" + ["bg_BG"]="font-noto font-liberation" + ["th_TH"]="font-noto-thai" + ["he_IL"]="font-noto-hebrew" + ["GENERAL"]="font-noto font-dejavu font-liberation font-source-code-pro font-awesome" ) -# Install fonts for other languages which generally do not need special packages beyond 'font-noto' -other_langs=("en_GB" "en_US" "de_DE" "fr_FR" "es_ES" "ca_CA" "it_IT" "pt_BR" "nl_NL" "sv_SE" "pl_PL" "ro_RO" "hu_HU" "tr_TR" "id_ID" "eu_ES") +# Install fonts based on specified languages if [[ $LANGS == "ALL" ]]; then # Install all fonts from the language_fonts map + declare -A installed_fonts for fonts in "${language_fonts[@]}"; do for font in $fonts; do - install_font $font + if [[ -z "${installed_fonts[$font]}" ]]; then + install_font "$font" + installed_fonts[$font]=1 + fi done done else # Split comma-separated languages and install necessary fonts + declare -A installed_fonts IFS=',' read -ra LANG_CODES <<< "$LANGS" for code in "${LANG_CODES[@]}"; do - if [[ " ${other_langs[@]} " =~ " ${code} " ]]; then - install_font font-noto - else - fonts_to_install=${language_fonts[$code]} - if [ ! -z "$fonts_to_install" ]; then - for font in $fonts_to_install; do - install_font $font - done - fi + fonts_to_install=${language_fonts[$code]} + if [ ! -z "$fonts_to_install" ]; then + for font in $fonts_to_install; do + if [[ -z "${installed_fonts[$font]}" ]]; then + install_font "$font" + installed_fonts[$font]=1 + fi + done fi done fi + +# Cleanup +rm -rf "$TEMP_DIR" + +# Update font cache +if command -v fc-cache >/dev/null; then + fc-cache -f "$FONT_DIR" + echo "Font cache updated" +else + echo "Warning: fc-cache not found. You may need to manually update your font cache" +fi + +echo "Font installation completed. Fonts installed in: $FONT_DIR" \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/EE/LicenseKeyChecker.java b/src/main/java/stirling/software/SPDF/EE/LicenseKeyChecker.java index 93648dfa..108ee302 100644 --- a/src/main/java/stirling/software/SPDF/EE/LicenseKeyChecker.java +++ b/src/main/java/stirling/software/SPDF/EE/LicenseKeyChecker.java @@ -28,7 +28,7 @@ public class LicenseKeyChecker { this.checkLicense(); } - @Scheduled(initialDelay = 604800000,fixedRate = 604800000) // 7 days in milliseconds + @Scheduled(initialDelay = 604800000, fixedRate = 604800000) // 7 days in milliseconds public void checkLicensePeriodically() { checkLicense(); } diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java index 96745c4a..d6950e95 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java @@ -595,7 +595,9 @@ public class GetInfoOnPDF { permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument())); permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent())); - permissionsNode.put("Extracting for accessibility", getPermissionState(ap.canExtractForAccessibility())); + permissionsNode.put( + "Extracting for accessibility", + getPermissionState(ap.canExtractForAccessibility())); permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm())); permissionsNode.put("Modifying", getPermissionState(ap.canModify())); permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations())); diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java b/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java index 94e99dd9..257100ee 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java @@ -3,11 +3,12 @@ package stirling.software.SPDF.controller.api.security; import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; +import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; -import java.util.Date; import java.util.List; import org.apache.pdfbox.pdmodel.PDDocument; @@ -41,7 +42,6 @@ import stirling.software.SPDF.service.CustomPDDocumentFactory; @RequestMapping("/api/v1/security") @Tag(name = "Security", description = "Security APIs") public class ValidateSignatureController { - private final CustomPDDocumentFactory pdfDocumentFactory; private final CertificateValidationService certValidationService; @@ -56,7 +56,7 @@ public class ValidateSignatureController { @Operation( summary = "Validate PDF Digital Signature", description = - "Validates the digital signatures in a PDF file against default or custom certificates. Input:PDF Output:JSON Type:SISO") + "Validates digital signatures in a PDF file against default or custom certificates.") @PostMapping(value = "/validate-signature") public ResponseEntity> validateSignature( @ModelAttribute SignatureValidationRequest request) throws IOException { @@ -80,7 +80,6 @@ public class ValidateSignatureController { for (PDSignature sig : signatures) { SignatureValidationResult result = new SignatureValidationResult(); - try { byte[] signedContent = sig.getSignedContent(file.getInputStream()); byte[] signatureBytes = sig.getContents(file.getInputStream()); @@ -92,77 +91,90 @@ public class ValidateSignatureController { SignerInformationStore signerStore = signedData.getSignerInfos(); for (SignerInformation signer : signerStore.getSigners()) { - X509CertificateHolder certHolder = (X509CertificateHolder) certStore.getMatches(signer.getSID()).iterator().next(); - X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder); + X509CertificateHolder certHolder = + (X509CertificateHolder) + certStore.getMatches(signer.getSID()).iterator().next(); + X509Certificate cert = + new JcaX509CertificateConverter().getCertificate(certHolder); - boolean isValid = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert)); - result.setValid(isValid); + // Basic signature validation + result.setValid( + signer.verify( + new JcaSimpleSignerInfoVerifierBuilder().build(cert))); - // Additional validations - result.setChainValid(customCert != null - ? certValidationService.validateCertificateChainWithCustomCert(cert, customCert) - : certValidationService.validateCertificateChain(cert)); - - result.setTrustValid(customCert != null - ? certValidationService.validateTrustWithCustomCert(cert, customCert) - : certValidationService.validateTrustStore(cert)); - - result.setNotRevoked(!certValidationService.isRevoked(cert)); - result.setNotExpired(!cert.getNotAfter().before(new Date())); - - // Set basic signature info - result.setSignerName(sig.getName()); - result.setSignatureDate(sig.getSignDate().getTime().toString()); - result.setReason(sig.getReason()); - result.setLocation(sig.getLocation()); - - // Set new certificate details - result.setIssuerDN(cert.getIssuerX500Principal().getName()); - result.setSubjectDN(cert.getSubjectX500Principal().getName()); - result.setSerialNumber(cert.getSerialNumber().toString(16)); // Hex format - result.setValidFrom(cert.getNotBefore().toString()); - result.setValidUntil(cert.getNotAfter().toString()); - result.setSignatureAlgorithm(cert.getSigAlgName()); - - // Get key size (if possible) - try { - result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength()); - } catch (Exception e) { - // If not RSA or error, set to 0 - result.setKeySize(0); + // Perform chain validation + CertificateValidationService.ValidationResult chainResult; + if (customCert != null) { + chainResult = + certValidationService.validateWithCustomCert(cert, customCert); + } else { + chainResult = certValidationService.validateCertificateChain(cert); } - result.setVersion(String.valueOf(cert.getVersion())); - - // Set key usage - List keyUsages = new ArrayList<>(); - boolean[] keyUsageFlags = cert.getKeyUsage(); - if (keyUsageFlags != null) { - String[] keyUsageLabels = { - "Digital Signature", "Non-Repudiation", "Key Encipherment", - "Data Encipherment", "Key Agreement", "Certificate Signing", - "CRL Signing", "Encipher Only", "Decipher Only" - }; - for (int i = 0; i < keyUsageFlags.length; i++) { - if (keyUsageFlags[i]) { - keyUsages.add(keyUsageLabels[i]); - } + result.setChainValid(chainResult.isValid()); + result.setTrustValid(chainResult.isValid()); + result.setNotExpired(!chainResult.isExpired()); + + // Check if signature was valid at the time of signing + if (sig.getSignDate() != null) { + try { + cert.checkValidity(sig.getSignDate().getTime()); + result.setValidAtTimeOfSigning(true); + } catch (CertificateExpiredException + | CertificateNotYetValidException e) { + result.setValidAtTimeOfSigning(false); } } - result.setKeyUsages(keyUsages); - - // Check if self-signed - result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal())); + + // Set signature info + populateSignatureInfo(result, sig, cert); } } catch (Exception e) { result.setValid(false); result.setErrorMessage("Signature validation failed: " + e.getMessage()); } - results.add(result); } } - return ResponseEntity.ok(results); } + + private void populateSignatureInfo( + SignatureValidationResult result, PDSignature sig, X509Certificate cert) { + result.setSignerName(sig.getName()); + result.setSignatureDate(sig.getSignDate().getTime().toString()); + result.setReason(sig.getReason()); + result.setLocation(sig.getLocation()); + result.setIssuerDN(cert.getIssuerX500Principal().getName()); + result.setSubjectDN(cert.getSubjectX500Principal().getName()); + result.setSerialNumber(cert.getSerialNumber().toString(16)); + result.setValidFrom(cert.getNotBefore().toString()); + result.setValidUntil(cert.getNotAfter().toString()); + result.setSignatureAlgorithm(cert.getSigAlgName()); + + try { + result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength()); + } catch (Exception e) { + result.setKeySize(0); + } + + result.setVersion(String.valueOf(cert.getVersion())); + + List keyUsages = new ArrayList<>(); + boolean[] keyUsageFlags = cert.getKeyUsage(); + if (keyUsageFlags != null) { + String[] keyUsageLabels = { + "Digital Signature", "Non-Repudiation", "Key Encipherment", + "Data Encipherment", "Key Agreement", "Certificate Signing", + "CRL Signing", "Encipher Only", "Decipher Only" + }; + for (int i = 0; i < keyUsageFlags.length; i++) { + if (keyUsageFlags[i]) { + keyUsages.add(keyUsageLabels[i]); + } + } + } + result.setKeyUsages(keyUsages); + result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal())); + } } diff --git a/src/main/java/stirling/software/SPDF/model/api/security/SignatureValidationResult.java b/src/main/java/stirling/software/SPDF/model/api/security/SignatureValidationResult.java index 1aafd8ec..c9bd997c 100644 --- a/src/main/java/stirling/software/SPDF/model/api/security/SignatureValidationResult.java +++ b/src/main/java/stirling/software/SPDF/model/api/security/SignatureValidationResult.java @@ -16,16 +16,16 @@ public class SignatureValidationResult { private boolean trustValid; private boolean notExpired; private boolean notRevoked; - - private String issuerDN; // Certificate issuer's Distinguished Name - private String subjectDN; // Certificate subject's Distinguished Name - private String serialNumber; // Certificate serial number - private String validFrom; // Certificate validity start date - private String validUntil; // Certificate validity end date - private String signatureAlgorithm;// Algorithm used for signing - private int keySize; // Key size in bits - private String version; // Certificate version - private List keyUsages; // List of key usage purposes - private boolean isSelfSigned; // Whether the certificate is self-signed - + private boolean validAtTimeOfSigning; + + private String issuerDN; // Certificate issuer's Distinguished Name + private String subjectDN; // Certificate subject's Distinguished Name + private String serialNumber; // Certificate serial number + private String validFrom; // Certificate validity start date + private String validUntil; // Certificate validity end date + private String signatureAlgorithm; // Algorithm used for signing + private int keySize; // Key size in bits + private String version; // Certificate version + private List keyUsages; // List of key usage purposes + private boolean isSelfSigned; // Whether the certificate is self-signed } diff --git a/src/main/java/stirling/software/SPDF/service/CertificateValidationService.java b/src/main/java/stirling/software/SPDF/service/CertificateValidationService.java index 41f54f4a..767ac89f 100644 --- a/src/main/java/stirling/software/SPDF/service/CertificateValidationService.java +++ b/src/main/java/stirling/software/SPDF/service/CertificateValidationService.java @@ -1,13 +1,11 @@ package stirling.software.SPDF.service; -import io.github.pixee.security.BoundedLineReader; -import java.io.BufferedReader; +import java.io.*; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.io.InputStreamReader; import java.security.KeyStore; import java.security.KeyStoreException; +import java.security.cert.*; import java.security.cert.CertPath; import java.security.cert.CertPathValidator; import java.security.cert.CertificateExpiredException; @@ -16,84 +14,126 @@ import java.security.cert.CertificateNotYetValidException; import java.security.cert.PKIXParameters; import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; -import java.util.Arrays; +import java.util.*; import java.util.Enumeration; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; +import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import jakarta.annotation.PostConstruct; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; @Service +@Slf4j public class CertificateValidationService { private KeyStore trustStore; + private static final String AATL_RESOURCE = "/tl12.acrobatsecuritysettings"; @PostConstruct private void initializeTrustStore() throws Exception { trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(null, null); - loadMozillaCertificates(); + loadAATLCertificatesFromPDF(); } - private void loadMozillaCertificates() throws Exception { - try (InputStream is = getClass().getResourceAsStream("/certdata.txt")) { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - String line; - StringBuilder certData = new StringBuilder(); - boolean inCert = false; - int certCount = 0; + private void loadAATLCertificatesFromPDF() throws Exception { + log.debug("Starting AATL certificate loading from PDF..."); - while ((line = BoundedLineReader.readLine(reader, 5_000_000)) != null) { - if (line.startsWith("CKA_VALUE MULTILINE_OCTAL")) { - inCert = true; - certData = new StringBuilder(); - continue; - } - if (inCert) { - if ("END".equals(line)) { - inCert = false; - byte[] certBytes = parseOctalData(certData.toString()); - if (certBytes != null) { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509Certificate cert = - (X509Certificate) - cf.generateCertificate( - new ByteArrayInputStream(certBytes)); - trustStore.setCertificateEntry("mozilla-cert-" + certCount++, cert); - } - } else { - certData.append(line).append("\n"); - } + try (InputStream pdfStream = new ClassPathResource(AATL_RESOURCE).getInputStream()) { + PDDocument document = Loader.loadPDF(pdfStream.readAllBytes()); + + PDEmbeddedFilesNameTreeNode embeddedFiles = + document.getDocumentCatalog().getNames().getEmbeddedFiles(); + Map files = embeddedFiles.getNames(); + + for (Map.Entry entry : files.entrySet()) { + log.debug(entry.getKey()); + if (entry.getKey().equals("SecuritySettings.xml")) { + byte[] xmlContent = entry.getValue().getEmbeddedFile().toByteArray(); + processSecuritySettingsXML(xmlContent); + break; } } } } - private byte[] parseOctalData(String data) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - String[] tokens = data.split("\\\\"); - for (String token : tokens) { - token = token.trim(); - if (!token.isEmpty()) { - baos.write(Integer.parseInt(token, 8)); + private void processSecuritySettingsXML(byte[] xmlContent) throws Exception { + // Simple XML parsing using String operations + String xmlString = new String(xmlContent, "UTF-8"); + int certCount = 0; + int failedCerts = 0; + + // Find all Certificate tags + String startTag = ""; + String endTag = ""; + int startIndex = 0; + + while ((startIndex = xmlString.indexOf(startTag, startIndex)) != -1) { + int endIndex = xmlString.indexOf(endTag, startIndex); + if (endIndex == -1) break; + + // Extract certificate data + String certData = xmlString.substring(startIndex + startTag.length(), endIndex).trim(); + startIndex = endIndex + endTag.length(); + + try { + byte[] certBytes = Base64.getDecoder().decode(certData); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = + (X509Certificate) + cf.generateCertificate(new ByteArrayInputStream(certBytes)); + + // Only store root certificates (self-signed) + if (cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal())) { + trustStore.setCertificateEntry("aatl-cert-" + certCount, cert); + log.trace( + "Successfully loaded AATL root certificate #" + + certCount + + "\n Subject: " + + cert.getSubjectX500Principal().getName() + + "\n Valid until: " + + cert.getNotAfter()); + certCount++; } + } catch (Exception e) { + failedCerts++; + log.error("Failed to process AATL certificate: " + e.getMessage()); } - return baos.toByteArray(); - } catch (Exception e) { - return null; } + + log.debug("AATL Certificate loading completed:"); + log.debug(" Total root certificates successfully loaded: " + certCount); + log.debug(" Failed certificates: " + failedCerts); } - public boolean validateCertificateChain(X509Certificate cert) { + @Data + public static class ValidationResult { + private boolean valid; + private boolean expired; + private boolean validAtSigningTime; + private String errorMessage; + } + + public ValidationResult validateCertificateChain(X509Certificate signerCert) { + ValidationResult result = new ValidationResult(); try { - CertPathValidator validator = CertPathValidator.getInstance("PKIX"); + // Build the certificate chain + List certChain = buildCertificateChain(signerCert); + + // Create certificate path CertificateFactory cf = CertificateFactory.getInstance("X.509"); - List certList = Arrays.asList(cert); - CertPath certPath = cf.generateCertPath(certList); + CertPath certPath = cf.generateCertPath(certChain); + // Set up trust anchors Set anchors = new HashSet<>(); Enumeration aliases = trustStore.aliases(); while (aliases.hasMoreElements()) { @@ -103,31 +143,113 @@ public class CertificateValidationService { } } + // Set up validation parameters PKIXParameters params = new PKIXParameters(anchors); params.setRevocationEnabled(false); + + // Validate the path + CertPathValidator validator = CertPathValidator.getInstance("PKIX"); validator.validate(certPath, params); - return true; + + result.setValid(true); + result.setExpired(isExpired(signerCert)); + + return result; } catch (Exception e) { - return false; + result.setValid(false); + result.setErrorMessage(e.getMessage()); + return result; } } - public boolean validateTrustStore(X509Certificate cert) { + public ValidationResult validateWithCustomCert( + X509Certificate signerCert, X509Certificate customCert) { + ValidationResult result = new ValidationResult(); + try { + // Build the complete chain from signer cert + List certChain = buildCertificateChain(signerCert); + + // Check if custom cert matches any cert in the chain + boolean matchFound = false; + for (X509Certificate chainCert : certChain) { + if (chainCert.equals(customCert)) { + matchFound = true; + break; + } + } + + if (!matchFound) { + // Check if custom cert is a valid issuer for any cert in the chain + for (X509Certificate chainCert : certChain) { + try { + chainCert.verify(customCert.getPublicKey()); + matchFound = true; + break; + } catch (Exception e) { + // Continue checking next cert + } + } + } + + result.setValid(matchFound); + if (!matchFound) { + result.setErrorMessage( + "Custom certificate is not part of the chain and is not a valid issuer"); + } + + return result; + } catch (Exception e) { + result.setValid(false); + result.setErrorMessage(e.getMessage()); + return result; + } + } + + private List buildCertificateChain(X509Certificate signerCert) + throws CertificateException { + List chain = new ArrayList<>(); + chain.add(signerCert); + + X509Certificate current = signerCert; + while (!isSelfSigned(current)) { + X509Certificate issuer = findIssuer(current); + if (issuer == null) break; + chain.add(issuer); + current = issuer; + } + + return chain; + } + + private boolean isSelfSigned(X509Certificate cert) { + return cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()); + } + + private X509Certificate findIssuer(X509Certificate cert) throws CertificateException { try { Enumeration aliases = trustStore.aliases(); while (aliases.hasMoreElements()) { - Object trustCert = trustStore.getCertificate(aliases.nextElement()); - if (trustCert instanceof X509Certificate && cert.equals(trustCert)) { - return true; + Certificate trustCert = trustStore.getCertificate(aliases.nextElement()); + if (trustCert instanceof X509Certificate) { + X509Certificate x509TrustCert = (X509Certificate) trustCert; + if (cert.getIssuerX500Principal() + .equals(x509TrustCert.getSubjectX500Principal())) { + try { + cert.verify(x509TrustCert.getPublicKey()); + return x509TrustCert; + } catch (Exception e) { + // Continue searching if verification fails + } + } } } - return false; } catch (KeyStoreException e) { - return false; + throw new CertificateException("Error accessing trust store", e); } + return null; } - public boolean isRevoked(X509Certificate cert) { + private boolean isExpired(X509Certificate cert) { try { cert.checkValidity(); return false; @@ -135,23 +257,4 @@ public class CertificateValidationService { return true; } } - - public boolean validateCertificateChainWithCustomCert( - X509Certificate cert, X509Certificate customCert) { - try { - cert.verify(customCert.getPublicKey()); - return true; - } catch (Exception e) { - return false; - } - } - - public boolean validateTrustWithCustomCert(X509Certificate cert, X509Certificate customCert) { - try { - // Compare the issuer of the signature certificate with the custom certificate - return cert.getIssuerX500Principal().equals(customCert.getSubjectX500Principal()); - } catch (Exception e) { - return false; - } - } } diff --git a/src/main/resources/tl12.acrobatsecuritysettings b/src/main/resources/tl12.acrobatsecuritysettings new file mode 100644 index 00000000..55a70fb4 Binary files /dev/null and b/src/main/resources/tl12.acrobatsecuritysettings differ