From 57fed3825b50b738cdb4f8d9194f64b5554f7b35 Mon Sep 17 00:00:00 2001 From: tarique-azeez Date: Tue, 25 Nov 2025 16:17:08 +0530 Subject: [PATCH 1/3] fixed security hotspot Signed-off-by: tarique-azeez --- .../cbeffutil/common/CbeffXSDValidator.java | 23 ++-- .../util/FontPdfRendererBuilder.java | 112 ++++++++++++++++-- 2 files changed, 117 insertions(+), 18 deletions(-) diff --git a/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java b/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java index 546f996664d..c9f0b13d321 100644 --- a/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java +++ b/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java @@ -83,7 +83,7 @@ public class CbeffXSDValidator { /** * Secure SchemaFactory instance for W3C XML Schema (XSD). */ - private static final SchemaFactory SCHEMA_FACTORY; + private static final SchemaFactory SCHEMA_FACTORY = create (); /** * Cache of compiled {@link Schema} objects. * Key: "length:checksum" (CRC32), Value: Compiled {@link Schema}. @@ -95,22 +95,27 @@ public class CbeffXSDValidator { */ private static final ConcurrentHashMap> TL_VALIDATORS = new ConcurrentHashMap<>(); - static { - SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + private static SchemaFactory create() { + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); try { LOGGER.debug("Initializing hardened SchemaFactory for CBEFF XSD validation..."); // Enable secure processing - SCHEMA_FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); // Block DOCTYPE declarations (Apache Xerces specific) - SCHEMA_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // Disable all external resource resolution - SCHEMA_FACTORY.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - SCHEMA_FACTORY.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "http,https"); + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "http,https"); LOGGER.debug("SchemaFactory successfully hardened against XXE and external entities."); } catch (Exception e) { LOGGER.error("Failed to configure secure SchemaFactory: {}", e.getMessage(), e); throw new IllegalStateException("Unable to initialize secure XML schema validator", e); } + return factory; + } + + private static SchemaFactory getSchemaFactory() { + return SCHEMA_FACTORY; } /** @@ -200,9 +205,9 @@ public static boolean validateXML(final Schema schema, final byte[] xmlBytes) th public static Schema compileSchema(final byte[] xsdBytes) throws Exception { requireNonEmpty(xsdBytes, "xsdBytes"); LOGGER.debug("Compiling XSD schema from {} bytes", xsdBytes.length); - synchronized (SCHEMA_FACTORY) { + synchronized (getSchemaFactory()) { try (ByteArrayInputStream xsdStream = new ByteArrayInputStream(xsdBytes)) { - Schema schema = SCHEMA_FACTORY.newSchema(new StreamSource(xsdStream, "memory:cbeff-xsd")); + Schema schema = getSchemaFactory().newSchema(new StreamSource(xsdStream, "memory:cbeff-xsd")); LOGGER.debug("XSD schema compiled successfully: {}", schema); return schema; } catch (Exception e) { diff --git a/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java b/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java index 30c84e05b91..7b27c22b9bd 100644 --- a/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java +++ b/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java @@ -9,8 +9,13 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.Objects; +import java.util.Set; public class FontPdfRendererBuilder { @@ -32,15 +37,104 @@ public static synchronized PdfRendererBuilder getBuilder(String ttfFilePath) thr return builderInstance; } - private static void initializeFonts(PdfRendererBuilder builder,String ttfFilePath) throws IOException { - File tempFontDir = Files.createTempDirectory("loaded-fonts").toFile(); - tempFontDir.deleteOnExit(); - if(ttfFilePath.contains(CLASS_PATH)) { - // Load fonts from classpath - loadFontsFromClasspath(builder, ttfFilePath, tempFontDir); - }else { - // Load fonts from external directory - loadFontsFromExternalDirectory(builder, ttfFilePath, tempFontDir); + private static Path createSecureTempDirectory() throws IOException { + Path baseTemp = Path.of(System.getProperty("java.io.tmpdir"), "mosip-pdf-fonts"); + Files.createDirectories(baseTemp); + FileAttribute> attrs = getSecurePermissions(); + Path dir = Files.createTempDirectory(baseTemp, "fonts-", attrs); + LOGGER.debug("Created secure temp font directory: {}", dir); + return dir; + } + private static void initializeFonts(PdfRendererBuilder builder, String ttfFilePath) throws IOException { + Path tempFontDir = createSecureTempDirectory(); + try { + if (ttfFilePath.contains(CLASS_PATH)) { + loadFontsFromClasspath(builder, ttfFilePath, tempFontDir); + } else { + loadFontsFromExternalDirectory(builder, ttfFilePath, tempFontDir); + } + } finally { + // Always clean up immediately — don't rely on JVM exit + deleteDirectoryRecursively(tempFontDir); + } + } + private static FileAttribute> getSecurePermissions() { + try { + Set perms = PosixFilePermissions.fromString("rwx------"); + return PosixFilePermissions.asFileAttribute(perms); + } catch (UnsupportedOperationException e) { + return null; + } + } + + private static void loadFontsFromClasspath(PdfRendererBuilder builder, String classpathTtfPath, Path tempFontDir) { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + try { + Resource[] resources = resolver.getResources(classpathTtfPath); + if (resources.length == 0) { + LOGGER.info("No fonts found in classpath: {}", classpathTtfPath); + return; + } + for (Resource resource : resources) { + String filename = resource.getFilename(); + if (filename == null || !filename.toLowerCase().endsWith(".ttf")) { + continue; + } + Path target = tempFontDir.resolve(filename); + try (InputStream is = resource.getInputStream()) { + Files.copy(is, target, StandardCopyOption.REPLACE_EXISTING); + registerFont(builder, target); + } + } + } catch (Exception e) { + LOGGER.error("Failed to load fonts from classpath: {}", classpathTtfPath, e); + } + } + + private static void loadFontsFromExternalDirectory(PdfRendererBuilder builder, String externalTtfDir, Path tempFontDir) { + File dir = new File(externalTtfDir); + if (!dir.isDirectory()) { + LOGGER.warn("External font directory does not exist or is not a directory: {}", externalTtfDir); + return; + } + File[] fontFiles = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".ttf")); + if (fontFiles == null || fontFiles.length == 0) { + LOGGER.info("No TTF fonts found in external directory: {}", externalTtfDir); + return; + } + for (File fontFile : fontFiles) { + Path target = tempFontDir.resolve(fontFile.getName()); + try { + Files.copy(fontFile.toPath(), target, StandardCopyOption.REPLACE_EXISTING); + registerFont(builder, target); + } catch (IOException e) { + LOGGER.warn("Failed to copy font file: {}", fontFile.getName(), e); + } + } + } + + private static void registerFont(PdfRendererBuilder builder, Path fontPath) { + String fontName = fontPath.getFileName().toString().replaceAll("\\.tt[fF]$", ""); + try { + builder.useFont(fontPath.toFile(), fontName); + LOGGER.info("Successfully registered font: {} ({})", fontName, fontPath); + } catch (Exception e) { + LOGGER.warn("Failed to register font: {}", fontPath, e); + } + } + + private static void deleteDirectoryRecursively(Path path) { + if (path == null || !Files.exists(path)) return; + try (var stream = Files.walk(path)) { + stream.sorted(java.util.Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(file -> { + if (!file.delete()) { + LOGGER.debug("Failed to delete: {}", file); + } + }); + } catch (IOException e) { + LOGGER.warn("Incomplete cleanup of temp font directory: {}", path, e); } } /** From f34cd1a90023b737c559cb04f3e41b9ab02015ce Mon Sep 17 00:00:00 2001 From: tarique-azeez Date: Wed, 26 Nov 2025 11:16:16 +0530 Subject: [PATCH 2/3] fixed security hotspot and reliability Signed-off-by: tarique-azeez --- .../cbeffutil/common/CbeffXSDValidator.java | 23 ++-- .../util/FontPdfRendererBuilder.java | 112 ++---------------- 2 files changed, 18 insertions(+), 117 deletions(-) diff --git a/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java b/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java index c9f0b13d321..546f996664d 100644 --- a/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java +++ b/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java @@ -83,7 +83,7 @@ public class CbeffXSDValidator { /** * Secure SchemaFactory instance for W3C XML Schema (XSD). */ - private static final SchemaFactory SCHEMA_FACTORY = create (); + private static final SchemaFactory SCHEMA_FACTORY; /** * Cache of compiled {@link Schema} objects. * Key: "length:checksum" (CRC32), Value: Compiled {@link Schema}. @@ -95,27 +95,22 @@ public class CbeffXSDValidator { */ private static final ConcurrentHashMap> TL_VALIDATORS = new ConcurrentHashMap<>(); - private static SchemaFactory create() { - SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + static { + SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); try { LOGGER.debug("Initializing hardened SchemaFactory for CBEFF XSD validation..."); // Enable secure processing - factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + SCHEMA_FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); // Block DOCTYPE declarations (Apache Xerces specific) - factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + SCHEMA_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // Disable all external resource resolution - factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "http,https"); + SCHEMA_FACTORY.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + SCHEMA_FACTORY.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "http,https"); LOGGER.debug("SchemaFactory successfully hardened against XXE and external entities."); } catch (Exception e) { LOGGER.error("Failed to configure secure SchemaFactory: {}", e.getMessage(), e); throw new IllegalStateException("Unable to initialize secure XML schema validator", e); } - return factory; - } - - private static SchemaFactory getSchemaFactory() { - return SCHEMA_FACTORY; } /** @@ -205,9 +200,9 @@ public static boolean validateXML(final Schema schema, final byte[] xmlBytes) th public static Schema compileSchema(final byte[] xsdBytes) throws Exception { requireNonEmpty(xsdBytes, "xsdBytes"); LOGGER.debug("Compiling XSD schema from {} bytes", xsdBytes.length); - synchronized (getSchemaFactory()) { + synchronized (SCHEMA_FACTORY) { try (ByteArrayInputStream xsdStream = new ByteArrayInputStream(xsdBytes)) { - Schema schema = getSchemaFactory().newSchema(new StreamSource(xsdStream, "memory:cbeff-xsd")); + Schema schema = SCHEMA_FACTORY.newSchema(new StreamSource(xsdStream, "memory:cbeff-xsd")); LOGGER.debug("XSD schema compiled successfully: {}", schema); return schema; } catch (Exception e) { diff --git a/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java b/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java index 7b27c22b9bd..30c84e05b91 100644 --- a/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java +++ b/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java @@ -9,13 +9,8 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.PosixFilePermissions; import java.util.Objects; -import java.util.Set; public class FontPdfRendererBuilder { @@ -37,104 +32,15 @@ public static synchronized PdfRendererBuilder getBuilder(String ttfFilePath) thr return builderInstance; } - private static Path createSecureTempDirectory() throws IOException { - Path baseTemp = Path.of(System.getProperty("java.io.tmpdir"), "mosip-pdf-fonts"); - Files.createDirectories(baseTemp); - FileAttribute> attrs = getSecurePermissions(); - Path dir = Files.createTempDirectory(baseTemp, "fonts-", attrs); - LOGGER.debug("Created secure temp font directory: {}", dir); - return dir; - } - private static void initializeFonts(PdfRendererBuilder builder, String ttfFilePath) throws IOException { - Path tempFontDir = createSecureTempDirectory(); - try { - if (ttfFilePath.contains(CLASS_PATH)) { - loadFontsFromClasspath(builder, ttfFilePath, tempFontDir); - } else { - loadFontsFromExternalDirectory(builder, ttfFilePath, tempFontDir); - } - } finally { - // Always clean up immediately — don't rely on JVM exit - deleteDirectoryRecursively(tempFontDir); - } - } - private static FileAttribute> getSecurePermissions() { - try { - Set perms = PosixFilePermissions.fromString("rwx------"); - return PosixFilePermissions.asFileAttribute(perms); - } catch (UnsupportedOperationException e) { - return null; - } - } - - private static void loadFontsFromClasspath(PdfRendererBuilder builder, String classpathTtfPath, Path tempFontDir) { - PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - try { - Resource[] resources = resolver.getResources(classpathTtfPath); - if (resources.length == 0) { - LOGGER.info("No fonts found in classpath: {}", classpathTtfPath); - return; - } - for (Resource resource : resources) { - String filename = resource.getFilename(); - if (filename == null || !filename.toLowerCase().endsWith(".ttf")) { - continue; - } - Path target = tempFontDir.resolve(filename); - try (InputStream is = resource.getInputStream()) { - Files.copy(is, target, StandardCopyOption.REPLACE_EXISTING); - registerFont(builder, target); - } - } - } catch (Exception e) { - LOGGER.error("Failed to load fonts from classpath: {}", classpathTtfPath, e); - } - } - - private static void loadFontsFromExternalDirectory(PdfRendererBuilder builder, String externalTtfDir, Path tempFontDir) { - File dir = new File(externalTtfDir); - if (!dir.isDirectory()) { - LOGGER.warn("External font directory does not exist or is not a directory: {}", externalTtfDir); - return; - } - File[] fontFiles = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".ttf")); - if (fontFiles == null || fontFiles.length == 0) { - LOGGER.info("No TTF fonts found in external directory: {}", externalTtfDir); - return; - } - for (File fontFile : fontFiles) { - Path target = tempFontDir.resolve(fontFile.getName()); - try { - Files.copy(fontFile.toPath(), target, StandardCopyOption.REPLACE_EXISTING); - registerFont(builder, target); - } catch (IOException e) { - LOGGER.warn("Failed to copy font file: {}", fontFile.getName(), e); - } - } - } - - private static void registerFont(PdfRendererBuilder builder, Path fontPath) { - String fontName = fontPath.getFileName().toString().replaceAll("\\.tt[fF]$", ""); - try { - builder.useFont(fontPath.toFile(), fontName); - LOGGER.info("Successfully registered font: {} ({})", fontName, fontPath); - } catch (Exception e) { - LOGGER.warn("Failed to register font: {}", fontPath, e); - } - } - - private static void deleteDirectoryRecursively(Path path) { - if (path == null || !Files.exists(path)) return; - try (var stream = Files.walk(path)) { - stream.sorted(java.util.Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(file -> { - if (!file.delete()) { - LOGGER.debug("Failed to delete: {}", file); - } - }); - } catch (IOException e) { - LOGGER.warn("Incomplete cleanup of temp font directory: {}", path, e); + private static void initializeFonts(PdfRendererBuilder builder,String ttfFilePath) throws IOException { + File tempFontDir = Files.createTempDirectory("loaded-fonts").toFile(); + tempFontDir.deleteOnExit(); + if(ttfFilePath.contains(CLASS_PATH)) { + // Load fonts from classpath + loadFontsFromClasspath(builder, ttfFilePath, tempFontDir); + }else { + // Load fonts from external directory + loadFontsFromExternalDirectory(builder, ttfFilePath, tempFontDir); } } /** From 78a9b5e5c0e6139cca5c0c67cd69eb69f239fcbb Mon Sep 17 00:00:00 2001 From: tarique-azeez Date: Tue, 25 Nov 2025 16:17:08 +0530 Subject: [PATCH 3/3] fixed security hotspot Signed-off-by: tarique-azeez --- .../cbeffutil/common/CbeffXSDValidator.java | 23 ++-- .../util/FontPdfRendererBuilder.java | 112 ++++++++++++++++-- 2 files changed, 117 insertions(+), 18 deletions(-) diff --git a/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java b/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java index 546f996664d..c9f0b13d321 100644 --- a/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java +++ b/kernel/kernel-core/src/main/java/io/mosip/kernel/core/cbeffutil/common/CbeffXSDValidator.java @@ -83,7 +83,7 @@ public class CbeffXSDValidator { /** * Secure SchemaFactory instance for W3C XML Schema (XSD). */ - private static final SchemaFactory SCHEMA_FACTORY; + private static final SchemaFactory SCHEMA_FACTORY = create (); /** * Cache of compiled {@link Schema} objects. * Key: "length:checksum" (CRC32), Value: Compiled {@link Schema}. @@ -95,22 +95,27 @@ public class CbeffXSDValidator { */ private static final ConcurrentHashMap> TL_VALIDATORS = new ConcurrentHashMap<>(); - static { - SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + private static SchemaFactory create() { + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); try { LOGGER.debug("Initializing hardened SchemaFactory for CBEFF XSD validation..."); // Enable secure processing - SCHEMA_FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); // Block DOCTYPE declarations (Apache Xerces specific) - SCHEMA_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // Disable all external resource resolution - SCHEMA_FACTORY.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - SCHEMA_FACTORY.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "http,https"); + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "http,https"); LOGGER.debug("SchemaFactory successfully hardened against XXE and external entities."); } catch (Exception e) { LOGGER.error("Failed to configure secure SchemaFactory: {}", e.getMessage(), e); throw new IllegalStateException("Unable to initialize secure XML schema validator", e); } + return factory; + } + + private static SchemaFactory getSchemaFactory() { + return SCHEMA_FACTORY; } /** @@ -200,9 +205,9 @@ public static boolean validateXML(final Schema schema, final byte[] xmlBytes) th public static Schema compileSchema(final byte[] xsdBytes) throws Exception { requireNonEmpty(xsdBytes, "xsdBytes"); LOGGER.debug("Compiling XSD schema from {} bytes", xsdBytes.length); - synchronized (SCHEMA_FACTORY) { + synchronized (getSchemaFactory()) { try (ByteArrayInputStream xsdStream = new ByteArrayInputStream(xsdBytes)) { - Schema schema = SCHEMA_FACTORY.newSchema(new StreamSource(xsdStream, "memory:cbeff-xsd")); + Schema schema = getSchemaFactory().newSchema(new StreamSource(xsdStream, "memory:cbeff-xsd")); LOGGER.debug("XSD schema compiled successfully: {}", schema); return schema; } catch (Exception e) { diff --git a/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java b/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java index 30c84e05b91..7b27c22b9bd 100644 --- a/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java +++ b/kernel/kernel-pdfgenerator/src/main/java/io/mosip/kernel/pdfgenerator/util/FontPdfRendererBuilder.java @@ -9,8 +9,13 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.Objects; +import java.util.Set; public class FontPdfRendererBuilder { @@ -32,15 +37,104 @@ public static synchronized PdfRendererBuilder getBuilder(String ttfFilePath) thr return builderInstance; } - private static void initializeFonts(PdfRendererBuilder builder,String ttfFilePath) throws IOException { - File tempFontDir = Files.createTempDirectory("loaded-fonts").toFile(); - tempFontDir.deleteOnExit(); - if(ttfFilePath.contains(CLASS_PATH)) { - // Load fonts from classpath - loadFontsFromClasspath(builder, ttfFilePath, tempFontDir); - }else { - // Load fonts from external directory - loadFontsFromExternalDirectory(builder, ttfFilePath, tempFontDir); + private static Path createSecureTempDirectory() throws IOException { + Path baseTemp = Path.of(System.getProperty("java.io.tmpdir"), "mosip-pdf-fonts"); + Files.createDirectories(baseTemp); + FileAttribute> attrs = getSecurePermissions(); + Path dir = Files.createTempDirectory(baseTemp, "fonts-", attrs); + LOGGER.debug("Created secure temp font directory: {}", dir); + return dir; + } + private static void initializeFonts(PdfRendererBuilder builder, String ttfFilePath) throws IOException { + Path tempFontDir = createSecureTempDirectory(); + try { + if (ttfFilePath.contains(CLASS_PATH)) { + loadFontsFromClasspath(builder, ttfFilePath, tempFontDir); + } else { + loadFontsFromExternalDirectory(builder, ttfFilePath, tempFontDir); + } + } finally { + // Always clean up immediately — don't rely on JVM exit + deleteDirectoryRecursively(tempFontDir); + } + } + private static FileAttribute> getSecurePermissions() { + try { + Set perms = PosixFilePermissions.fromString("rwx------"); + return PosixFilePermissions.asFileAttribute(perms); + } catch (UnsupportedOperationException e) { + return null; + } + } + + private static void loadFontsFromClasspath(PdfRendererBuilder builder, String classpathTtfPath, Path tempFontDir) { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + try { + Resource[] resources = resolver.getResources(classpathTtfPath); + if (resources.length == 0) { + LOGGER.info("No fonts found in classpath: {}", classpathTtfPath); + return; + } + for (Resource resource : resources) { + String filename = resource.getFilename(); + if (filename == null || !filename.toLowerCase().endsWith(".ttf")) { + continue; + } + Path target = tempFontDir.resolve(filename); + try (InputStream is = resource.getInputStream()) { + Files.copy(is, target, StandardCopyOption.REPLACE_EXISTING); + registerFont(builder, target); + } + } + } catch (Exception e) { + LOGGER.error("Failed to load fonts from classpath: {}", classpathTtfPath, e); + } + } + + private static void loadFontsFromExternalDirectory(PdfRendererBuilder builder, String externalTtfDir, Path tempFontDir) { + File dir = new File(externalTtfDir); + if (!dir.isDirectory()) { + LOGGER.warn("External font directory does not exist or is not a directory: {}", externalTtfDir); + return; + } + File[] fontFiles = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".ttf")); + if (fontFiles == null || fontFiles.length == 0) { + LOGGER.info("No TTF fonts found in external directory: {}", externalTtfDir); + return; + } + for (File fontFile : fontFiles) { + Path target = tempFontDir.resolve(fontFile.getName()); + try { + Files.copy(fontFile.toPath(), target, StandardCopyOption.REPLACE_EXISTING); + registerFont(builder, target); + } catch (IOException e) { + LOGGER.warn("Failed to copy font file: {}", fontFile.getName(), e); + } + } + } + + private static void registerFont(PdfRendererBuilder builder, Path fontPath) { + String fontName = fontPath.getFileName().toString().replaceAll("\\.tt[fF]$", ""); + try { + builder.useFont(fontPath.toFile(), fontName); + LOGGER.info("Successfully registered font: {} ({})", fontName, fontPath); + } catch (Exception e) { + LOGGER.warn("Failed to register font: {}", fontPath, e); + } + } + + private static void deleteDirectoryRecursively(Path path) { + if (path == null || !Files.exists(path)) return; + try (var stream = Files.walk(path)) { + stream.sorted(java.util.Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(file -> { + if (!file.delete()) { + LOGGER.debug("Failed to delete: {}", file); + } + }); + } catch (IOException e) { + LOGGER.warn("Incomplete cleanup of temp font directory: {}", path, e); } } /**