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;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageSource messageSource() {
|
||||
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||
//messageSource.setBasename("classpath:messages");
|
||||
messageSource.setDefaultEncoding("UTF-8");
|
||||
messageSource.setUseCodeAsDefaultMessage(true);
|
||||
messageSource.setDefaultLocale(Locale.UK); // setting default locale
|
||||
return messageSource;
|
||||
}
|
||||
// @Bean
|
||||
// public MessageSource messageSource() {
|
||||
// ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||
// //messageSource.setBasename("classpath:messages");
|
||||
// messageSource.setDefaultEncoding("UTF-8");
|
||||
// messageSource.setUseCodeAsDefaultMessage(true);
|
||||
// messageSource.setDefaultLocale(Locale.UK); // setting default locale
|
||||
// return messageSource;
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -44,6 +44,9 @@ import org.springframework.web.multipart.MultipartFile;
|
|||
|
||||
import com.itextpdf.kernel.pdf.*;
|
||||
import com.itextpdf.signatures.*;
|
||||
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -55,7 +58,7 @@ import java.security.cert.Certificate;
|
|||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
|
||||
import com.itextpdf.kernel.geom.Rectangle;
|
||||
@RestController
|
||||
public class CertSignController {
|
||||
|
||||
|
@ -66,39 +69,55 @@ public class CertSignController {
|
|||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
||||
public ResponseEntity<String> signPDF(
|
||||
public ResponseEntity<byte[]> signPDF(
|
||||
@RequestParam("pdf") MultipartFile pdf,
|
||||
@RequestParam(value = "certType", required = false) String certType,
|
||||
@RequestParam(value = "key", required = false) MultipartFile privateKeyFile,
|
||||
@RequestParam(value = "cert", required = false) MultipartFile certFile,
|
||||
@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();
|
||||
Security.addProvider(provider);
|
||||
|
||||
PrivateKey privateKey = 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) {
|
||||
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);
|
||||
} else {
|
||||
// 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()));
|
||||
}
|
||||
|
||||
// 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()));
|
||||
// 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()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,6 +131,42 @@ public class CertSignController {
|
|||
.setReason("Test")
|
||||
.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
|
||||
PrivateKeySignature pks = 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
|
||||
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());
|
||||
|
||||
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 {
|
||||
PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content)));
|
||||
return pemReader.readPemObject().getContent();
|
||||
|
|
|
@ -34,4 +34,11 @@ public class SecurityWebController {
|
|||
model.addAttribute("currentPage", "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
|
||||
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.header=Remove Blank Pages
|
||||
removeBlanks.threshold=Threshold:
|
||||
|
|
|
@ -218,7 +218,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
</dialog>
|
||||
</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>
|
||||
$(document).ready(function() {
|
||||
$('form').submit(async function(event) {
|
||||
|
@ -486,7 +486,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
|
||||
<div class="custom-file-chooser">
|
||||
<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>
|
||||
</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