总体来说
这篇文章是针对您的问题的原始修订版本所写的。
您的代码提到了PAdES。因此,我假设您在说“尝试使用DSS签署PDF文档”时指的是集成的PAdES(而不是分离的CAdES或XAdES)签名。
创建集成PDF签名(例如PAdES)需要首先准备PDF以能够携带嵌入式签名,即向现有或新的签名字段添加签名字典。此签名字典包含多个信息,例如签名时间、签名原因等,还包含一个占位符以后来嵌入CMS签名容器。然后对此已经准备好的PDF(除了占位符以外的部分)进行哈希处理。
此外,您的代码提到了您“选择签名级别(-B、-T、-LT、-LTA)”。
创建PAdES基线LT和PAdES基线LTA签名需要准备一份具有PAdES基线T签名的PDF,并添加一组附加对象,具体取决于T签名的性质。
eSig DSS可以为您完成所有这些准备工作,如果它拥有要准备的PDF文件。
因此,如果您只想从服务器A向B发送哈希值,则必须在您的服务器A上使用eSig DSS来完成大部分工作,服务器B仅充当一个愚笨的签名服务,返回已签名的哈希值或最多可用于PAdES的CMS容器。
是否可以在不让服务器A了解证书的情况下执行此操作,取决于您是否希望证书详细信息出现在新签名的签名小部件中。创建小部件外观是PDF准备步骤的一部分,因此如果您希望具有证书信息的小部件,则服务器A需要在请求签名之前通常甚至知道证书!
然后,在服务器A和B之间运行何种协议取决于您。您只需相应地实现SignatureTokenConnection
以在eSig DSS中在服务器A上与服务器B通信即可。
针对您的方法
现在,您展示了两个服务器的代码,我们可以讨论您的具体方法。
在服务器A上,您使用eSig DSS准备了一个PAdES签名,并嵌入了一个CMS签名容器,其中包含您的SignHashDocument.signHash
调用返回的SignatureValue
:
ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);
SignatureValue signatureValue = SignHashDocument.signHash(dataToSign);
DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);
即,服务器A创建CMS签名容器,服务器B仅提供已签署哈希。
如果您不知道用于签名的证书并在 service.getDataToSign
调用之前设置它,这是行不通的。
原因是CMS容器在未签名字节和(对于PAdES)容器的已签名字节中都包含对该证书的引用。 对于未签名字节中的参考,理论上检索与签名字节一起的证书就足够了,但对于已签名字节中的参考,必须事先知道。
或者您可以尝试在服务器B上实现完整的CMS容器生成。
虽然这需要相当大的更改,并且您必须深入研究 PAdESService
源代码,而不是在服务器A上引用的三行代码。
first retrieve a PDF object library and PDF signature service
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory()
PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService()
prepare the PDF a first time for signing and calculate the document digest,
byte[] hash = pdfSignatureService.digest(toSignDocument, parameters)
send this document digest to the backend (server B) which must create and return a special CAdES signature container, not merely naked signature bytes,
and prepare the PDF a second time for signing and inject this signature container:
DSSDocument signature = pdfSignatureService.sign(toSignDocument, encodedData, parameters)
一个概念验证
这里是使用 eSig DSS 5.8 进行的一个概念验证:
在您的服务器 A 上,我们基本上可以使用您现有的代码:
DSSDocument toSignDocument = PDF_DOCUMENT_TO_SIGN;
DSSDocument image = IMAGE_DOCUMENT;
PAdESSignatureParameters parameters = new PAdESSignatureParameters();
parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
parameters.setReason("Preuve de signature");
parameters.setLocation("MAROC");
parameters.setGenerateTBSWithoutCertificate(true);
SignatureImageParameters imageParameters = new SignatureImageParameters();
imageParameters.setPage(1);
imageParameters.setImage(image);
imageParameters.setxAxis(350);
imageParameters.setyAxis(400);
imageParameters.setWidth(200);
imageParameters.setHeight(100);
parameters.setImageParameters(imageParameters);
SignatureImageTextParameters textParameters = new SignatureImageTextParameters();
DSSFont font = new DSSJavaFont(Font.SERIF);
font.setSize(16);
textParameters.setFont(font);
textParameters.setTextColor(Color.BLUE);
textParameters.setSignerTextPosition(SignerTextPosition.RIGHT);
textParameters.setSignerTextHorizontalAlignment(SignerTextHorizontalAlignment.LEFT);
textParameters.setSignerTextVerticalAlignment(SignerTextVerticalAlignment.TOP);
textParameters.setText("TESTING");
imageParameters.setTextParameters(textParameters);
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);
byte[] signatureValue = signHash(hash);
DSSDocument signedDocument = pdfSignatureService.sign(toSignDocument, signatureValue, parameters);
signedDocument.save(PATH_TO_SAVE_THE_SIGNED_DOCUMENT_TO);
(SplitPAdESSigning测试testSplitPAdESGenerationForMehdi
)
现在signHash
方法将独立为给定文档哈希创建一个符合PAdES要求的CMS签名容器。eSig DSS包含提供此功能的方法和类,但它们是protected
甚至不可见的。因此,为了我们的POC,我们只需将它们复制到我们的代码中。
为简单起见,我使用硬编码的SHA512withRSA作为签名算法。
因此:
byte[] signHash(byte[] hash) throws IOException {
Pkcs12SignatureToken signingToken = new Pkcs12SignatureToken(YOUR_P12_DATA);
DSSPrivateKeyEntry privateKey = signingToken.getKey(YOUR_ALIAS);
CommonCertificateVerifier commonCertificateVerifier = new CommonCertificateVerifier();
padesCMSSignedDataBuilder = new PadesCMSSignedDataBuilder(commonCertificateVerifier);
PAdESSignatureParameters parameters = new PAdESSignatureParameters();
parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
parameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA);
parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
parameters.setSigningCertificate(privateKey.getCertificate());
ToBeSigned dataToSign = getDataToSign(hash, parameters);
SignatureValue signatureValue = signingToken.sign(dataToSign, DigestAlgorithm.SHA512, privateKey);
return generateCMSSignedData(hash, parameters, signatureValue);
}
PadesCMSSignedDataBuilder padesCMSSignedDataBuilder;
(SplitPAdESSigning方法)
getDataToSign
和generateCMSSignedData
这两个辅助方法基本复制自PAdESService
;它们使用由signHash
提供的padesCMSSignedDataBuilder
(你也可以把它作为这两种方法的另一个参数而不是成员变量):
public ToBeSigned getDataToSign(byte[] messageDigest, final PAdESSignatureParameters parameters) throws DSSException {
final SignatureAlgorithm signatureAlgorithm = parameters.getSignatureAlgorithm();
final CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId());
SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = padesCMSSignedDataBuilder.getSignerInfoGeneratorBuilder(parameters, messageDigest);
final CMSSignedDataGenerator generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(parameters, customContentSigner,
signerInfoGeneratorBuilder, null);
final CMSProcessableByteArray content = new CMSProcessableByteArray(messageDigest);
CMSUtils.generateDetachedCMSSignedData(generator, content);
final byte[] dataToSign = customContentSigner.getOutputStream().toByteArray();
return new ToBeSigned(dataToSign);
}
protected byte[] generateCMSSignedData(byte[] messageDigest, final PAdESSignatureParameters parameters,
final SignatureValue signatureValue) {
final SignatureAlgorithm signatureAlgorithm = parameters.getSignatureAlgorithm();
final SignatureLevel signatureLevel = parameters.getSignatureLevel();
Objects.requireNonNull(signatureAlgorithm, "SignatureAlgorithm cannot be null!");
Objects.requireNonNull(signatureLevel, "SignatureLevel must be defined!");
final CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId(), signatureValue.getValue());
final SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = padesCMSSignedDataBuilder.getSignerInfoGeneratorBuilder(parameters, messageDigest);
final CMSSignedDataGenerator generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(parameters, customContentSigner,
signerInfoGeneratorBuilder, null);
final CMSProcessableByteArray content = new CMSProcessableByteArray(messageDigest);
CMSSignedData data = CMSUtils.generateDetachedCMSSignedData(generator, content);
return DSSASN1Utils.getDEREncoded(data);
}
由于可见性受限,
PadesCMSSignedDataBuilder
和
PAdESLevelBaselineB
类被复制并添加了
SplitPAdESSigning方法。
class PadesCMSSignedDataBuilder extends CMSSignedDataBuilder {
public PadesCMSSignedDataBuilder(CertificateVerifier certificateVerifier) {
super(certificateVerifier);
}
@Override
protected CMSSignedDataGenerator createCMSSignedDataGenerator(CAdESSignatureParameters parameters, ContentSigner contentSigner, SignerInfoGeneratorBuilder signerInfoGeneratorBuilder,
CMSSignedData originalSignedData) throws DSSException {
return super.createCMSSignedDataGenerator(parameters, contentSigner, signerInfoGeneratorBuilder, originalSignedData);
}
protected SignerInfoGeneratorBuilder getSignerInfoGeneratorBuilder(final PAdESSignatureParameters parameters, final byte[] messageDigest) {
final CAdESLevelBaselineB cadesLevelBaselineB = new CAdESLevelBaselineB(true);
final PAdESLevelBaselineB padesProfileB = new PAdESLevelBaselineB();
final DigestCalculatorProvider digestCalculatorProvider = new BcDigestCalculatorProvider();
SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = new SignerInfoGeneratorBuilder(digestCalculatorProvider);
signerInfoGeneratorBuilder = signerInfoGeneratorBuilder.setSignedAttributeGenerator(new CMSAttributeTableGenerator() {
@Override
public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
return padesProfileB.getSignedAttributes(params, cadesLevelBaselineB, parameters, messageDigest);
}
});
signerInfoGeneratorBuilder = signerInfoGeneratorBuilder.setUnsignedAttributeGenerator(new CMSAttributeTableGenerator() {
@Override
public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
return padesProfileB.getUnsignedAttributes();
}
});
return signerInfoGeneratorBuilder;
}
}
class PAdESLevelBaselineB {
AttributeTable getSignedAttributes(@SuppressWarnings("rawtypes") Map params,
CAdESLevelBaselineB cadesProfile, PAdESSignatureParameters parameters, byte[] messageDigest) {
AttributeTable signedAttributes = cadesProfile.getSignedAttributes(parameters);
if (signedAttributes.get(CMSAttributes.contentType) == null) {
ASN1ObjectIdentifier contentType = (ASN1ObjectIdentifier) params.get(CMSAttributeTableGenerator.CONTENT_TYPE);
if (contentType != null) {
signedAttributes = signedAttributes.add(CMSAttributes.contentType, contentType);
}
}
if (signedAttributes.get(CMSAttributes.messageDigest) == null) {
signedAttributes = signedAttributes.add(CMSAttributes.messageDigest, new DEROctetString(messageDigest));
}
return signedAttributes;
}
AttributeTable getUnsignedAttributes() {
return null;
}
}
(SplitPAdESSigning辅助类)
signHash
及其辅助类不依赖于服务器A代码,因此也可以位于服务器B上。