wiki.xsx.core.pdf.doc.XEasyPdfDocumentSigner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of x-easypdf Show documentation
Show all versions of x-easypdf Show documentation
A framework based on pdfbox
package wiki.xsx.core.pdf.doc;
import lombok.SneakyThrows;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import java.awt.image.BufferedImage;
import java.io.*;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
/**
* pdf文档签名器
*
* @author xsx
* @date 2021/12/7
* @since 1.8
*
* Copyright (c) 2020-2022 xsx All Rights Reserved.
* x-easypdf is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*
*/
public class XEasyPdfDocumentSigner implements Serializable {
private static final long serialVersionUID = -3449241065190072431L;
/**
* pdf文档签名器参数
*/
private final XEasyPdfDocumentSignerParam param = new XEasyPdfDocumentSignerParam();
/**
* 有参构造
*
* @param pdfDocument pdf文档
*/
XEasyPdfDocumentSigner(XEasyPdfDocument pdfDocument) {
this.param.setPdfDocument(pdfDocument);
this.param.setDocument(this.param.getPdfDocument().build(true));
}
/**
* 设置签名信息
*
* @param name 名称
* @param location 位置
* @param reason 原因
* @param contactInfo 联系信息
* @return 返回pdf文档签名器
*/
public XEasyPdfDocumentSigner setSignerInfo(
String name,
String location,
String reason,
String contactInfo
) {
this.param.getSignature().setName(name);
this.param.getSignature().setLocation(location);
this.param.getSignature().setReason(reason);
this.param.getSignature().setContactInfo(contactInfo);
return this;
}
/**
* 设置签名过滤器
*
* @param filter 过滤器
* @param subFilter 子过滤器
* @return 返回pdf文档签名器
*/
public XEasyPdfDocumentSigner setSignFilter(
SignFilter.Filter filter,
SignFilter.SubFilter subFilter
) {
if (filter != null) {
this.param.getSignature().setFilter(filter.filter);
}
if (subFilter != null) {
this.param.getSignature().setSubFilter(subFilter.filter);
}
return this;
}
/**
* 设置签名图片
*
* @param image 图片
* @param marginLeft 图片左边距
* @param marginTop 图片上边距
* @param scalePercent 图片缩放比例
* @return 返回pdf文档签名器
*/
public XEasyPdfDocumentSigner setSignImage(
BufferedImage image,
float marginLeft,
float marginTop,
float scalePercent
) {
this.param.setImage(image)
.setImageMarginLeft(marginLeft)
.setImageMarginTop(marginTop)
.setImageScalePercent(scalePercent - 100);
return this;
}
/**
* 设置签名证书
*
* @param signAlgorithm 签名算法
* @param keyStoreType 密钥库类型
* @param certificate 证书文件
* @param certificatePassword 证书密码
* @return 返回pdf文档签名器
*/
public XEasyPdfDocumentSigner setCertificate(
SignAlgorithm signAlgorithm,
KeyStoreType keyStoreType,
File certificate,
String certificatePassword
) {
this.param.setSignAlgorithm(signAlgorithm)
.setKeyStoreType(keyStoreType)
.setCertificate(certificate)
.setCertificatePassword(certificatePassword);
return this;
}
/**
* 设置签名内存大小(默认:250K)
*
* @param preferredSignatureSize 签名内存大小
* @return 返回pdf文档签名器
*/
public XEasyPdfDocumentSigner setPreferredSignatureSize(int preferredSignatureSize) {
this.param.setPreferredSignatureSize(preferredSignatureSize);
return this;
}
/**
* 设置自定义签名接口
*
* @param customSignature 自定义pdfbox签名接口
* @return 返回pdf文档签名器
*/
public XEasyPdfDocumentSigner setCustomSignature(SignatureInterface customSignature) {
this.param.setCustomSignature(customSignature);
return this;
}
/**
* 设置签名后pdf访问权限
*
* @param accessPermissions pdf访问权限
* @return 返回pdf文档签名器
*/
public XEasyPdfDocumentSigner setAccessPermissions(int accessPermissions) {
this.param.setAccessPermissions(accessPermissions);
return this;
}
/**
* 签名
*
* @param pageIndex 签名页面索引
* @param outputStream 输出流
*/
@SneakyThrows
public void sign(int pageIndex, OutputStream outputStream) {
// 初始化参数
this.param.init(pageIndex);
// 创建字节数组输出流
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8192)) {
// 创建任务文档
PDDocument target = this.param.getDocument();
// 保存文档
target.save(byteArrayOutputStream);
// 添加签名
this.addSignature(PDDocument.load(byteArrayOutputStream.toByteArray()), outputStream);
}
// 关闭文档
this.param.getPdfDocument().close();
}
/**
* 添加签名
*
* @param target 目标文档
* @param outputStream 输出流
*/
@SneakyThrows
void addSignature(PDDocument target, OutputStream outputStream) {
// 获取pdf文档
XEasyPdfDocument pdfDocument = this.param.getPdfDocument();
// 替换总页码占位符
pdfDocument.replaceTotalPagePlaceholder(target, false);
// 设置基础信息(文档信息、保护策略、版本、xmp信息及书签)
pdfDocument.setBasicInfo(target);
// 设置mdp权限
this.setMdpPermission(target, this.param.getSignature(), this.param.getAccessPermissions());
// 重置签名表单
this.resetSignForm(target);
// 添加签名
target.addSignature(this.param.getSignature(), this.getSignatureInterface(), this.param.getSignatureOptions());
// 保存文档
target.saveIncremental(outputStream);
// 关闭任务文档
target.close();
}
/**
* 重置签名表单
*
* @param target 目标文档
*/
void resetSignForm(PDDocument target) {
// 获取pdfbox表单
PDAcroForm acroForm = target.getDocumentCatalog().getAcroForm();
// 如果表单不为空且首次使用,则清除首次使用项
if (acroForm != null && acroForm.getNeedAppearances()) {
// 如果表单字段为空,则清除首次使用项
if (acroForm.getFields().isEmpty()) {
// 清除首次使用项
acroForm.getCOSObject().removeItem(COSName.NEED_APPEARANCES);
}
}
}
/**
* 获取签名接口
*
* @return 返回签名接口
*/
@SneakyThrows
private SignatureInterface getSignatureInterface() {
return this.param.getCustomSignature() != null ? this.param.getCustomSignature() : new DefaultSignatureImplement(this);
}
/**
* 获取mdp权限
*
* @param doc pdfbox文档
* @return 返回mdp权限
*/
private int getMdpPermission(PDDocument doc) {
COSBase base = doc.getDocumentCatalog().getCOSObject().getDictionaryObject(COSName.PERMS);
if (base instanceof COSDictionary) {
COSDictionary permsDict = (COSDictionary) base;
base = permsDict.getDictionaryObject(COSName.DOCMDP);
if (base instanceof COSDictionary) {
COSDictionary signatureDict = (COSDictionary) base;
base = signatureDict.getDictionaryObject("Reference");
if (base instanceof COSArray) {
COSArray refArray = (COSArray) base;
for (int i = 0; i < refArray.size(); ++i) {
base = refArray.getObject(i);
if (base instanceof COSDictionary) {
COSDictionary sigRefDict = (COSDictionary) base;
if (COSName.DOCMDP.equals(sigRefDict.getDictionaryObject("TransformMethod"))) {
base = sigRefDict.getDictionaryObject("TransformParams");
if (base instanceof COSDictionary) {
COSDictionary transformDict = (COSDictionary) base;
int accessPermissions = transformDict.getInt(COSName.P, 2);
if (accessPermissions < 1 || accessPermissions > 3) {
accessPermissions = 2;
}
return accessPermissions;
}
}
}
}
}
}
}
return 0;
}
/**
* 设置mdp权限
*
* @param doc pdfbox文档
* @param signature pdfbox签名
* @param accessPermissions 签名后pdf访问权限
*/
@SneakyThrows
private void setMdpPermission(PDDocument doc, PDSignature signature, int accessPermissions) {
List signatureList = doc.getSignatureDictionaries();
for (PDSignature sig : signatureList) {
if (COSName.DOC_TIME_STAMP.equals(sig.getCOSObject().getItem(COSName.TYPE))) {
continue;
}
if (sig.getCOSObject().containsKey(COSName.CONTENTS)) {
throw new IOException("DocMDP transform method not allowed if an approval signature exists");
}
}
COSDictionary sigDict = signature.getCOSObject();
// DocMDP specific stuff
COSDictionary transformParameters = new COSDictionary();
transformParameters.setItem(COSName.TYPE, COSName.getPDFName("TransformParams"));
transformParameters.setInt(COSName.P, accessPermissions);
transformParameters.setName(COSName.V, "1.2");
transformParameters.setNeedToBeUpdated(true);
COSDictionary referenceDict = new COSDictionary();
referenceDict.setItem(COSName.TYPE, COSName.getPDFName("SigRef"));
referenceDict.setItem("TransformMethod", COSName.DOCMDP);
referenceDict.setItem("DigestMethod", COSName.getPDFName("SHA1"));
referenceDict.setItem("TransformParams", transformParameters);
referenceDict.setNeedToBeUpdated(true);
COSArray referenceArray = new COSArray();
referenceArray.add(referenceDict);
sigDict.setItem("Reference", referenceArray);
referenceArray.setNeedToBeUpdated(true);
// Catalog
COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
COSDictionary permsDict = new COSDictionary();
catalogDict.setItem(COSName.PERMS, permsDict);
permsDict.setItem(COSName.DOCMDP, signature);
catalogDict.setNeedToBeUpdated(true);
permsDict.setNeedToBeUpdated(true);
}
/**
* 签名接口默认实现
*/
public static class DefaultSignatureImplement implements SignatureInterface {
/**
* pdf签名器
*/
private final XEasyPdfDocumentSigner signer;
/**
* 提供者bc
*/
private static final String PROVIDER = "BC";
/**
* 有参构造
*
* @param signer pdf签名器
*/
DefaultSignatureImplement(XEasyPdfDocumentSigner signer) {
this.signer = signer;
}
/**
* 签名
*
* @param content 内容
* @return 返回字节数组
*/
@SneakyThrows
@Override
public byte[] sign(InputStream content) {
// 设置提供者bc
Security.addProvider(new BouncyCastleProvider());
// 获取密码字符数组
char[] passwordCharArray = this.signer.param.getCertificatePassword().toCharArray();
// 获取密钥库
KeyStore keyStore = KeyStore.getInstance(this.signer.param.getKeyStoreType().name());
// 定义证书文件流
try (FileInputStream inputStream = new FileInputStream(this.signer.param.getCertificate())) {
// 加载证书
keyStore.load(inputStream, passwordCharArray);
}
// 证书获取别名
String alias = keyStore.aliases().nextElement();
// 获取证书链
Certificate[] certificateChain = keyStore.getCertificateChain(alias);
// 定义cms签名器
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
// 定义内容签名器
ContentSigner sha1Signer = new JcaContentSignerBuilder(
this.signer.param.getSignAlgorithm().name()
).setProvider(PROVIDER).build((PrivateKey) keyStore.getKey(alias, passwordCharArray));
// 添加签名信息
generator.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider(PROVIDER).build()
).build(sha1Signer, (X509Certificate) certificateChain[0])
);
// 添加证书
generator.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
// 返回签名字节数组
return generator.generate(new CmsProcessableInputStream(content), true).getEncoded();
}
/**
* cms数据
*/
public static class CmsProcessableInputStream implements CMSTypedData {
/**
* 输入流
*/
private final InputStream in;
/**
* 内容类型
*/
private final ASN1ObjectIdentifier contentType;
/**
* 有参构造
*
* @param is 输入流
*/
CmsProcessableInputStream(InputStream is) {
this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), is);
}
/**
* 有参构造
*
* @param type 内容类型
* @param is 输入流
*/
CmsProcessableInputStream(ASN1ObjectIdentifier type, InputStream is) {
this.contentType = type;
this.in = is;
}
/**
* 获取内容
*
* @return 返回输入流
*/
@Override
public Object getContent() {
return this.in;
}
/**
* 写出
*
* @param out 输出流
* @throws IOException IO异常
*/
@Override
public void write(OutputStream out) throws IOException {
IOUtils.copy(this.in, out);
this.in.close();
}
/**
* 获取内容类型
*
* @return 返回内容类型
*/
@Override
public ASN1ObjectIdentifier getContentType() {
return this.contentType;
}
}
}
/**
* 证书类型
*/
public enum KeyStoreType {
/**
* JCEKS
*/
JCEKS,
/**
* JKS
*/
JKS,
/**
* PKCS12
*/
PKCS12
}
/**
* 签名算法枚举
*/
public enum SignAlgorithm {
/**
* RSA
*/
NONEwithRSA("RSA", "NONEwithRSA"),
MD2withRSA("RSA", "MD2withRSA"),
MD5withRSA("RSA", "MD5withRSA"),
SHA1withRSA("RSA", "SHA1withRSA"),
SHA256withRSA("RSA", "SHA256withRSA"),
SHA384withRSA("RSA", "SHA384withRSA"),
SHA512withRSA("RSA", "SHA512withRSA"),
/**
* DSA
*/
NONEwithDSA("DSA", "NONEwithDSA"),
SHA1withDSA("DSA", "SHA1withDSA"),
/**
* ECDSA
*/
NONEwithECDSA("ECDSA", "NONEwithECDSA"),
SHA1withECDSA("ECDSA", "SHA1withECDSA"),
SHA256withECDSA("ECDSA", "SHA256withECDSA"),
SHA384withECDSA("ECDSA", "SHA384withECDSA"),
SHA512withECDSA("ECDSA", "SHA512withECDSA");
/**
* 有参构造
*
* @param type 类型
* @param name 名称
*/
SignAlgorithm(String type, String name) {
}
}
/**
* 签名过滤器
*/
public static class SignFilter {
/**
* 过滤器
*/
public enum Filter {
/**
* Adobe.PPKLite
*/
FILTER_ADOBE_PPKLITE(COSName.ADOBE_PPKLITE),
/**
* Entrust.PPKEF
*/
FILTER_ENTRUST_PPKEF(COSName.ENTRUST_PPKEF),
/**
* CICI.SignIt
*/
FILTER_CICI_SIGNIT(COSName.CICI_SIGNIT),
/**
* VeriSign.PPKVS
*/
FILTER_VERISIGN_PPKVS(COSName.VERISIGN_PPKVS);
/**
* 过滤器
*/
private final COSName filter;
/**
* 有参构造
*
* @param filter 过滤器
*/
Filter(COSName filter) {
this.filter = filter;
}
/**
* 获取过滤器
*
* @return 返回过滤器
*/
public COSName getFilter() {
return filter;
}
}
/**
* 子过滤器
*/
public enum SubFilter {
/**
* adbe.x509.rsa_sha1
*/
SUBFILTER_ADBE_X509_RSA_SHA1(COSName.ADBE_X509_RSA_SHA1),
/**
* adbe.pkcs7.detached
*/
SUBFILTER_ADBE_PKCS7_DETACHED(COSName.ADBE_PKCS7_DETACHED),
/**
* ETSI.CAdES.detached
*/
SUBFILTER_ETSI_CADES_DETACHED(COSName.getPDFName("ETSI.CAdES.detached")),
/**
* adbe.pkcs7.sha1
*/
SUBFILTER_ADBE_PKCS7_SHA1(COSName.ADBE_PKCS7_SHA1);
/**
* 过滤器
*/
private final COSName filter;
/**
* 有参构造
*
* @param filter 过滤器
*/
SubFilter(COSName filter) {
this.filter = filter;
}
/**
* 获取过滤器
*
* @return 返回过滤器
*/
public COSName getFilter() {
return filter;
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy