使用PKCS#11和iText使用eID对PDF进行签名

时间:2014-03-04 17:20:11

标签: pdf itext digital-signature

http://itextpdf.com/book/digitalsignatures中跟随“使用智能卡和PKCS#11签署文档”主题并创建类似于提供的代码示例之后,签名文件签名在Adobe Reader中无效,签名外观具有不可否认证书的名称(即eID所有者的名称),但在Adobe Reader的签名面板中显示:

Signed by Unknown

验证时发生错误: Error message

我正在使用 Gemalto PinPad 葡萄牙语eID pteidpkcs11.dll与位于C:\ Windows \ System32中的eID中间件软件一起安装。< / p>

我试过了:

  • 无效检查
  • 手动创建证书链,因为ks.getCertificateChain("CITIZEN SIGNATURE CERTIFICATE");返回的证书[]只有签名证书

4 个答案:

答案 0 :(得分:3)

作为替代方案,您只需使用www.poreid.org上提供的java组件,即可使用葡萄牙语开发卡(CartãodeCidadão)进行签名。它也可以在maven中央存储库中使用artifactid poreid

以下是基于itext documentation

中提供的示例的示例
public void createPdf(String filename) throws IOException, DocumentException {
    Document document = new Document();
    PdfWriter.getInstance(document, new FileOutputStream(filename));
    document.open();
    document.add(new Paragraph("Assinado com o Cartão de Cidadão!"));
    document.close();
}

public void signPdf(String src, String dest)
    throws IOException, DocumentException, GeneralSecurityException {
    KeyStore ks = KeyStore.getInstance(POReIDConfig.POREID);
    ks.load(null);
    PrivateKey pk = (PrivateKey) ks.getKey(POReIDConfig.AUTENTICACAO, null);
    Certificate[] chain = ks.getCertificateChain(POReIDConfig.AUTENTICACAO);

    // reader and stamper
    PdfReader reader = new PdfReader(src);
    FileOutputStream os = new FileOutputStream(dest);
    PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');

    // appearance
    PdfSignatureAppearance appearance = stamper .getSignatureAppearance();
    appearance.setReason("qualquer motivo");
    appearance.setLocation("qualquer localização");
    appearance.setVisibleSignature(new Rectangle(72, 732, 144, 780), 1, "primeira assinatura");

    // digital signature
    ExternalSignature es = new PrivateKeySignature(pk, "SHA-256", POReIDConfig.POREID);
    ExternalDigest digest = new ProviderDigest(null); // find provider
    MakeSignature.signDetached(appearance, digest, es, chain, null, null, null, 0, CryptoStandard.CMS);
}


public static void main(String[] args) throws DocumentException, IOException, GeneralSecurityException {
    Security.addProvider(new POReIDProvider());

    App exemplo = new App();
    exemplo.createPdf("/home/quim/exemplo.pdf");
    exemplo.signPdf("/home/quim/exemplo.pdf","/home/quim/exemplo.assinado.pdf");
}

答案 1 :(得分:2)

提供的代码示例尝试获取签名证书的PrivateKey,我发现它很奇怪,但认为它只是用作参考。浏览用户取消PinPad中的进程时触发的异常的堆栈跟踪给了我以下想法,幸运的是,这解决了这个问题:

  1. 创建自定义com.itextpdf.text.pdf.security.ExternalSignature实施
  2. 实现一个实用程序类,使用sun.security.pkcs11.wrapper.PKCS11包装器与您的eID pkcs11 dll(在我的情况下, pteidpkcs11.dll )进行交互,并提供一个接收字节的签名方法[ ]消息然后被发送到SmartCard阅读器进行签名,并返回此操作的byte []结果
  3. 在CustomExternalSignature.sign(...)
  4. 中使用实用程序类

    如果您正在为葡萄牙语eID CartãoCidadão进行开发,可以使用一些提示:

    • 对于上一个列表的第二项,我正在使用AndréBarbosa创建的名为pteid4j的开源项目中的PTeID4JPKCS11类,您只需要调用PTeID4JPKCS11.getInstance().sign(...);
    • 关于ExternalSignature接口所需的哈希加密算法,哈希为 SHA-1 ,加密<强> RSA

答案 2 :(得分:1)

我一直在使用葡萄牙公民卡进行PDF文档的数字签名,这就是我所拥有的:

public void signCAdES(...) {
    String pkcs11Config = "name=GemPC" + "\n" + "library=C:\\WINDOWS\\SysWOW64\\pteidpkcs11.dll";
    ByteArrayInputStream configStream = new ByteArrayInputStream(pkcs11Config.getBytes());
    Provider pkcs11Provider = new sun.security.pkcs11.SunPKCS11(configStream);

    //provider_name: SunPKCS11-GemPC
    Security.addProvider(pkcs11Provider);

    javax.security.auth.callback.CallbackHandler cmdLineHdlr = new DialogCallbackHandler();

    KeyStore.Builder builder = KeyStore.Builder.newInstance("PKCS11", pkcs11Provider,
            new KeyStore.CallbackHandlerProtection(cmdLineHdlr));
    KeyStore ks= builder.getKeyStore();

    PdfReader reader = new PdfReader(src);
    FileOutputStream os = new FileOutputStream(dest);

    PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', new File(tempPath), true);
    PdfSignatureAppearance appearance = stamper.getSignatureAppearance();

    appearance.setReason(reason);
    appearance.setLocation(location);
    appearance.setCertificationLevel(level);

    String alias = "CITIZEN SIGNATURE CERTIFICATE";

    //certificates from electronic card and resources folder
    Certificate[] certs = getSignatureCertificatesChain(ks);

    PrivateKey pk = (PrivateKey) ks.getKey(alias, null);

    ExternalSignature es = new PrivateKeySignature(pk, "SHA-1", pkcs11Provider.getName());
    ExternalDigest digest = new BouncyCastleDigest();

    MakeSignature.signDetached(appearance, digest, es, certs, null, null, null, 0, MakeSignature.CryptoStandard.CADES);
}

我必须建立证书链( getSignatureCertificatesChain(ks)),因为 ks.getCertificateChain(“CITIZEN SIGNATURE CERTIFICATE”)只提供一个证书,然后是卡本身没有所有证书,所以我必须在 pki.cartaodecidadao.pt 网站上找到丢失的证书并将它们放在资源文件夹中。基本上,我使用卡中和资源文件夹中的两个证书来构建我的链,方法是将它们与 certificate.getIssuerX500Principal()。getName() certificate.getSubjecX500Principal()中的值相关联。 .getName()(不同的卡可以使用相同类型的不同证书,因为有效性可能会有所不同,因此在同一类型中可以有004或008)。

根据我的理解,对CAdES( MakeSignature.CryptoStandard.CADES )的itext支持是最新的,但您需要使用它,因为使用MakeSignature.CryptoStandard.CMS可能会导致签名不满足CAdES的所有标准(例如,缺少签名证书属性 - 请参阅http://docbox.etsi.org/ESI/Open/Latest_Drafts/prEN-319122-1v003-CAdES-core-STABLE-DRAFT.pdf)。

此代码认为唯一的小问题是可能缺少某些可选属性。我使用了此工具https://github.com/arhs/sd-dss中的验证程序,当生成的签名通过验证时,它仍然会发出缺少 issuer-serial 属性的警告。我已创建帖子,希望有人知道如何在此处添加属性:CAdES Digital Signature

答案 3 :(得分:0)

 ks.getCertificateChain("CITIZEN SIGNATURE CERTIFICATE") 

应该返回证书链。

我在哪里可以找到有效的在线工具?

建议:致电负责中间件的组织并寻求支持。