further sign stuff

This commit is contained in:
Anthony Stirling 2023-05-21 12:32:24 +01:00
parent 3cad43006a
commit 763aeb5fae
6 changed files with 280 additions and 38 deletions

View file

@ -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;
} // }
} }

View file

@ -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,25 +69,38 @@ 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) { if (p12File != null) {
KeyStore ks = KeyStore.getInstance("PKCS12"); KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray()); ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray());
String alias = ks.aliases().nextElement(); String alias = ks.aliases().nextElement();
privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray()); privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray());
cert = (X509Certificate) ks.getCertificate(alias); cert = (X509Certificate) ks.getCertificate(alias);
} else { }
break;
case "PEM":
if (privateKeyFile != null && certFile != null) {
// Load private key // Load private key
KeyFactory keyFactory = KeyFactory.getInstance("RSA", provider); KeyFactory keyFactory = KeyFactory.getInstance("RSA", provider);
if (isPEM(privateKeyFile.getBytes())) { if (isPEM(privateKeyFile.getBytes())) {
@ -101,6 +117,9 @@ public class CertSignController {
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certFile.getBytes())); cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certFile.getBytes()));
} }
} }
break;
}
}
// Set up the PDF reader and stamper // Set up the PDF reader and stamper
PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf.getBytes())); PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf.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();

View file

@ -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";
}
} }

View file

@ -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:

View file

@ -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>

View 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>