Fix desktop cert store support
This commit is contained in:
@@ -10,7 +10,6 @@ import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
@@ -20,7 +19,6 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.bouncycastle.asn1.x500.RDN;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||
@@ -32,6 +30,7 @@ import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@@ -47,6 +46,8 @@ import stirling.software.SPDF.model.Dependency;
|
||||
import stirling.software.SPDF.model.SignatureFile;
|
||||
import stirling.software.SPDF.model.api.security.CertStoreEntriesRequest;
|
||||
import stirling.software.SPDF.service.SharedSignatureService;
|
||||
import stirling.software.SPDF.util.DesktopModeUtils;
|
||||
import stirling.software.SPDF.util.Pkcs11ProviderLoader;
|
||||
import stirling.software.common.annotations.api.UiDataApi;
|
||||
import stirling.software.common.configuration.InstallationPathConfig;
|
||||
import stirling.software.common.configuration.RuntimePathConfig;
|
||||
@@ -218,8 +219,7 @@ public class UIDataController {
|
||||
})
|
||||
@Operation(summary = "Get available certificates from OS-backed stores")
|
||||
public ResponseEntity<CertificateStoreEntriesData> getCertificateStoreEntries(
|
||||
@org.springframework.web.bind.annotation.ModelAttribute CertStoreEntriesRequest request)
|
||||
throws Exception {
|
||||
@ModelAttribute CertStoreEntriesRequest request) throws Exception {
|
||||
String certType = request.getCertType();
|
||||
MultipartFile pkcs11ConfigFile = request.getPkcs11ConfigFile();
|
||||
String password = request.getPassword();
|
||||
@@ -270,9 +270,7 @@ public class UIDataController {
|
||||
}
|
||||
|
||||
private void ensureDesktopMode(String certType) {
|
||||
boolean isDesktopMode =
|
||||
Boolean.parseBoolean(System.getProperty("STIRLING_PDF_TAURI_MODE", "false"));
|
||||
if (!isDesktopMode) {
|
||||
if (!DesktopModeUtils.isDesktopMode()) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.invalidArgument",
|
||||
"Invalid argument: {0}",
|
||||
@@ -299,7 +297,7 @@ public class UIDataController {
|
||||
"Invalid argument: {0}",
|
||||
"PKCS11 configuration file is required");
|
||||
}
|
||||
Provider pkcs11Provider = loadPkcs11Provider(pkcs11ConfigFile);
|
||||
Provider pkcs11Provider = Pkcs11ProviderLoader.loadProvider(pkcs11ConfigFile);
|
||||
KeyStore pkcs11Store = KeyStore.getInstance("PKCS11", pkcs11Provider);
|
||||
pkcs11Store.load(null, password != null ? password.toCharArray() : null);
|
||||
return pkcs11Store;
|
||||
@@ -311,21 +309,6 @@ public class UIDataController {
|
||||
}
|
||||
}
|
||||
|
||||
private Provider loadPkcs11Provider(MultipartFile configFile) throws IOException {
|
||||
Provider baseProvider = Security.getProvider("SunPKCS11");
|
||||
if (baseProvider == null) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.invalidArgument",
|
||||
"Invalid argument: {0}",
|
||||
"SunPKCS11 provider is not available in this JVM");
|
||||
}
|
||||
java.io.File tempFile = java.io.File.createTempFile("spdf-pkcs11", ".cfg");
|
||||
FileUtils.copyInputStreamToFile(configFile.getInputStream(), tempFile);
|
||||
Provider provider = baseProvider.configure(tempFile.getAbsolutePath());
|
||||
Security.addProvider(provider);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private CertificateStoreEntry buildCertificateEntry(String alias, X509Certificate certificate) {
|
||||
String displayName = getCertificateDisplayName(certificate);
|
||||
return new CertificateStoreEntry(
|
||||
|
||||
+4
-19
@@ -73,6 +73,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
|
||||
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
||||
import stirling.software.SPDF.util.DesktopModeUtils;
|
||||
import stirling.software.SPDF.util.Pkcs11ProviderLoader;
|
||||
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.service.ServerCertificateServiceInterface;
|
||||
@@ -260,7 +262,7 @@ public class CertSignController {
|
||||
pkcs11ConfigFile,
|
||||
"PKCS11 configuration",
|
||||
"PKCS11 configuration file is required");
|
||||
Provider pkcs11Provider = loadPkcs11Provider(pkcs11ConfigFile);
|
||||
Provider pkcs11Provider = Pkcs11ProviderLoader.loadProvider(pkcs11ConfigFile);
|
||||
ks = KeyStore.getInstance("PKCS11", pkcs11Provider);
|
||||
ks.load(null, password != null ? password.toCharArray() : null);
|
||||
aliasRequired = true;
|
||||
@@ -312,9 +314,7 @@ public class CertSignController {
|
||||
}
|
||||
|
||||
private void ensureDesktopMode(String certType) {
|
||||
boolean isDesktopMode =
|
||||
Boolean.parseBoolean(System.getProperty("STIRLING_PDF_TAURI_MODE", "false"));
|
||||
if (!isDesktopMode) {
|
||||
if (!DesktopModeUtils.isDesktopMode()) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.invalidArgument",
|
||||
"Invalid argument: {0}",
|
||||
@@ -367,21 +367,6 @@ public class CertSignController {
|
||||
return new KeyStoreSelection(filtered, entryPassword);
|
||||
}
|
||||
|
||||
private Provider loadPkcs11Provider(MultipartFile configFile) throws IOException {
|
||||
Provider baseProvider = Security.getProvider("SunPKCS11");
|
||||
if (baseProvider == null) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.invalidArgument",
|
||||
"Invalid argument: {0}",
|
||||
"SunPKCS11 provider is not available in this JVM");
|
||||
}
|
||||
File tempFile = File.createTempFile("spdf-pkcs11", ".cfg");
|
||||
FileUtils.copyInputStreamToFile(configFile.getInputStream(), tempFile);
|
||||
Provider provider = baseProvider.configure(tempFile.getAbsolutePath());
|
||||
Security.addProvider(provider);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
|
||||
throws IOException, OperatorCreationException, PKCSException {
|
||||
try (PEMParser pemParser =
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package stirling.software.SPDF.util;
|
||||
|
||||
public final class DesktopModeUtils {
|
||||
|
||||
private DesktopModeUtils() {}
|
||||
|
||||
public static boolean isDesktopMode() {
|
||||
return Boolean.parseBoolean(System.getProperty("STIRLING_PDF_TAURI_MODE", "false"))
|
||||
|| Boolean.parseBoolean(
|
||||
System.getenv().getOrDefault("STIRLING_PDF_TAURI_MODE", "false"))
|
||||
|| Boolean.parseBoolean(System.getenv().getOrDefault("STIRLING_DESKTOP", "false"))
|
||||
|| Boolean.parseBoolean(System.getenv().getOrDefault("VITE_DESKTOP", "false"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package stirling.software.SPDF.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
|
||||
public final class Pkcs11ProviderLoader {
|
||||
|
||||
private Pkcs11ProviderLoader() {}
|
||||
|
||||
public static Provider loadProvider(MultipartFile configFile) throws IOException {
|
||||
Provider baseProvider = Security.getProvider("SunPKCS11");
|
||||
if (baseProvider == null) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.invalidArgument",
|
||||
"Invalid argument: {0}",
|
||||
"SunPKCS11 provider is not available in this JVM");
|
||||
}
|
||||
|
||||
File tempFile = File.createTempFile("spdf-pkcs11", ".cfg");
|
||||
tempFile.deleteOnExit();
|
||||
try {
|
||||
FileUtils.copyInputStreamToFile(configFile.getInputStream(), tempFile);
|
||||
Provider provider = baseProvider.configure(tempFile.getAbsolutePath());
|
||||
Provider existingProvider = Security.getProvider(provider.getName());
|
||||
if (existingProvider != null) {
|
||||
return existingProvider;
|
||||
}
|
||||
Security.addProvider(provider);
|
||||
return provider;
|
||||
} catch (IOException e) {
|
||||
if (tempFile.exists()) {
|
||||
tempFile.delete();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2655,6 +2655,7 @@ chooseJksFile = "Choose JKS File"
|
||||
chooseP12File = "Choose PKCS12 File"
|
||||
choosePfxFile = "Choose PFX File"
|
||||
choosePrivateKey = "Choose Private Key File"
|
||||
choosePkcs11Config = "Choose PKCS#11 Config File"
|
||||
location = "Location"
|
||||
logoTitle = "Logo"
|
||||
name = "Name"
|
||||
@@ -2663,8 +2664,18 @@ pageNumber = "Page Number"
|
||||
password = "Certificate Password"
|
||||
passwordOptional = "Leave empty if no password"
|
||||
reason = "Reason"
|
||||
refreshStoreCertificates = "Refresh"
|
||||
selectStoreCertificate = "Select a certificate"
|
||||
serverCertMessage = "Using server certificate - no files or password required"
|
||||
showLogo = "Show Logo"
|
||||
storeCertificateExpires = "Expires"
|
||||
storeCertificateIssuer = "Issuer"
|
||||
storeCertificateSubject = "Subject"
|
||||
storeCertificates = "Available Certificates"
|
||||
storeCertificatesError = "Failed to load certificates"
|
||||
loadingStoreCertificates = "Loading certificates..."
|
||||
pin = "Token PIN / Password"
|
||||
pinOptional = "Leave empty if not required"
|
||||
|
||||
[certSign.signMode]
|
||||
stepTitle = "Sign Mode"
|
||||
@@ -2693,6 +2704,11 @@ text = "Need recipient <b>Trusted</b> status? <b>Manual</b>. Need a fast, tamper
|
||||
[certSign.certTypeStep]
|
||||
stepTitle = "Certificate Format"
|
||||
|
||||
[certSign.certType]
|
||||
windowsStore = "Windows Store"
|
||||
macKeychain = "macOS Keychain"
|
||||
pkcs11 = "PKCS#11"
|
||||
|
||||
[certSign.certFiles]
|
||||
stepTitle = "Certificate Files"
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const isDesktopMode = (): boolean =>
|
||||
import.meta.env.MODE === 'desktop'
|
||||
|| import.meta.env.VITE_DESKTOP === 'true'
|
||||
|| import.meta.env.STIRLING_DESKTOP === 'true';
|
||||
|| import.meta.env.STIRLING_DESKTOP === 'true'
|
||||
|| import.meta.env.STIRLING_PDF_TAURI_MODE === 'true';
|
||||
|
||||
Reference in New Issue
Block a user