further sign stuff
This commit is contained in:
parent
3cad43006a
commit
763aeb5fae
6 changed files with 280 additions and 38 deletions
|
@ -52,15 +52,15 @@ public class Beans implements WebMvcConfigurer {
|
||||||
return slr;
|
return slr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
// @Bean
|
||||||
public MessageSource messageSource() {
|
// public MessageSource messageSource() {
|
||||||
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
// ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||||
//messageSource.setBasename("classpath:messages");
|
// //messageSource.setBasename("classpath:messages");
|
||||||
messageSource.setDefaultEncoding("UTF-8");
|
// messageSource.setDefaultEncoding("UTF-8");
|
||||||
messageSource.setUseCodeAsDefaultMessage(true);
|
// messageSource.setUseCodeAsDefaultMessage(true);
|
||||||
messageSource.setDefaultLocale(Locale.UK); // setting default locale
|
// messageSource.setDefaultLocale(Locale.UK); // setting default locale
|
||||||
return messageSource;
|
// return messageSource;
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,9 @@ import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import com.itextpdf.kernel.pdf.*;
|
import com.itextpdf.kernel.pdf.*;
|
||||||
import com.itextpdf.signatures.*;
|
import com.itextpdf.signatures.*;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
@ -55,7 +58,7 @@ import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import com.itextpdf.kernel.geom.Rectangle;
|
||||||
@RestController
|
@RestController
|
||||||
public class CertSignController {
|
public class CertSignController {
|
||||||
|
|
||||||
|
@ -66,39 +69,55 @@ public class CertSignController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
||||||
public ResponseEntity<String> signPDF(
|
public ResponseEntity<byte[]> signPDF(
|
||||||
@RequestParam("pdf") MultipartFile pdf,
|
@RequestParam("pdf") MultipartFile pdf,
|
||||||
|
@RequestParam(value = "certType", required = false) String certType,
|
||||||
@RequestParam(value = "key", required = false) MultipartFile privateKeyFile,
|
@RequestParam(value = "key", required = false) MultipartFile privateKeyFile,
|
||||||
@RequestParam(value = "cert", required = false) MultipartFile certFile,
|
@RequestParam(value = "cert", required = false) MultipartFile certFile,
|
||||||
@RequestParam(value = "p12", required = false) MultipartFile p12File,
|
@RequestParam(value = "p12", required = false) MultipartFile p12File,
|
||||||
@RequestParam(value = "password", required = false) String password) throws Exception {
|
@RequestParam(value = "password", required = false) String password,
|
||||||
|
@RequestParam(value = "showSignature", required = false) Boolean showSignature,
|
||||||
|
@RequestParam(value = "reason", required = false) String reason,
|
||||||
|
@RequestParam(value = "location", required = false) String location,
|
||||||
|
@RequestParam(value = "name", required = false) String name,
|
||||||
|
@RequestParam(value = "pageNumber", required = false) Integer pageNumber) throws Exception {
|
||||||
|
|
||||||
BouncyCastleProvider provider = new BouncyCastleProvider();
|
BouncyCastleProvider provider = new BouncyCastleProvider();
|
||||||
Security.addProvider(provider);
|
Security.addProvider(provider);
|
||||||
|
|
||||||
PrivateKey privateKey = null;
|
PrivateKey privateKey = null;
|
||||||
X509Certificate cert = null;
|
X509Certificate cert = null;
|
||||||
|
|
||||||
|
if (certType != null) {
|
||||||
|
switch (certType) {
|
||||||
|
case "PKCS12":
|
||||||
|
if (p12File != null) {
|
||||||
|
KeyStore ks = KeyStore.getInstance("PKCS12");
|
||||||
|
ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray());
|
||||||
|
String alias = ks.aliases().nextElement();
|
||||||
|
privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray());
|
||||||
|
cert = (X509Certificate) ks.getCertificate(alias);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "PEM":
|
||||||
|
if (privateKeyFile != null && certFile != null) {
|
||||||
|
// Load private key
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA", provider);
|
||||||
|
if (isPEM(privateKeyFile.getBytes())) {
|
||||||
|
privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes())));
|
||||||
|
} else {
|
||||||
|
privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes()));
|
||||||
|
}
|
||||||
|
|
||||||
if (p12File != null) {
|
// Load certificate
|
||||||
KeyStore ks = KeyStore.getInstance("PKCS12");
|
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", provider);
|
||||||
ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray());
|
if (isPEM(certFile.getBytes())) {
|
||||||
String alias = ks.aliases().nextElement();
|
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes())));
|
||||||
privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray());
|
} else {
|
||||||
cert = (X509Certificate) ks.getCertificate(alias);
|
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certFile.getBytes()));
|
||||||
} else {
|
}
|
||||||
// Load private key
|
}
|
||||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA", provider);
|
break;
|
||||||
if (isPEM(privateKeyFile.getBytes())) {
|
|
||||||
privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes())));
|
|
||||||
} else {
|
|
||||||
privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load certificate
|
|
||||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", provider);
|
|
||||||
if (isPEM(certFile.getBytes())) {
|
|
||||||
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes())));
|
|
||||||
} else {
|
|
||||||
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certFile.getBytes()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +131,42 @@ public class CertSignController {
|
||||||
.setReason("Test")
|
.setReason("Test")
|
||||||
.setLocation("TestLocation");
|
.setLocation("TestLocation");
|
||||||
|
|
||||||
|
if (showSignature != null && showSignature) {
|
||||||
|
// Get the page size
|
||||||
|
PdfPage page = signer.getDocument().getPage(1);
|
||||||
|
Rectangle pageSize = page.getPageSize();
|
||||||
|
|
||||||
|
// Define the size of the signature rectangle
|
||||||
|
float sigWidth = 200; // adjust this as needed
|
||||||
|
float sigHeight = 100; // adjust this as needed
|
||||||
|
|
||||||
|
// Define the margins from the page edges
|
||||||
|
float marginRight = 36; // adjust this as needed
|
||||||
|
float marginBottom = 36; // adjust this as needed
|
||||||
|
|
||||||
|
// Define the position and dimension of the signature field
|
||||||
|
Rectangle rect = new Rectangle(
|
||||||
|
pageSize.getRight() - sigWidth - marginRight,
|
||||||
|
pageSize.getBottom() + marginBottom,
|
||||||
|
sigWidth,
|
||||||
|
sigHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
// Creating the appearance
|
||||||
|
appearance
|
||||||
|
.setPageRect(rect)
|
||||||
|
.setPageNumber(pageNumber)
|
||||||
|
.setReason(reason)
|
||||||
|
.setLocation(location)
|
||||||
|
.setLayer2Text(name) // Set the signer name to be displayed in the signature field
|
||||||
|
.setReuseAppearance(false);
|
||||||
|
signer.setFieldName("sig");
|
||||||
|
|
||||||
|
signer.setFieldName("sig");
|
||||||
|
} else {
|
||||||
|
appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
|
||||||
|
}
|
||||||
|
|
||||||
// Set up the signer
|
// Set up the signer
|
||||||
PrivateKeySignature pks = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName());
|
PrivateKeySignature pks = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName());
|
||||||
IExternalSignature pss = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName());
|
IExternalSignature pss = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName());
|
||||||
|
@ -120,13 +175,48 @@ public class CertSignController {
|
||||||
// Call iTex7 to sign the PDF
|
// Call iTex7 to sign the PDF
|
||||||
signer.signDetached(digest, pks, new Certificate[] {cert}, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
|
signer.signDetached(digest, pks, new Certificate[] {cert}, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
|
||||||
|
|
||||||
// This is just an example, you might want to save this signed PDF into your system or send it back in the response.
|
|
||||||
// For simplicity, we will just print out the size of the signed PDF.
|
|
||||||
System.out.println("Signed PDF size: " + signedPdf.size());
|
System.out.println("Signed PDF size: " + signedPdf.size());
|
||||||
|
|
||||||
return ResponseEntity.ok("Signed PDF successfully");
|
System.out.println("PDF signed = " + isPdfSigned(signedPdf.toByteArray()));
|
||||||
|
return PdfUtils.bytesToWebResponse(signedPdf.toByteArray(), "example.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPdfSigned(byte[] pdfData) throws IOException {
|
||||||
|
InputStream pdfStream = new ByteArrayInputStream(pdfData);
|
||||||
|
PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfStream));
|
||||||
|
SignatureUtil signatureUtil = new SignatureUtil(pdfDoc);
|
||||||
|
List<String> names = signatureUtil.getSignatureNames();
|
||||||
|
|
||||||
|
boolean isSigned = false;
|
||||||
|
|
||||||
|
for (String name : names) {
|
||||||
|
PdfPKCS7 pkcs7 = signatureUtil.readSignatureData(name);
|
||||||
|
if (pkcs7 != null) {
|
||||||
|
System.out.println("Signature found.");
|
||||||
|
|
||||||
|
// Log certificate details
|
||||||
|
Certificate[] signChain = pkcs7.getSignCertificateChain();
|
||||||
|
for (Certificate cert : signChain) {
|
||||||
|
if (cert instanceof X509Certificate) {
|
||||||
|
X509Certificate x509 = (X509Certificate) cert;
|
||||||
|
System.out.println("Certificate Details:");
|
||||||
|
System.out.println("Subject: " + x509.getSubjectDN());
|
||||||
|
System.out.println("Issuer: " + x509.getIssuerDN());
|
||||||
|
System.out.println("Serial: " + x509.getSerialNumber());
|
||||||
|
System.out.println("Not Before: " + x509.getNotBefore());
|
||||||
|
System.out.println("Not After: " + x509.getNotAfter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isSigned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfDoc.close();
|
||||||
|
|
||||||
|
return isSigned;
|
||||||
|
}
|
||||||
private byte[] parsePEM(byte[] content) throws IOException {
|
private byte[] parsePEM(byte[] content) throws IOException {
|
||||||
PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content)));
|
PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content)));
|
||||||
return pemReader.readPemObject().getContent();
|
return pemReader.readPemObject().getContent();
|
||||||
|
|
|
@ -34,4 +34,11 @@ public class SecurityWebController {
|
||||||
model.addAttribute("currentPage", "add-watermark");
|
model.addAttribute("currentPage", "add-watermark");
|
||||||
return "security/add-watermark";
|
return "security/add-watermark";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/cert-sign")
|
||||||
|
@Hidden
|
||||||
|
public String certSignForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "cert-sign");
|
||||||
|
return "security/cert-sign";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,6 +129,17 @@ downloadPdf=Download PDF
|
||||||
text=Text
|
text=Text
|
||||||
font=Font
|
font=Font
|
||||||
|
|
||||||
|
certSign.title=Certificate Signing
|
||||||
|
certSign.header=Sign a PDF with your certificate
|
||||||
|
certSign.selectPDF=Select a PDF File for Signing:
|
||||||
|
certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der):
|
||||||
|
certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der):
|
||||||
|
certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate):
|
||||||
|
certSign.password=Enter Your Keystore or Private Key Password (If Any):
|
||||||
|
certSign.submit=Sign PDF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
removeBlanks.title=Remove Blanks
|
removeBlanks.title=Remove Blanks
|
||||||
removeBlanks.header=Remove Blank Pages
|
removeBlanks.header=Remove Blank Pages
|
||||||
removeBlanks.threshold=Threshold:
|
removeBlanks.threshold=Threshold:
|
||||||
|
|
|
@ -218,7 +218,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
</dialog>
|
</dialog>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: 'true'">
|
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: 'true', notRequired=${notRequired} ?: false">
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('form').submit(async function(event) {
|
$('form').submit(async function(event) {
|
||||||
|
@ -486,7 +486,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
|
||||||
<div class="custom-file-chooser">
|
<div class="custom-file-chooser">
|
||||||
<div class="custom-file">
|
<div class="custom-file">
|
||||||
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple required>
|
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple th:classappend="${notRequired ? '' : 'required'}">
|
||||||
<label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label>
|
<label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="selected-files"></div>
|
<div class="selected-files"></div>
|
||||||
|
|
134
src/main/resources/templates/security/cert-sign.html
Normal file
134
src/main/resources/templates/security/cert-sign.html
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html th:lang="${#locale.toString()}"
|
||||||
|
th:lang-direction="#{language.direction}"
|
||||||
|
xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
<th:block
|
||||||
|
th:insert="~{fragments/common :: head(title=#{certSign.title})}"></th:block>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="page-container">
|
||||||
|
<div id="content-wrap">
|
||||||
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
<br> <br>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2 th:text="#{certSign.header}"></h2>
|
||||||
|
|
||||||
|
<form action="/cert-sign" method="post"
|
||||||
|
enctype="multipart/form-data">
|
||||||
|
<div class="form-group">
|
||||||
|
<label th:text="#{certSign.selectPDF}"></label>
|
||||||
|
<div
|
||||||
|
th:replace="~{fragments/common :: fileSelector(name='pdf', multiple=false, accept='application/pdf')}"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="certType">Certificate Type</label> <select
|
||||||
|
class="form-control" id="certType" name="certType">
|
||||||
|
<option value="">-- Select --</option>
|
||||||
|
<option value="PKCS12">PKCS12</option>
|
||||||
|
<option value="PEM">PEM</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" id="p12Group" style="display: none;">
|
||||||
|
<label th:text="#{certSign.selectP12}"></label>
|
||||||
|
<div
|
||||||
|
th:replace="~{fragments/common :: fileSelector(name='p12', notRequired=true, multiple=false, accept='.p12,.pfx')}"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="pemGroup" style="display: none;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label th:text="#{certSign.selectKey}"></label>
|
||||||
|
<div
|
||||||
|
th:replace="~{fragments/common :: fileSelector(name='key', multiple=false, notRequired=true, accept='.pem,.der')}"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label th:text="#{certSign.selectCert}"></label>
|
||||||
|
<div
|
||||||
|
th:replace="~{fragments/common :: fileSelector(name='cert', multiple=false, notRequired=true, accept='.pem,.der')}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label th:text="#{certSign.password}"></label> <input
|
||||||
|
type="password" class="form-control" id="password"
|
||||||
|
name="password">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label><input type="checkbox" id="showSignature"
|
||||||
|
name="showSignature"> Show Signature</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="signatureDetails" style="display: none;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="reason">Reason</label> <input type="text"
|
||||||
|
class="form-control" id="reason" name="reason">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="location">Location</label> <input type="text"
|
||||||
|
class="form-control" id="location" name="location">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Name</label> <input type="text"
|
||||||
|
class="form-control" id="name" name="name">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pageNumber">Page Number</label> <input
|
||||||
|
type="number" class="form-control" id="pageNumber"
|
||||||
|
name="pageNumber" min="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
document
|
||||||
|
.getElementById('certType')
|
||||||
|
.addEventListener(
|
||||||
|
'change',
|
||||||
|
function() {
|
||||||
|
var p12Group = document
|
||||||
|
.getElementById('p12Group');
|
||||||
|
var pemGroup = document
|
||||||
|
.getElementById('pemGroup');
|
||||||
|
if (this.value === 'PKCS12') {
|
||||||
|
p12Group.style.display = 'block';
|
||||||
|
pemGroup.style.display = 'none';
|
||||||
|
} else if (this.value === 'PEM') {
|
||||||
|
p12Group.style.display = 'none';
|
||||||
|
pemGroup.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
p12Group.style.display = 'none';
|
||||||
|
pemGroup.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('showSignature')
|
||||||
|
.addEventListener(
|
||||||
|
'change',
|
||||||
|
function() {
|
||||||
|
var signatureDetails = document
|
||||||
|
.getElementById('signatureDetails');
|
||||||
|
if (this.checked) {
|
||||||
|
signatureDetails.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
signatureDetails.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-group text-center">
|
||||||
|
<button type="submit" id="submitBtn" class="btn btn-primary"
|
||||||
|
th:text="#{certSign.submit}"></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue