使用 DSS(数字签名服务)签署哈希
Signing a hash with DSS (Digital Signature Service)
我正在尝试使用 DSS 对 PDF 文档进行签名,我的问题是我无法在服务器 A 中计算文档的哈希值,然后在服务器 B 中对其进行签名。
知道服务器 A 包含 PDF 文档并且在服务器 B 中我们检索用于签名的证书
我的问题是如何在不需要证书的情况下计算服务器 A 中文档的哈希值。然后发送到服务器B签名?
更新:
****** 哈希的准备和计算 ********
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
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);
FileDocument imageFile = new FileDocument("logo.png");
RemoteDocument fileImage = RemoteDocumentConverter.toRemoteDocument(imageFile);
DSSDocument image = RemoteDocumentConverter.toDSSDocument(fileImage);
// set an image
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); // Specifies the text size value (the default font size is 12pt)
textParameters.setFont(font);
textParameters.setTextColor(Color.BLUE);
textParameters.setSignerTextPosition(SignerTextPosition.RIGHT);
// Specifies a horizontal alignment of a text with respect to its area
textParameters.setSignerTextHorizontalAlignment(SignerTextHorizontalAlignment.LEFT);
// Specifies a vertical alignment of a text block with respect to a signature field area
textParameters.setSignerTextVerticalAlignment(SignerTextVerticalAlignment.TOP);
imageParameters.setTextParameters(textParameters);
FileDocument fileToSign = new FileDocument("file.pdf");
RemoteDocument fileSign = RemoteDocumentConverter.toRemoteDocument(fileToSign);
DSSDocument toSignDocument = RemoteDocumentConverter.toDSSDocument(fileSign);
byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);
DSSDocument signatureValue = SignHashDocument.signHash(hash);
DSSDocument signedDocument = pdfSignatureService.sign(toSignDocument, DSSUtils.toByteArray(signatureValue), parameters);
save(signedDocument);
****** 哈希签名 ********
// Create common certificate verifier
CommonCertificateVerifier commonCertificateVerifier = new CommonCertificateVerifier();
// Create CAdESService for signature
CAdESService service = new CAdESService(commonCertificateVerifier);
CAdESSignatureParameters parameters = new CAdESSignatureParameters();
DSSPrivateKeyEntry privateKey = getKey("certificate.p12","123456");
// We choose the level of the signature (-B, -T, -LT, -LTA).
parameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_B);
parameters.setSignaturePackaging(SignaturePackaging.ENVELOPING);
parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
// We set the signing certificate
parameters.setSigningCertificate(privateKey.getCertificate());
// We set the certificate chain
parameters.setCertificateChain(privateKey.getCertificateChain());
SignatureTokenConnection signingToken = new Pkcs12SignatureToken("certificate.p12",
new KeyStore.PasswordProtection("123456".toCharArray()));
convertByteArrayToFile(hashToSign,"filetosign.hash");
FileDocument fileToSign = new FileDocument("filetosign.hash");
RemoteDocument fileSign = RemoteDocumentConverter.toRemoteDocument(fileToSign);
DSSDocument toSignDocument = RemoteDocumentConverter.toDSSDocument(fileSign);
//ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);
ToBeSigned dataToSign = new ToBeSigned(hashToSign);
DigestAlgorithm digestAlgorithm = parameters.getDigestAlgorithm();
SignatureValue signatureValue = signingToken.sign(dataToSign, digestAlgorithm, privateKey);
DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);
return signedDocument;
******** PDF 错误:*********
一般
这是针对您问题的原始修订而写的。
您的代码提到了 PAdES。因此,当您说 尝试使用 DSS 签署 PDF 文档时,我假设您指的是集成 PAdES(不是分离的 CAdES 或 XAdES)签名。
创建集成的 PDF 签名(如 PAdES)需要首先准备 PDF 以便能够携带嵌入式签名,即将签名字典添加到现有或新的签名字段。这个签名字典里面包含了很多信息,签名时间,签名原因等等,还有一个占位符,方便后面嵌入一个CMS签名容器。然后对准备好的 PDF(占位符除外)进行哈希处理。
此外,您的代码提到您选择签名级别(-B、-T、-LT、-LTA)。
创建 PAdES Baseline LT 和 PAdES Baseline LTA 签名需要准备一个带有 PAdES Baseline T 签名的 PDF 并添加一组附加对象,具体取决于 T 签名的性质。
eSig DSS 可以为您做所有这些准备如果它有 PDF 可以准备。
因此,如果您只想从服务器 A 向 B 发送哈希值,则必须在服务器 A 上使用 eSig DSS 来完成大部分工作,而服务器 B 仅用作哑签名服务 return正在处理一个已签名的哈希值或至多一个可用于 PAdES 的 CMS 容器。
您是否可以在服务器 A 不知道证书的情况下执行此操作,取决于您是否希望证书详细信息出现在新签名的签名小部件中。创建 widget 外观是 PDF 准备步骤的一部分,所以如果你想要这样一个带有证书信息的 widget,服务器 A 需要知道证书,通常甚至 在请求签名之前 !
服务器 A 和 B 之间 运行 使用何种协议,由您决定。您只需要相应地实施 SignatureTokenConnection
即可在 eSig DSS 中的服务器 A 上使用以与服务器 B 通信。
以防你的做法
在您展示了来自两个服务器的代码之后,可以讨论您的具体方法。
在服务器 A 上,您使用 eSig DSS 准备 PAdES 签名并使用 SignatureValue
您的 SignHashDocument.signHash
调用 returns:
嵌入 CMS 签名容器
ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);
SignatureValue signatureValue = SignHashDocument.signHash(dataToSign);
DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);
即服务器 A 创建 CMS 签名容器,服务器 B 仅提供签名哈希。
除非您知道用于签名的证书并在 service.getDataToSign
调用之前将其设置在 parameters
中,否则这将无法工作。
原因是 CMS 容器在容器的无符号字节和(对于 PAdES)有符号字节中都包含对该证书的引用。对于无符号字节中的引用,理论上将证书与签名字节一起检索就足够了,但对于有符号字节中的引用,必须事先知道。
或者您可以尝试在服务器 B 上实现 CMS 容器的完整生成。
不过,这需要进行相当大的更改,并且您必须更深入地研究 PAdESService
来源。而不是上面在服务器 A 上引用的三行,您必须:
先获取一个PDF对象库和PDF签名服务
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
第一次准备 PDF 进行签名并计算文档摘要,
byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);
将此文档摘要发送到后端(服务器 B),后者必须创建和 return 一个特殊的 CAdES 签名容器,而不仅仅是裸签名字节,
并再次准备 PDF 以进行签名并注入此签名容器:
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
现在应该为给定的文档哈希独立创建一个CMS签名容器,这个容器应该符合PAdES要求。 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;
辅助方法 getDataToSign
和 generateCMSSignedData
本质上是从 PAdESService
复制而来的;他们使用 signHash
提供的 padesCMSSignedDataBuilder
(你也可以将其设为这两种方法的另一个参数,而不是成员变量):
/** @see eu.europa.esig.dss.pades.signature.PAdESService#getDataToSign(DSSDocument, PAdESSignatureParameters) */
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);
}
/** @see eu.europa.esig.dss.pades.signature.PAdESService#generateCMSSignedData(DSSDocument, PAdESSignatureParameters, SignatureValue) */
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);
}
(SplitPAdESSigning 方法)
类 PadesCMSSignedDataBuilder
和 PAdESLevelBaselineB
由于可见性受限,复制如下:
/** @see eu.europa.esig.dss.cades.signature.CMSSignedDataBuilder */
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;
}
}
/** @see eu.europa.esig.dss.pades.signature.PAdESLevelBaselineB */
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 上。
这是可能的,可以在此处看到一个开源的完整实现 https://github.com/eideasy/eideasy-external-pades-digital-signatures
您需要创建与签名后格式相同的 PDF,删除所有签名 ByteRange,然后计算哈希值。
获得 CAdES 签名后,只需将其添加到 ByteRange。这将为您提供基线-T 签名。
对于基线 LT,您需要添加 DSS 以及所有使用的证书、OCSP 响应和 crls。
如果有更多问题,您可以使用我个人资料中的详细信息与我联系。
这是整个应用程序中最重要的部分,它将计算要为您签名的摘要。计算摘要时,signatureBytes 可以是 new byte[0].
public byte[] signDetached(SignatureParameters parameters, PDDocument document, byte[] signatureBytes, OutputStream out)
throws IOException, NoSuchAlgorithmException {
if (document.getDocumentId() == null) {
document.setDocumentId(parameters.getSignatureTime());
}
PDSignature signature = createSignatureDictionary(parameters);
SignatureOptions options = new SignatureOptions();
// Enough room for signature, timestamp and OCSP for baseline-LT profile.
options.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);
document.addSignature(signature, options);
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(out);
byte[] dataToSign = IOUtils.toByteArray(externalSigning.getContent());
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] digestBytes = digest.digest(dataToSign);
if (signatureBytes != null) {
externalSigning.setSignature(signatureBytes);
}
return digestBytes;
}
private PDSignature createSignatureDictionary(final SignatureParameters parameters) {
PDSignature signature = new PDSignature();
signature.setType(COSName.getPDFName("Sig"));
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
if (notEmpty(parameters.getSignerName())) {
signature.setName(parameters.getSignerName());
}
if (notEmpty(parameters.getContactInfo())) {
signature.setContactInfo(parameters.getContactInfo());
}
if (notEmpty(parameters.getLocation())) {
signature.setLocation(parameters.getLocation());
}
if (notEmpty(parameters.getReason())) {
signature.setReason(parameters.getReason());
}
// the signing date, needed for valid signature
final Calendar cal = Calendar.getInstance();
final Date signingDate = new Date(parameters.getSignatureTime());
cal.setTime(signingDate);
signature.setSignDate(cal);
return signature;
}
在对 github dss esig 存储库进行一些研究后,我找到了一个看起来正确的解决方案:
********************* 服务器 A 上 ********************* *
public class ServerA {
private static PAdESSignatureParameters signatureParameters;
private static DSSDocument documentToSign;
public static ExternalCMSPAdESService service;
public static void main(String[] args) throws Exception {
documentToSign = new FileDocument(new File("file.pdf"));
signatureParameters = new PAdESSignatureParameters();
signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
signatureParameters.setLocation("Luxembourg");
signatureParameters.setReason("DSS testing");
signatureParameters.setContactInfo("Jira");
signatureParameters.setGenerateTBSWithoutCertificate(true);
service = new ExternalCMSPAdESService(getOfflineCertificateVerifier());
byte[] documentDigest = computeDocumentDigest(documentToSign, signatureParameters);
// Embedded CAdES is generated by a third party
byte[] cmsSignedData = ServerB.getSignedCMSignedData(documentDigest);
service.setCmsSignedData(cmsSignedData);
DSSDocument finalDoc = service.signDocument(documentToSign, signatureParameters, null);
save(finalDoc);
}
private static void save(DSSDocument signedDocument) {
try (FileOutputStream fos = new FileOutputStream("DSS.pdf")) {
Utils.copy(signedDocument.openStream(), fos);
} catch (Exception e) {
Alert alert = new Alert(Alert.AlertType.ERROR, "Unable to save file : " + e.getMessage(), ButtonType.CLOSE);
alert.showAndWait();
return;
}
}
public static CertificateVerifier getOfflineCertificateVerifier() {
CertificateVerifier cv = new CommonCertificateVerifier();
cv.setDataLoader(new IgnoreDataLoader());
return cv;
}
protected static byte[] computeDocumentDigest(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters) {
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
final PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
return pdfSignatureService.digest(toSignDocument, parameters);
}
private static class ExternalCMSPAdESService extends PAdESService {
private static final long serialVersionUID = -2003453716888412577L;
private byte[] cmsSignedData;
public ExternalCMSPAdESService(CertificateVerifier certificateVerifier) {
super(certificateVerifier);
}
@Override
protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters,
final SignatureValue signatureValue) {
if (this.cmsSignedData == null) {
throw new NullPointerException("A CMS signed data must be provided");
}
return this.cmsSignedData;
}
public void setCmsSignedData(final byte[] cmsSignedData) {
this.cmsSignedData = cmsSignedData;
}
}
}
并且能够对计算出的散列进行签名:
********************* 在服务器 B 上 ********************* *
public class ServerB {
private static PAdESSignatureParameters signatureParameters;
private static DSSDocument documentToSign;
public static ExternalCMSPAdESService service;
/**
* Computes a CAdES with specific things for PAdES
*/
public static byte[] getSignedCMSignedData(byte[] documentDigest) throws Exception {
signatureParameters = new PAdESSignatureParameters();
signatureParameters.setSigningCertificate(getSigningCert());
signatureParameters.setCertificateChain(getCertificateChain());
signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
signatureParameters.setLocation("Luxembourg");
signatureParameters.setReason("DSS testing");
signatureParameters.setContactInfo("Jira");
CMSProcessableByteArray content = new CMSProcessableByteArray(documentDigest);
PadesCMSSignedDataBuilder padesCMSSignedDataBuilder = new PadesCMSSignedDataBuilder(getOfflineCertificateVerifier());
SignatureAlgorithm signatureAlgorithm = signatureParameters.getSignatureAlgorithm();
CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId());
SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = padesCMSSignedDataBuilder.getSignerInfoGeneratorBuilder(signatureParameters, documentDigest);
CMSSignedDataGenerator generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(signatureParameters, customContentSigner,
signerInfoGeneratorBuilder, null);
CMSUtils.generateDetachedCMSSignedData(generator, content);
SignatureTokenConnection signingToken = new Pkcs12SignatureToken("certificate.p12",
new KeyStore.PasswordProtection("123456".toCharArray()));
DSSPrivateKeyEntry privateKey = getKey("certificate.p12","123456");
SignatureValue signatureValue = signingToken.sign(new ToBeSigned(customContentSigner.getOutputStream().toByteArray()),
signatureParameters.getDigestAlgorithm(), privateKey);
customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId(), signatureValue.getValue());
generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(signatureParameters, customContentSigner, signerInfoGeneratorBuilder, null);
CMSSignedData cmsSignedData = CMSUtils.generateDetachedCMSSignedData(generator, content);
return DSSASN1Utils.getDEREncoded(cmsSignedData);
}
public static CertificateVerifier getOfflineCertificateVerifier() {
CertificateVerifier cv = new CommonCertificateVerifier();
cv.setDataLoader(new IgnoreDataLoader());
return cv;
}
public static List<CertificateToken> getCertificateChain() throws Exception {
List<CertificateToken> list = new ArrayList<>();
CertificateToken[] l = getKey("certificate.p12","123456").getCertificateChain();
for (int i = 0; i < l.length; i++) {
list.add(l[i]);
}
return list;
}
public static CertificateToken getSigningCert() throws Exception {
return getKey("certificate.p12","123456").getCertificate();
}
public static DSSPrivateKeyEntry getKey(String certificate, String pin) throws Exception {
try (Pkcs12SignatureToken signatureToken = new Pkcs12SignatureToken("certificate.p12",
new KeyStore.PasswordProtection("123456".toCharArray()))) {
List<DSSPrivateKeyEntry> keys = signatureToken.getKeys();
KSPrivateKeyEntry dssPrivateKeyEntry = (KSPrivateKeyEntry) keys.get(0);
DSSPrivateKeyEntry entry = signatureToken.getKey(dssPrivateKeyEntry.getAlias(),
new KeyStore.PasswordProtection("123456".toCharArray()));
return entry;
}
}
private static class ExternalCMSPAdESService extends PAdESService {
private static final long serialVersionUID = -2003453716888412577L;
private byte[] cmsSignedData;
public ExternalCMSPAdESService(CertificateVerifier certificateVerifier) {
super(certificateVerifier);
}
@Override
protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters,
final SignatureValue signatureValue) {
if (this.cmsSignedData == null) {
throw new NullPointerException("A CMS signed data must be provided");
}
return this.cmsSignedData;
}
public void setCmsSignedData(final byte[] cmsSignedData) {
this.cmsSignedData = cmsSignedData;
}
}
}
使用 IAIK 在 HSM 上签名,并使用 Europa DSS 组装 PAdES 文件的示例:
1/ 准备要使用 Europa DSS 签名的数据
CertificateFactory fact = CertificateFactory.getInstance("X.509");
X509Certificate cer = null;
try (FileInputStream is = new FileInputStream (certFile);) {
cer = (X509Certificate) fact.generateCertificate(is);
}
CertificateToken certificateToken = new CertificateToken(cer);
CertificateToken[] certificateChain = new CertificateToken[] {
certificateToken
};
PAdESSignatureParameters parameters = buildPAdESSignatureParameters(certificateToken, certificateChain);
public static PAdESSignatureParameters buildPAdESSignatureParameters(
CertificateToken signingCertificate,
final CertificateToken... certificateChain) {
PAdESSignatureParameters parameters = new PAdESSignatureParameters();
// We choose the level of the signature (-B, -T, -LT, -LTA).
// parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_T);
// parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_LT);
// parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_LTA);
parameters.setReason("Preuve de signature");
parameters.setLocation("PARIS");
parameters.setSigningCertificate(signingCertificate);
parameters.setCertificateChain(certificateChain);
return parameters;
}
2/ 使用 IAIK 使用 HSM(例如 Bull Proteccio)签名
String modulePath = "C:/data/applications/HSM/europa_dss/win64/nethsm.dll";
iaik.pkcs.pkcs11.Module module = iaik.pkcs.pkcs11.Module.getInstance(modulePath);
module.initialize(null);
Token token = retrieveToken(slotId, module);
System.out.println("module: " + infoModule(module));
Session session = null;
session = openSession(token, pincode);
// Get the SignedInfo segment that need to be signed.
DSSDocument toSignDocument = new FileDocument(toSignFile);
ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);
byte[] content = dataToSign.getBytes();
System.out.println("parameters: " + parameters);
ByteBuffer byteBuffer = ByteBuffer.wrap(content);
ByteBuffer signedData = signDataByKeyIdAndLabel(keyId, label, byteBuffer, session);
byte[] cmsContent = signedData.array();
System.out.println("signed data: " + Hex.encodeHexString( signedData.array() ) );
System.out.println("signing date: " + parameters.getSigningDate());
System.out.println("signing time: " + parameters.getSigningDate().getTime());
Key hsmPublicKey = findPublicKeyByIDAndLabel(session, keyId, label);
boolean verify = verifyHSM(session, hsmPublicKey, content, cmsContent);
System.out.println("public key: " + hsmPublicKey);
System.out.println("public key (cer): " + cer);
System.out.println("verify: " + verify);
closeSession(session);
private byte[] signHSM(Session session, Key key, byte[] data) throws TokenException {
session.signInit(Mechanism.get(PKCS11Constants.CKM_SHA256_RSA_PKCS), key);
return session.sign(data);
}
private boolean verifyHSM(Session session, Key key, byte[] data, byte[] signature) {
try {
session.verifyInit(Mechanism.get(PKCS11Constants.CKM_SHA256_RSA_PKCS), key);
session.verify(data, signature);
return true;
} catch (TokenException e) {
return false;
}
}
3/Assemble
assemble(
parameters,
cmsContent,
toSignFile,
signedFile,
service);
public static void assemble(
PAdESSignatureParameters parameters,
byte[] signatureValueBytes,
File toSignFile,
File signedFile,
PAdESService service)
throws IOException {
DSSDocument toSignDocument = new FileDocument(toSignFile);
SignatureValue signatureValue = new SignatureValue(SignatureAlgorithm.RSA_SHA256, signatureValueBytes);
DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);
signedDocument.save(signedFile.getAbsolutePath());
}
}
我正在尝试使用 DSS 对 PDF 文档进行签名,我的问题是我无法在服务器 A 中计算文档的哈希值,然后在服务器 B 中对其进行签名。
知道服务器 A 包含 PDF 文档并且在服务器 B 中我们检索用于签名的证书
我的问题是如何在不需要证书的情况下计算服务器 A 中文档的哈希值。然后发送到服务器B签名?
更新:
****** 哈希的准备和计算 ********
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
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);
FileDocument imageFile = new FileDocument("logo.png");
RemoteDocument fileImage = RemoteDocumentConverter.toRemoteDocument(imageFile);
DSSDocument image = RemoteDocumentConverter.toDSSDocument(fileImage);
// set an image
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); // Specifies the text size value (the default font size is 12pt)
textParameters.setFont(font);
textParameters.setTextColor(Color.BLUE);
textParameters.setSignerTextPosition(SignerTextPosition.RIGHT);
// Specifies a horizontal alignment of a text with respect to its area
textParameters.setSignerTextHorizontalAlignment(SignerTextHorizontalAlignment.LEFT);
// Specifies a vertical alignment of a text block with respect to a signature field area
textParameters.setSignerTextVerticalAlignment(SignerTextVerticalAlignment.TOP);
imageParameters.setTextParameters(textParameters);
FileDocument fileToSign = new FileDocument("file.pdf");
RemoteDocument fileSign = RemoteDocumentConverter.toRemoteDocument(fileToSign);
DSSDocument toSignDocument = RemoteDocumentConverter.toDSSDocument(fileSign);
byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);
DSSDocument signatureValue = SignHashDocument.signHash(hash);
DSSDocument signedDocument = pdfSignatureService.sign(toSignDocument, DSSUtils.toByteArray(signatureValue), parameters);
save(signedDocument);
****** 哈希签名 ********
// Create common certificate verifier
CommonCertificateVerifier commonCertificateVerifier = new CommonCertificateVerifier();
// Create CAdESService for signature
CAdESService service = new CAdESService(commonCertificateVerifier);
CAdESSignatureParameters parameters = new CAdESSignatureParameters();
DSSPrivateKeyEntry privateKey = getKey("certificate.p12","123456");
// We choose the level of the signature (-B, -T, -LT, -LTA).
parameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_B);
parameters.setSignaturePackaging(SignaturePackaging.ENVELOPING);
parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
// We set the signing certificate
parameters.setSigningCertificate(privateKey.getCertificate());
// We set the certificate chain
parameters.setCertificateChain(privateKey.getCertificateChain());
SignatureTokenConnection signingToken = new Pkcs12SignatureToken("certificate.p12",
new KeyStore.PasswordProtection("123456".toCharArray()));
convertByteArrayToFile(hashToSign,"filetosign.hash");
FileDocument fileToSign = new FileDocument("filetosign.hash");
RemoteDocument fileSign = RemoteDocumentConverter.toRemoteDocument(fileToSign);
DSSDocument toSignDocument = RemoteDocumentConverter.toDSSDocument(fileSign);
//ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);
ToBeSigned dataToSign = new ToBeSigned(hashToSign);
DigestAlgorithm digestAlgorithm = parameters.getDigestAlgorithm();
SignatureValue signatureValue = signingToken.sign(dataToSign, digestAlgorithm, privateKey);
DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);
return signedDocument;
******** PDF 错误:*********
一般
这是针对您问题的原始修订而写的。
您的代码提到了 PAdES。因此,当您说 尝试使用 DSS 签署 PDF 文档时,我假设您指的是集成 PAdES(不是分离的 CAdES 或 XAdES)签名。
创建集成的 PDF 签名(如 PAdES)需要首先准备 PDF 以便能够携带嵌入式签名,即将签名字典添加到现有或新的签名字段。这个签名字典里面包含了很多信息,签名时间,签名原因等等,还有一个占位符,方便后面嵌入一个CMS签名容器。然后对准备好的 PDF(占位符除外)进行哈希处理。
此外,您的代码提到您选择签名级别(-B、-T、-LT、-LTA)。
创建 PAdES Baseline LT 和 PAdES Baseline LTA 签名需要准备一个带有 PAdES Baseline T 签名的 PDF 并添加一组附加对象,具体取决于 T 签名的性质。
eSig DSS 可以为您做所有这些准备如果它有 PDF 可以准备。
因此,如果您只想从服务器 A 向 B 发送哈希值,则必须在服务器 A 上使用 eSig DSS 来完成大部分工作,而服务器 B 仅用作哑签名服务 return正在处理一个已签名的哈希值或至多一个可用于 PAdES 的 CMS 容器。
您是否可以在服务器 A 不知道证书的情况下执行此操作,取决于您是否希望证书详细信息出现在新签名的签名小部件中。创建 widget 外观是 PDF 准备步骤的一部分,所以如果你想要这样一个带有证书信息的 widget,服务器 A 需要知道证书,通常甚至 在请求签名之前 !
服务器 A 和 B 之间 运行 使用何种协议,由您决定。您只需要相应地实施 SignatureTokenConnection
即可在 eSig DSS 中的服务器 A 上使用以与服务器 B 通信。
以防你的做法
在您展示了来自两个服务器的代码之后,可以讨论您的具体方法。
在服务器 A 上,您使用 eSig DSS 准备 PAdES 签名并使用 SignatureValue
您的 SignHashDocument.signHash
调用 returns:
ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);
SignatureValue signatureValue = SignHashDocument.signHash(dataToSign);
DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);
即服务器 A 创建 CMS 签名容器,服务器 B 仅提供签名哈希。
除非您知道用于签名的证书并在 service.getDataToSign
调用之前将其设置在 parameters
中,否则这将无法工作。
原因是 CMS 容器在容器的无符号字节和(对于 PAdES)有符号字节中都包含对该证书的引用。对于无符号字节中的引用,理论上将证书与签名字节一起检索就足够了,但对于有符号字节中的引用,必须事先知道。
或者您可以尝试在服务器 B 上实现 CMS 容器的完整生成。
不过,这需要进行相当大的更改,并且您必须更深入地研究 PAdESService
来源。而不是上面在服务器 A 上引用的三行,您必须:
先获取一个PDF对象库和PDF签名服务
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory(); PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
第一次准备 PDF 进行签名并计算文档摘要,
byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);
将此文档摘要发送到后端(服务器 B),后者必须创建和 return 一个特殊的 CAdES 签名容器,而不仅仅是裸签名字节,
并再次准备 PDF 以进行签名并注入此签名容器:
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
现在应该为给定的文档哈希独立创建一个CMS签名容器,这个容器应该符合PAdES要求。 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;
辅助方法 getDataToSign
和 generateCMSSignedData
本质上是从 PAdESService
复制而来的;他们使用 signHash
提供的 padesCMSSignedDataBuilder
(你也可以将其设为这两种方法的另一个参数,而不是成员变量):
/** @see eu.europa.esig.dss.pades.signature.PAdESService#getDataToSign(DSSDocument, PAdESSignatureParameters) */
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);
}
/** @see eu.europa.esig.dss.pades.signature.PAdESService#generateCMSSignedData(DSSDocument, PAdESSignatureParameters, SignatureValue) */
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);
}
(SplitPAdESSigning 方法)
类 PadesCMSSignedDataBuilder
和 PAdESLevelBaselineB
由于可见性受限,复制如下:
/** @see eu.europa.esig.dss.cades.signature.CMSSignedDataBuilder */
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;
}
}
/** @see eu.europa.esig.dss.pades.signature.PAdESLevelBaselineB */
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 上。
这是可能的,可以在此处看到一个开源的完整实现 https://github.com/eideasy/eideasy-external-pades-digital-signatures
您需要创建与签名后格式相同的 PDF,删除所有签名 ByteRange,然后计算哈希值。
获得 CAdES 签名后,只需将其添加到 ByteRange。这将为您提供基线-T 签名。
对于基线 LT,您需要添加 DSS 以及所有使用的证书、OCSP 响应和 crls。
如果有更多问题,您可以使用我个人资料中的详细信息与我联系。
这是整个应用程序中最重要的部分,它将计算要为您签名的摘要。计算摘要时,signatureBytes 可以是 new byte[0].
public byte[] signDetached(SignatureParameters parameters, PDDocument document, byte[] signatureBytes, OutputStream out)
throws IOException, NoSuchAlgorithmException {
if (document.getDocumentId() == null) {
document.setDocumentId(parameters.getSignatureTime());
}
PDSignature signature = createSignatureDictionary(parameters);
SignatureOptions options = new SignatureOptions();
// Enough room for signature, timestamp and OCSP for baseline-LT profile.
options.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);
document.addSignature(signature, options);
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(out);
byte[] dataToSign = IOUtils.toByteArray(externalSigning.getContent());
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] digestBytes = digest.digest(dataToSign);
if (signatureBytes != null) {
externalSigning.setSignature(signatureBytes);
}
return digestBytes;
}
private PDSignature createSignatureDictionary(final SignatureParameters parameters) {
PDSignature signature = new PDSignature();
signature.setType(COSName.getPDFName("Sig"));
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
if (notEmpty(parameters.getSignerName())) {
signature.setName(parameters.getSignerName());
}
if (notEmpty(parameters.getContactInfo())) {
signature.setContactInfo(parameters.getContactInfo());
}
if (notEmpty(parameters.getLocation())) {
signature.setLocation(parameters.getLocation());
}
if (notEmpty(parameters.getReason())) {
signature.setReason(parameters.getReason());
}
// the signing date, needed for valid signature
final Calendar cal = Calendar.getInstance();
final Date signingDate = new Date(parameters.getSignatureTime());
cal.setTime(signingDate);
signature.setSignDate(cal);
return signature;
}
在对 github dss esig 存储库进行一些研究后,我找到了一个看起来正确的解决方案:
********************* 服务器 A 上 ********************* *
public class ServerA {
private static PAdESSignatureParameters signatureParameters;
private static DSSDocument documentToSign;
public static ExternalCMSPAdESService service;
public static void main(String[] args) throws Exception {
documentToSign = new FileDocument(new File("file.pdf"));
signatureParameters = new PAdESSignatureParameters();
signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
signatureParameters.setLocation("Luxembourg");
signatureParameters.setReason("DSS testing");
signatureParameters.setContactInfo("Jira");
signatureParameters.setGenerateTBSWithoutCertificate(true);
service = new ExternalCMSPAdESService(getOfflineCertificateVerifier());
byte[] documentDigest = computeDocumentDigest(documentToSign, signatureParameters);
// Embedded CAdES is generated by a third party
byte[] cmsSignedData = ServerB.getSignedCMSignedData(documentDigest);
service.setCmsSignedData(cmsSignedData);
DSSDocument finalDoc = service.signDocument(documentToSign, signatureParameters, null);
save(finalDoc);
}
private static void save(DSSDocument signedDocument) {
try (FileOutputStream fos = new FileOutputStream("DSS.pdf")) {
Utils.copy(signedDocument.openStream(), fos);
} catch (Exception e) {
Alert alert = new Alert(Alert.AlertType.ERROR, "Unable to save file : " + e.getMessage(), ButtonType.CLOSE);
alert.showAndWait();
return;
}
}
public static CertificateVerifier getOfflineCertificateVerifier() {
CertificateVerifier cv = new CommonCertificateVerifier();
cv.setDataLoader(new IgnoreDataLoader());
return cv;
}
protected static byte[] computeDocumentDigest(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters) {
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
final PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
return pdfSignatureService.digest(toSignDocument, parameters);
}
private static class ExternalCMSPAdESService extends PAdESService {
private static final long serialVersionUID = -2003453716888412577L;
private byte[] cmsSignedData;
public ExternalCMSPAdESService(CertificateVerifier certificateVerifier) {
super(certificateVerifier);
}
@Override
protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters,
final SignatureValue signatureValue) {
if (this.cmsSignedData == null) {
throw new NullPointerException("A CMS signed data must be provided");
}
return this.cmsSignedData;
}
public void setCmsSignedData(final byte[] cmsSignedData) {
this.cmsSignedData = cmsSignedData;
}
}
}
并且能够对计算出的散列进行签名:
********************* 在服务器 B 上 ********************* *
public class ServerB {
private static PAdESSignatureParameters signatureParameters;
private static DSSDocument documentToSign;
public static ExternalCMSPAdESService service;
/**
* Computes a CAdES with specific things for PAdES
*/
public static byte[] getSignedCMSignedData(byte[] documentDigest) throws Exception {
signatureParameters = new PAdESSignatureParameters();
signatureParameters.setSigningCertificate(getSigningCert());
signatureParameters.setCertificateChain(getCertificateChain());
signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
signatureParameters.setLocation("Luxembourg");
signatureParameters.setReason("DSS testing");
signatureParameters.setContactInfo("Jira");
CMSProcessableByteArray content = new CMSProcessableByteArray(documentDigest);
PadesCMSSignedDataBuilder padesCMSSignedDataBuilder = new PadesCMSSignedDataBuilder(getOfflineCertificateVerifier());
SignatureAlgorithm signatureAlgorithm = signatureParameters.getSignatureAlgorithm();
CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId());
SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = padesCMSSignedDataBuilder.getSignerInfoGeneratorBuilder(signatureParameters, documentDigest);
CMSSignedDataGenerator generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(signatureParameters, customContentSigner,
signerInfoGeneratorBuilder, null);
CMSUtils.generateDetachedCMSSignedData(generator, content);
SignatureTokenConnection signingToken = new Pkcs12SignatureToken("certificate.p12",
new KeyStore.PasswordProtection("123456".toCharArray()));
DSSPrivateKeyEntry privateKey = getKey("certificate.p12","123456");
SignatureValue signatureValue = signingToken.sign(new ToBeSigned(customContentSigner.getOutputStream().toByteArray()),
signatureParameters.getDigestAlgorithm(), privateKey);
customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId(), signatureValue.getValue());
generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(signatureParameters, customContentSigner, signerInfoGeneratorBuilder, null);
CMSSignedData cmsSignedData = CMSUtils.generateDetachedCMSSignedData(generator, content);
return DSSASN1Utils.getDEREncoded(cmsSignedData);
}
public static CertificateVerifier getOfflineCertificateVerifier() {
CertificateVerifier cv = new CommonCertificateVerifier();
cv.setDataLoader(new IgnoreDataLoader());
return cv;
}
public static List<CertificateToken> getCertificateChain() throws Exception {
List<CertificateToken> list = new ArrayList<>();
CertificateToken[] l = getKey("certificate.p12","123456").getCertificateChain();
for (int i = 0; i < l.length; i++) {
list.add(l[i]);
}
return list;
}
public static CertificateToken getSigningCert() throws Exception {
return getKey("certificate.p12","123456").getCertificate();
}
public static DSSPrivateKeyEntry getKey(String certificate, String pin) throws Exception {
try (Pkcs12SignatureToken signatureToken = new Pkcs12SignatureToken("certificate.p12",
new KeyStore.PasswordProtection("123456".toCharArray()))) {
List<DSSPrivateKeyEntry> keys = signatureToken.getKeys();
KSPrivateKeyEntry dssPrivateKeyEntry = (KSPrivateKeyEntry) keys.get(0);
DSSPrivateKeyEntry entry = signatureToken.getKey(dssPrivateKeyEntry.getAlias(),
new KeyStore.PasswordProtection("123456".toCharArray()));
return entry;
}
}
private static class ExternalCMSPAdESService extends PAdESService {
private static final long serialVersionUID = -2003453716888412577L;
private byte[] cmsSignedData;
public ExternalCMSPAdESService(CertificateVerifier certificateVerifier) {
super(certificateVerifier);
}
@Override
protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters,
final SignatureValue signatureValue) {
if (this.cmsSignedData == null) {
throw new NullPointerException("A CMS signed data must be provided");
}
return this.cmsSignedData;
}
public void setCmsSignedData(final byte[] cmsSignedData) {
this.cmsSignedData = cmsSignedData;
}
}
}
使用 IAIK 在 HSM 上签名,并使用 Europa DSS 组装 PAdES 文件的示例:
1/ 准备要使用 Europa DSS 签名的数据
CertificateFactory fact = CertificateFactory.getInstance("X.509");
X509Certificate cer = null;
try (FileInputStream is = new FileInputStream (certFile);) {
cer = (X509Certificate) fact.generateCertificate(is);
}
CertificateToken certificateToken = new CertificateToken(cer);
CertificateToken[] certificateChain = new CertificateToken[] {
certificateToken
};
PAdESSignatureParameters parameters = buildPAdESSignatureParameters(certificateToken, certificateChain);
public static PAdESSignatureParameters buildPAdESSignatureParameters(
CertificateToken signingCertificate,
final CertificateToken... certificateChain) {
PAdESSignatureParameters parameters = new PAdESSignatureParameters();
// We choose the level of the signature (-B, -T, -LT, -LTA).
// parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_T);
// parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_LT);
// parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_LTA);
parameters.setReason("Preuve de signature");
parameters.setLocation("PARIS");
parameters.setSigningCertificate(signingCertificate);
parameters.setCertificateChain(certificateChain);
return parameters;
}
2/ 使用 IAIK 使用 HSM(例如 Bull Proteccio)签名
String modulePath = "C:/data/applications/HSM/europa_dss/win64/nethsm.dll";
iaik.pkcs.pkcs11.Module module = iaik.pkcs.pkcs11.Module.getInstance(modulePath);
module.initialize(null);
Token token = retrieveToken(slotId, module);
System.out.println("module: " + infoModule(module));
Session session = null;
session = openSession(token, pincode);
// Get the SignedInfo segment that need to be signed.
DSSDocument toSignDocument = new FileDocument(toSignFile);
ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);
byte[] content = dataToSign.getBytes();
System.out.println("parameters: " + parameters);
ByteBuffer byteBuffer = ByteBuffer.wrap(content);
ByteBuffer signedData = signDataByKeyIdAndLabel(keyId, label, byteBuffer, session);
byte[] cmsContent = signedData.array();
System.out.println("signed data: " + Hex.encodeHexString( signedData.array() ) );
System.out.println("signing date: " + parameters.getSigningDate());
System.out.println("signing time: " + parameters.getSigningDate().getTime());
Key hsmPublicKey = findPublicKeyByIDAndLabel(session, keyId, label);
boolean verify = verifyHSM(session, hsmPublicKey, content, cmsContent);
System.out.println("public key: " + hsmPublicKey);
System.out.println("public key (cer): " + cer);
System.out.println("verify: " + verify);
closeSession(session);
private byte[] signHSM(Session session, Key key, byte[] data) throws TokenException {
session.signInit(Mechanism.get(PKCS11Constants.CKM_SHA256_RSA_PKCS), key);
return session.sign(data);
}
private boolean verifyHSM(Session session, Key key, byte[] data, byte[] signature) {
try {
session.verifyInit(Mechanism.get(PKCS11Constants.CKM_SHA256_RSA_PKCS), key);
session.verify(data, signature);
return true;
} catch (TokenException e) {
return false;
}
}
3/Assemble
assemble(
parameters,
cmsContent,
toSignFile,
signedFile,
service);
public static void assemble(
PAdESSignatureParameters parameters,
byte[] signatureValueBytes,
File toSignFile,
File signedFile,
PAdESService service)
throws IOException {
DSSDocument toSignDocument = new FileDocument(toSignFile);
SignatureValue signatureValue = new SignatureValue(SignatureAlgorithm.RSA_SHA256, signatureValueBytes);
DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);
signedDocument.save(signedFile.getAbsolutePath());
}
}