Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand All @@ -95,22 +95,27 @@ public class CbeffXSDValidator {
*/
private static final ConcurrentHashMap<Schema, ThreadLocal<Validator>> 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;
}

/**
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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<Set<PosixFilePermission>> 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<Set<PosixFilePermission>> getSecurePermissions() {
try {
Set<PosixFilePermission> 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);
}
}
/**
Expand Down