mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2024-12-21 19:08:24 +01:00
init docker
This commit is contained in:
parent
a772b4fa09
commit
d59cb18666
22
Dockerfile
22
Dockerfile
@ -6,7 +6,6 @@ COPY scripts /scripts
|
|||||||
COPY pipeline /pipeline
|
COPY pipeline /pipeline
|
||||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
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 src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
|
||||||
COPY build/libs/*.jar app.jar
|
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
@ -19,6 +18,10 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
|||||||
PGID=1000 \
|
PGID=1000 \
|
||||||
UMASK=022
|
UMASK=022
|
||||||
|
|
||||||
|
# Create non-root user first
|
||||||
|
RUN addgroup -S stirlingpdfgroup && \
|
||||||
|
adduser -S stirlingpdfuser -G stirlingpdfgroup
|
||||||
|
|
||||||
# JDK for app
|
# JDK for app
|
||||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
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/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 \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
qpdf \
|
qpdf \
|
||||||
shadow \
|
|
||||||
su-exec \
|
|
||||||
openssl \
|
openssl \
|
||||||
openssl-dev \
|
openssl-dev \
|
||||||
openjdk21-jre \
|
openjdk21-jre \
|
||||||
@ -50,15 +51,16 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
|||||||
# uno unoconv and HTML
|
# uno unoconv and HTML
|
||||||
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
|
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
|
||||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||||
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
|
||||||
fc-cache -f -v && \
|
fc-cache -f -v && \
|
||||||
chmod +x /scripts/* && \
|
|
||||||
chmod +x /scripts/init.sh && \
|
|
||||||
# User permissions
|
# User permissions
|
||||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
mkdir -p ${HOME} /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /scripts /usr/share/fonts/custom && \
|
||||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
chown -R stirlingpdfuser:stirlingpdfgroup ${HOME} /configs /logs /customFiles /pipeline /scripts /usr/share/fonts/custom && \
|
||||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
|
chmod -R 755 ${HOME} /configs /customFiles /pipeline /scripts /usr/share/fonts/custom && \
|
||||||
tesseract --list-langs
|
tesseract --list-langs && \
|
||||||
|
chmod -R 777 /logs
|
||||||
|
|
||||||
|
COPY build/libs/*.jar app.jar
|
||||||
|
RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
|
|
||||||
EXPOSE 8080/tcp
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Build the application
|
# Build stage
|
||||||
FROM gradle:8.11-jdk17 AS build
|
FROM gradle:8.11-jdk17 AS build
|
||||||
|
|
||||||
# Set the working directory
|
# Set the working directory
|
||||||
@ -7,18 +7,20 @@ WORKDIR /app
|
|||||||
# Copy the entire project to the working directory
|
# Copy the entire project to the working directory
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the application with DOCKER_ENABLE_SECURITY=false
|
# Build the application
|
||||||
RUN DOCKER_ENABLE_SECURITY=true \
|
RUN DOCKER_ENABLE_SECURITY=true \
|
||||||
./gradlew clean build
|
./gradlew clean build
|
||||||
|
|
||||||
# Main stage
|
# Main stage
|
||||||
FROM alpine:3.20.3
|
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 scripts /scripts
|
||||||
COPY pipeline /pipeline
|
COPY pipeline /pipeline
|
||||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||||
COPY --from=build /app/build/libs/*.jar app.jar
|
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
@ -33,11 +35,15 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
|||||||
FAT_DOCKER=true \
|
FAT_DOCKER=true \
|
||||||
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
|
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
|
# JDK and other dependencies
|
||||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" >> /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/community" >> /etc/apk/repositories && \
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
||||||
apk upgrade --no-cache -a && \
|
apk upgrade --no-cache -a && \
|
||||||
apk add --no-cache \
|
apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
@ -45,39 +51,29 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
|||||||
tini \
|
tini \
|
||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
shadow \
|
|
||||||
su-exec \
|
|
||||||
openssl \
|
openssl \
|
||||||
openssl-dev \
|
openssl-dev \
|
||||||
openjdk21-jre \
|
openjdk21-jre \
|
||||||
# Doc conversion
|
|
||||||
libreoffice \
|
libreoffice \
|
||||||
# pdftohtml
|
|
||||||
poppler-utils \
|
poppler-utils \
|
||||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
|
||||||
qpdf \
|
qpdf \
|
||||||
tesseract-ocr-data-eng \
|
tesseract-ocr-data-eng \
|
||||||
|
tesseract-ocr-data-fra \
|
||||||
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
|
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
|
||||||
# CV
|
|
||||||
py3-opencv \
|
py3-opencv \
|
||||||
# python3/pip
|
|
||||||
python3 \
|
python3 \
|
||||||
py3-pip && \
|
py3-pip && \
|
||||||
# uno unoconv and HTML
|
|
||||||
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
|
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
|
||||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||||
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
mkdir -p /usr/share/tessdata && \
|
||||||
fc-cache -f -v && \
|
chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tessdata /usr/share/fonts/opentype/noto && \
|
||||||
chmod +x /scripts/* && \
|
fc-cache -f -v
|
||||||
chmod +x /scripts/init.sh && \
|
|
||||||
# User permissions
|
COPY build/libs/*.jar app.jar
|
||||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
|
||||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
|
|
||||||
tesseract --list-langs
|
|
||||||
|
|
||||||
EXPOSE 8080/tcp
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
# Set user and run command
|
|
||||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||||
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
@ -17,7 +17,10 @@ COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
|||||||
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||||
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
||||||
COPY pipeline /pipeline
|
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
|
# Set up necessary directories and permissions
|
||||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||||
@ -30,18 +33,15 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
|||||||
tini \
|
tini \
|
||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
shadow \
|
|
||||||
su-exec \
|
|
||||||
openjdk21-jre && \
|
openjdk21-jre && \
|
||||||
# User permissions
|
# User permissions
|
||||||
mkdir -p /configs /logs /customFiles /usr/share/fonts/opentype/noto && \
|
mkdir -p ${HOME} /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /scripts /usr/share/fonts/custom && \
|
||||||
chmod +x /scripts/*.sh && \
|
chown -R stirlingpdfuser:stirlingpdfgroup ${HOME} /configs /logs /customFiles /pipeline /scripts /usr/share/fonts/custom && \
|
||||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
chmod -R 755 ${HOME} /configs /customFiles /pipeline /scripts /usr/share/fonts/custom && \
|
||||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
chmod -R 777 /logs
|
||||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
|
||||||
|
|
||||||
# Set environment variables
|
COPY build/libs/*.jar app.jar
|
||||||
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
|
|
||||||
EXPOSE 8080/tcp
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
|
@ -14,14 +14,17 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
volumes:
|
volumes:
|
||||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||||
- /stirling/latest/config:/configs:rw
|
- ./stirling/latest/config:/configs:rw
|
||||||
- /stirling/latest/logs:/logs:rw
|
- ./stirling/latest/logs:/logs:rw
|
||||||
|
user: "stirlingpdfuser"
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "true"
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
SECURITY_ENABLELOGIN: "false"
|
SECURITY_ENABLELOGIN: "false"
|
||||||
PUID: 1002
|
PUID: 1002
|
||||||
PGID: 1002
|
PGID: 1002
|
||||||
|
LANGS: "ALL"
|
||||||
|
TESSERACT_LANGS: "eng,fra,deu,spa,ita"
|
||||||
UMASK: "022"
|
UMASK: "022"
|
||||||
SYSTEM_DEFAULTLOCALE: en-US
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
UI_APPNAME: Stirling-PDF
|
UI_APPNAME: Stirling-PDF
|
||||||
|
@ -14,9 +14,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||||
- /stirling/latest/config:/configs:rw
|
- ./stirling/latest/config:/configs:rw
|
||||||
- /stirling/latest/logs:/logs:rw
|
- ./stirling/latest/logs:/logs:rw
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "true"
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
SECURITY_ENABLELOGIN: "true"
|
SECURITY_ENABLELOGIN: "true"
|
||||||
|
@ -14,9 +14,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||||
- /stirling/latest/config:/configs:rw
|
- ./stirling/latest/config:/configs:rw
|
||||||
- /stirling/latest/logs:/logs:rw
|
- ./stirling/latest/logs:/logs:rw
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "true"
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
SECURITY_ENABLELOGIN: "true"
|
SECURITY_ENABLELOGIN: "true"
|
||||||
|
@ -14,9 +14,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||||
- /stirling/latest/config:/configs:rw
|
- ./stirling/latest/config:/configs:rw
|
||||||
- /stirling/latest/logs:/logs:rw
|
- ./stirling/latest/logs:/logs:rw
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "true"
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
SECURITY_ENABLELOGIN: "true"
|
SECURITY_ENABLELOGIN: "true"
|
||||||
|
@ -14,8 +14,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- /stirling/latest/config:/configs:rw
|
- ./stirling/latest/config:/configs:rw
|
||||||
- /stirling/latest/logs:/logs:rw
|
- ./stirling/latest/logs:/logs:rw
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "false"
|
DOCKER_ENABLE_SECURITY: "false"
|
||||||
SECURITY_ENABLELOGIN: "false"
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
@ -14,9 +14,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||||
- /stirling/latest/config:/configs:rw
|
- ./stirling/latest/config:/configs:rw
|
||||||
- /stirling/latest/logs:/logs:rw
|
- ./stirling/latest/logs:/logs:rw
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "false"
|
DOCKER_ENABLE_SECURITY: "false"
|
||||||
SECURITY_ENABLELOGIN: "false"
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
@ -1,21 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/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
|
if [[ "$FAT_DOCKER" != "true" ]]; then
|
||||||
/scripts/download-security-jar.sh
|
/scripts/download-security-jar.sh
|
||||||
fi
|
fi
|
||||||
@ -24,14 +8,4 @@ if [[ -n "$LANGS" ]]; then
|
|||||||
/scripts/installFonts.sh $LANGS
|
/scripts/installFonts.sh $LANGS
|
||||||
fi
|
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 "$@"
|
exec "$@"
|
||||||
fi
|
|
||||||
|
@ -1,27 +1,35 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Copy the original tesseract-ocr files to the volume directory without overwriting existing files
|
# Copy the original tesseract-ocr files to the volume directory without overwriting existing files
|
||||||
echo "Copying original files 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 2>/dev/null || true
|
||||||
cp -rn /usr/share/tessdata-original/* /usr/share/tessdata
|
|
||||||
|
|
||||||
|
# Copy additional tessdata if available
|
||||||
if [ -d /usr/share/tesseract-ocr/4.00/tessdata ]; then
|
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
|
fi
|
||||||
|
|
||||||
if [ -d /usr/share/tesseract-ocr/5/tessdata ]; then
|
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
|
fi
|
||||||
|
|
||||||
# Check if TESSERACT_LANGS environment variable is set and is not empty
|
# Check if TESSERACT_LANGS environment variable is set and is not empty
|
||||||
if [[ -n "$TESSERACT_LANGS" ]]; then
|
if [[ -n "$TESSERACT_LANGS" ]]; then
|
||||||
# Convert comma-separated values to a space-separated list
|
# Convert comma-separated values to a space-separated list
|
||||||
LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ')
|
TES_LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ')
|
||||||
pattern='^[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?$'
|
pattern='^[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?$'
|
||||||
# Install each language pack
|
|
||||||
for LANG in $LANGS; do
|
# 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
|
if [[ $LANG =~ $pattern ]]; then
|
||||||
apk add --no-cache "tesseract-ocr-data-$LANG"
|
# 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
|
else
|
||||||
echo "Skipping invalid language code"
|
echo "Skipping invalid language code"
|
||||||
fi
|
fi
|
||||||
|
@ -1,67 +1,156 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
LANGS=$1
|
LANGS=$1
|
||||||
|
FONT_DIR="$HOME/.local/share/fonts"
|
||||||
|
TEMP_DIR=$(mktemp -d)
|
||||||
|
|
||||||
# Function to install a font package
|
# Create fonts directory if it doesn't exist
|
||||||
install_font() {
|
mkdir -p "$FONT_DIR"
|
||||||
echo "Installing font package: $1"
|
|
||||||
if ! apk add "$1" --no-cache; then
|
# Function to get latest GitHub release
|
||||||
echo "Failed to install $1"
|
get_latest_release() {
|
||||||
fi
|
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
|
# Function to download and install a font
|
||||||
#common_fonts=(
|
install_font() {
|
||||||
# font-terminus
|
local font_name=$1
|
||||||
# font-dejavu
|
echo "Installing font package: $font_name"
|
||||||
# font-noto
|
|
||||||
# font-noto-cjk
|
|
||||||
# font-awesome
|
|
||||||
# font-noto-extra
|
|
||||||
#)
|
|
||||||
#
|
|
||||||
#for font in "${common_fonts[@]}"; do
|
|
||||||
# install_font $font
|
|
||||||
#done
|
|
||||||
|
|
||||||
# Map languages to specific font packages
|
# 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enhanced language-specific font mappings
|
||||||
declare -A language_fonts=(
|
declare -A language_fonts=(
|
||||||
["ar_AR"]="font-noto-arabic"
|
["ar_AR"]="font-noto-arabic"
|
||||||
["zh_CN"]="font-isas-misc"
|
["zh_CN"]="font-noto-cjk"
|
||||||
["zh_TW"]="font-isas-misc"
|
["zh_TW"]="font-noto-cjk"
|
||||||
["ja_JP"]="font-noto font-noto-thai font-noto-tibetan font-ipa font-sony-misc font-jis-misc"
|
["ja_JP"]="font-noto font-noto-cjk"
|
||||||
["ru_RU"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic"
|
["ru_RU"]="font-noto font-liberation font-vollkorn"
|
||||||
["sr_LATN_RS"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic"
|
["sr_LATN_RS"]="font-noto font-liberation"
|
||||||
["uk_UA"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic"
|
["uk_UA"]="font-noto font-liberation"
|
||||||
["ko_KR"]="font-noto font-noto-thai font-noto-tibetan"
|
["ko_KR"]="font-noto font-noto-cjk"
|
||||||
["el_GR"]="font-noto"
|
["el_GR"]="font-noto"
|
||||||
["hi_IN"]="font-noto-devanagari"
|
["hi_IN"]="font-noto-devanagari"
|
||||||
["bg_BG"]="font-vollkorn font-misc-cyrillic"
|
["bg_BG"]="font-noto font-liberation"
|
||||||
["GENERAL"]="font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra"
|
["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'
|
# Install fonts based on specified languages
|
||||||
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")
|
|
||||||
if [[ $LANGS == "ALL" ]]; then
|
if [[ $LANGS == "ALL" ]]; then
|
||||||
# Install all fonts from the language_fonts map
|
# Install all fonts from the language_fonts map
|
||||||
|
declare -A installed_fonts
|
||||||
for fonts in "${language_fonts[@]}"; do
|
for fonts in "${language_fonts[@]}"; do
|
||||||
for font in $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
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
# Split comma-separated languages and install necessary fonts
|
# Split comma-separated languages and install necessary fonts
|
||||||
|
declare -A installed_fonts
|
||||||
IFS=',' read -ra LANG_CODES <<< "$LANGS"
|
IFS=',' read -ra LANG_CODES <<< "$LANGS"
|
||||||
for code in "${LANG_CODES[@]}"; do
|
for code in "${LANG_CODES[@]}"; do
|
||||||
if [[ " ${other_langs[@]} " =~ " ${code} " ]]; then
|
|
||||||
install_font font-noto
|
|
||||||
else
|
|
||||||
fonts_to_install=${language_fonts[$code]}
|
fonts_to_install=${language_fonts[$code]}
|
||||||
if [ ! -z "$fonts_to_install" ]; then
|
if [ ! -z "$fonts_to_install" ]; then
|
||||||
for font in $fonts_to_install; do
|
for font in $fonts_to_install; do
|
||||||
install_font $font
|
if [[ -z "${installed_fonts[$font]}" ]]; then
|
||||||
done
|
install_font "$font"
|
||||||
fi
|
installed_fonts[$font]=1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
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"
|
@ -595,7 +595,9 @@ public class GetInfoOnPDF {
|
|||||||
|
|
||||||
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
|
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
|
||||||
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
|
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("Form Filling", getPermissionState(ap.canFillInForm()));
|
||||||
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
|
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
|
||||||
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));
|
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));
|
||||||
|
@ -3,11 +3,12 @@ package stirling.software.SPDF.controller.api.security;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateExpiredException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.CertificateNotYetValidException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.interfaces.RSAPublicKey;
|
import java.security.interfaces.RSAPublicKey;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
@ -41,7 +42,6 @@ import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
|||||||
@RequestMapping("/api/v1/security")
|
@RequestMapping("/api/v1/security")
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class ValidateSignatureController {
|
public class ValidateSignatureController {
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
private final CertificateValidationService certValidationService;
|
private final CertificateValidationService certValidationService;
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ public class ValidateSignatureController {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Validate PDF Digital Signature",
|
summary = "Validate PDF Digital Signature",
|
||||||
description =
|
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")
|
@PostMapping(value = "/validate-signature")
|
||||||
public ResponseEntity<List<SignatureValidationResult>> validateSignature(
|
public ResponseEntity<List<SignatureValidationResult>> validateSignature(
|
||||||
@ModelAttribute SignatureValidationRequest request) throws IOException {
|
@ModelAttribute SignatureValidationRequest request) throws IOException {
|
||||||
@ -80,7 +80,6 @@ public class ValidateSignatureController {
|
|||||||
|
|
||||||
for (PDSignature sig : signatures) {
|
for (PDSignature sig : signatures) {
|
||||||
SignatureValidationResult result = new SignatureValidationResult();
|
SignatureValidationResult result = new SignatureValidationResult();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] signedContent = sig.getSignedContent(file.getInputStream());
|
byte[] signedContent = sig.getSignedContent(file.getInputStream());
|
||||||
byte[] signatureBytes = sig.getContents(file.getInputStream());
|
byte[] signatureBytes = sig.getContents(file.getInputStream());
|
||||||
@ -92,49 +91,75 @@ public class ValidateSignatureController {
|
|||||||
SignerInformationStore signerStore = signedData.getSignerInfos();
|
SignerInformationStore signerStore = signedData.getSignerInfos();
|
||||||
|
|
||||||
for (SignerInformation signer : signerStore.getSigners()) {
|
for (SignerInformation signer : signerStore.getSigners()) {
|
||||||
X509CertificateHolder certHolder = (X509CertificateHolder) certStore.getMatches(signer.getSID()).iterator().next();
|
X509CertificateHolder certHolder =
|
||||||
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder);
|
(X509CertificateHolder)
|
||||||
|
certStore.getMatches(signer.getSID()).iterator().next();
|
||||||
|
X509Certificate cert =
|
||||||
|
new JcaX509CertificateConverter().getCertificate(certHolder);
|
||||||
|
|
||||||
boolean isValid = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
|
// Basic signature validation
|
||||||
result.setValid(isValid);
|
result.setValid(
|
||||||
|
signer.verify(
|
||||||
|
new JcaSimpleSignerInfoVerifierBuilder().build(cert)));
|
||||||
|
|
||||||
// Additional validations
|
// Perform chain validation
|
||||||
result.setChainValid(customCert != null
|
CertificateValidationService.ValidationResult chainResult;
|
||||||
? certValidationService.validateCertificateChainWithCustomCert(cert, customCert)
|
if (customCert != null) {
|
||||||
: certValidationService.validateCertificateChain(cert));
|
chainResult =
|
||||||
|
certValidationService.validateWithCustomCert(cert, customCert);
|
||||||
|
} else {
|
||||||
|
chainResult = certValidationService.validateCertificateChain(cert);
|
||||||
|
}
|
||||||
|
|
||||||
result.setTrustValid(customCert != null
|
result.setChainValid(chainResult.isValid());
|
||||||
? certValidationService.validateTrustWithCustomCert(cert, customCert)
|
result.setTrustValid(chainResult.isValid());
|
||||||
: certValidationService.validateTrustStore(cert));
|
result.setNotExpired(!chainResult.isExpired());
|
||||||
|
|
||||||
result.setNotRevoked(!certValidationService.isRevoked(cert));
|
// Check if signature was valid at the time of signing
|
||||||
result.setNotExpired(!cert.getNotAfter().before(new Date()));
|
if (sig.getSignDate() != null) {
|
||||||
|
try {
|
||||||
|
cert.checkValidity(sig.getSignDate().getTime());
|
||||||
|
result.setValidAtTimeOfSigning(true);
|
||||||
|
} catch (CertificateExpiredException
|
||||||
|
| CertificateNotYetValidException e) {
|
||||||
|
result.setValidAtTimeOfSigning(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set basic signature info
|
// 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.setSignerName(sig.getName());
|
||||||
result.setSignatureDate(sig.getSignDate().getTime().toString());
|
result.setSignatureDate(sig.getSignDate().getTime().toString());
|
||||||
result.setReason(sig.getReason());
|
result.setReason(sig.getReason());
|
||||||
result.setLocation(sig.getLocation());
|
result.setLocation(sig.getLocation());
|
||||||
|
|
||||||
// Set new certificate details
|
|
||||||
result.setIssuerDN(cert.getIssuerX500Principal().getName());
|
result.setIssuerDN(cert.getIssuerX500Principal().getName());
|
||||||
result.setSubjectDN(cert.getSubjectX500Principal().getName());
|
result.setSubjectDN(cert.getSubjectX500Principal().getName());
|
||||||
result.setSerialNumber(cert.getSerialNumber().toString(16)); // Hex format
|
result.setSerialNumber(cert.getSerialNumber().toString(16));
|
||||||
result.setValidFrom(cert.getNotBefore().toString());
|
result.setValidFrom(cert.getNotBefore().toString());
|
||||||
result.setValidUntil(cert.getNotAfter().toString());
|
result.setValidUntil(cert.getNotAfter().toString());
|
||||||
result.setSignatureAlgorithm(cert.getSigAlgName());
|
result.setSignatureAlgorithm(cert.getSigAlgName());
|
||||||
|
|
||||||
// Get key size (if possible)
|
|
||||||
try {
|
try {
|
||||||
result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
|
result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// If not RSA or error, set to 0
|
|
||||||
result.setKeySize(0);
|
result.setKeySize(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.setVersion(String.valueOf(cert.getVersion()));
|
result.setVersion(String.valueOf(cert.getVersion()));
|
||||||
|
|
||||||
// Set key usage
|
|
||||||
List<String> keyUsages = new ArrayList<>();
|
List<String> keyUsages = new ArrayList<>();
|
||||||
boolean[] keyUsageFlags = cert.getKeyUsage();
|
boolean[] keyUsageFlags = cert.getKeyUsage();
|
||||||
if (keyUsageFlags != null) {
|
if (keyUsageFlags != null) {
|
||||||
@ -150,19 +175,6 @@ public class ValidateSignatureController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.setKeyUsages(keyUsages);
|
result.setKeyUsages(keyUsages);
|
||||||
|
|
||||||
// Check if self-signed
|
|
||||||
result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()));
|
result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
result.setValid(false);
|
|
||||||
result.setErrorMessage("Signature validation failed: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
results.add(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.ok(results);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ public class SignatureValidationResult {
|
|||||||
private boolean trustValid;
|
private boolean trustValid;
|
||||||
private boolean notExpired;
|
private boolean notExpired;
|
||||||
private boolean notRevoked;
|
private boolean notRevoked;
|
||||||
|
private boolean validAtTimeOfSigning;
|
||||||
|
|
||||||
private String issuerDN; // Certificate issuer's Distinguished Name
|
private String issuerDN; // Certificate issuer's Distinguished Name
|
||||||
private String subjectDN; // Certificate subject's Distinguished Name
|
private String subjectDN; // Certificate subject's Distinguished Name
|
||||||
@ -27,5 +28,4 @@ public class SignatureValidationResult {
|
|||||||
private String version; // Certificate version
|
private String version; // Certificate version
|
||||||
private List<String> keyUsages; // List of key usage purposes
|
private List<String> keyUsages; // List of key usage purposes
|
||||||
private boolean isSelfSigned; // Whether the certificate is self-signed
|
private boolean isSelfSigned; // Whether the certificate is self-signed
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package stirling.software.SPDF.service;
|
package stirling.software.SPDF.service;
|
||||||
|
|
||||||
import io.github.pixee.security.BoundedLineReader;
|
import java.io.*;
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.KeyStoreException;
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.cert.*;
|
||||||
import java.security.cert.CertPath;
|
import java.security.cert.CertPath;
|
||||||
import java.security.cert.CertPathValidator;
|
import java.security.cert.CertPathValidator;
|
||||||
import java.security.cert.CertificateExpiredException;
|
import java.security.cert.CertificateExpiredException;
|
||||||
@ -16,84 +14,126 @@ import java.security.cert.CertificateNotYetValidException;
|
|||||||
import java.security.cert.PKIXParameters;
|
import java.security.cert.PKIXParameters;
|
||||||
import java.security.cert.TrustAnchor;
|
import java.security.cert.TrustAnchor;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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 org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class CertificateValidationService {
|
public class CertificateValidationService {
|
||||||
private KeyStore trustStore;
|
private KeyStore trustStore;
|
||||||
|
private static final String AATL_RESOURCE = "/tl12.acrobatsecuritysettings";
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
private void initializeTrustStore() throws Exception {
|
private void initializeTrustStore() throws Exception {
|
||||||
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
trustStore.load(null, null);
|
trustStore.load(null, null);
|
||||||
loadMozillaCertificates();
|
loadAATLCertificatesFromPDF();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMozillaCertificates() throws Exception {
|
private void loadAATLCertificatesFromPDF() throws Exception {
|
||||||
try (InputStream is = getClass().getResourceAsStream("/certdata.txt")) {
|
log.debug("Starting AATL certificate loading from PDF...");
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
|
||||||
String line;
|
try (InputStream pdfStream = new ClassPathResource(AATL_RESOURCE).getInputStream()) {
|
||||||
StringBuilder certData = new StringBuilder();
|
PDDocument document = Loader.loadPDF(pdfStream.readAllBytes());
|
||||||
boolean inCert = false;
|
|
||||||
|
PDEmbeddedFilesNameTreeNode embeddedFiles =
|
||||||
|
document.getDocumentCatalog().getNames().getEmbeddedFiles();
|
||||||
|
Map<String, PDComplexFileSpecification> files = embeddedFiles.getNames();
|
||||||
|
|
||||||
|
for (Map.Entry<String, PDComplexFileSpecification> entry : files.entrySet()) {
|
||||||
|
log.debug(entry.getKey());
|
||||||
|
if (entry.getKey().equals("SecuritySettings.xml")) {
|
||||||
|
byte[] xmlContent = entry.getValue().getEmbeddedFile().toByteArray();
|
||||||
|
processSecuritySettingsXML(xmlContent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSecuritySettingsXML(byte[] xmlContent) throws Exception {
|
||||||
|
// Simple XML parsing using String operations
|
||||||
|
String xmlString = new String(xmlContent, "UTF-8");
|
||||||
int certCount = 0;
|
int certCount = 0;
|
||||||
|
int failedCerts = 0;
|
||||||
|
|
||||||
while ((line = BoundedLineReader.readLine(reader, 5_000_000)) != null) {
|
// Find all Certificate tags
|
||||||
if (line.startsWith("CKA_VALUE MULTILINE_OCTAL")) {
|
String startTag = "<Certificate>";
|
||||||
inCert = true;
|
String endTag = "</Certificate>";
|
||||||
certData = new StringBuilder();
|
int startIndex = 0;
|
||||||
continue;
|
|
||||||
}
|
while ((startIndex = xmlString.indexOf(startTag, startIndex)) != -1) {
|
||||||
if (inCert) {
|
int endIndex = xmlString.indexOf(endTag, startIndex);
|
||||||
if ("END".equals(line)) {
|
if (endIndex == -1) break;
|
||||||
inCert = false;
|
|
||||||
byte[] certBytes = parseOctalData(certData.toString());
|
// Extract certificate data
|
||||||
if (certBytes != null) {
|
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");
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
X509Certificate cert =
|
X509Certificate cert =
|
||||||
(X509Certificate)
|
(X509Certificate)
|
||||||
cf.generateCertificate(
|
cf.generateCertificate(new ByteArrayInputStream(certBytes));
|
||||||
new ByteArrayInputStream(certBytes));
|
|
||||||
trustStore.setCertificateEntry("mozilla-cert-" + certCount++, cert);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
certData.append(line).append("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] parseOctalData(String data) {
|
// Only store root certificates (self-signed)
|
||||||
try {
|
if (cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal())) {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
trustStore.setCertificateEntry("aatl-cert-" + certCount, cert);
|
||||||
String[] tokens = data.split("\\\\");
|
log.trace(
|
||||||
for (String token : tokens) {
|
"Successfully loaded AATL root certificate #"
|
||||||
token = token.trim();
|
+ certCount
|
||||||
if (!token.isEmpty()) {
|
+ "\n Subject: "
|
||||||
baos.write(Integer.parseInt(token, 8));
|
+ cert.getSubjectX500Principal().getName()
|
||||||
|
+ "\n Valid until: "
|
||||||
|
+ cert.getNotAfter());
|
||||||
|
certCount++;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return baos.toByteArray();
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return null;
|
failedCerts++;
|
||||||
|
log.error("Failed to process AATL certificate: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validateCertificateChain(X509Certificate cert) {
|
log.debug("AATL Certificate loading completed:");
|
||||||
|
log.debug(" Total root certificates successfully loaded: " + certCount);
|
||||||
|
log.debug(" Failed certificates: " + failedCerts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 {
|
try {
|
||||||
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
|
// Build the certificate chain
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
List<X509Certificate> certChain = buildCertificateChain(signerCert);
|
||||||
List<X509Certificate> certList = Arrays.asList(cert);
|
|
||||||
CertPath certPath = cf.generateCertPath(certList);
|
|
||||||
|
|
||||||
|
// Create certificate path
|
||||||
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
|
CertPath certPath = cf.generateCertPath(certChain);
|
||||||
|
|
||||||
|
// Set up trust anchors
|
||||||
Set<TrustAnchor> anchors = new HashSet<>();
|
Set<TrustAnchor> anchors = new HashSet<>();
|
||||||
Enumeration<String> aliases = trustStore.aliases();
|
Enumeration<String> aliases = trustStore.aliases();
|
||||||
while (aliases.hasMoreElements()) {
|
while (aliases.hasMoreElements()) {
|
||||||
@ -103,31 +143,113 @@ public class CertificateValidationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up validation parameters
|
||||||
PKIXParameters params = new PKIXParameters(anchors);
|
PKIXParameters params = new PKIXParameters(anchors);
|
||||||
params.setRevocationEnabled(false);
|
params.setRevocationEnabled(false);
|
||||||
|
|
||||||
|
// Validate the path
|
||||||
|
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
|
||||||
validator.validate(certPath, params);
|
validator.validate(certPath, params);
|
||||||
return true;
|
|
||||||
|
result.setValid(true);
|
||||||
|
result.setExpired(isExpired(signerCert));
|
||||||
|
|
||||||
|
return result;
|
||||||
} catch (Exception e) {
|
} 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<X509Certificate> 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<X509Certificate> buildCertificateChain(X509Certificate signerCert)
|
||||||
|
throws CertificateException {
|
||||||
|
List<X509Certificate> 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 {
|
try {
|
||||||
Enumeration<String> aliases = trustStore.aliases();
|
Enumeration<String> aliases = trustStore.aliases();
|
||||||
while (aliases.hasMoreElements()) {
|
while (aliases.hasMoreElements()) {
|
||||||
Object trustCert = trustStore.getCertificate(aliases.nextElement());
|
Certificate trustCert = trustStore.getCertificate(aliases.nextElement());
|
||||||
if (trustCert instanceof X509Certificate && cert.equals(trustCert)) {
|
if (trustCert instanceof X509Certificate) {
|
||||||
return true;
|
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) {
|
} 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 {
|
try {
|
||||||
cert.checkValidity();
|
cert.checkValidity();
|
||||||
return false;
|
return false;
|
||||||
@ -135,23 +257,4 @@ public class CertificateValidationService {
|
|||||||
return true;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
BIN
src/main/resources/tl12.acrobatsecuritysettings
Normal file
BIN
src/main/resources/tl12.acrobatsecuritysettings
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user