iText:使用java对PDF文档进行数字签名并观察java.security.NoSuchAlgorithmException错误

时间:2014-08-30 00:41:05

标签: java pdf itext keystore pkcs#11

我有一个带有PDF数字签名证书的SafeNet eToken 5100 USB令牌。我配置了一个windows和mac系统来使用它,我成功地在两台机器上用它的证书手动签名PDF文件。因此证书按预期工作。

我将令牌发送到我的网络托管公司,然后将它放在我的Linux(CentOS)服务器上。我在服务器上安装了SafeNet Authentication Client。

现在我正在尝试使用iText来应用签名。我正在阅读这本优秀书籍中的代码示例4.2:http://itextpdf.com/book/digitalsignatures。我的代码是:

    String config = "name = eToken5100_20130805\n" +
               "library = /usr/lib64/libeTPkcs11.so\n" +
               "slot = 0";  // create a dynamic conf file
    ByteArrayInputStream bais = new ByteArrayInputStream(config.getBytes());
    Provider providerPKCS11 = new SunPKCS11(bais);
    Security.addProvider(providerPKCS11);
    System.out.println(providerPKCS11.getName());
    BouncyCastleProvider providerBC = new BouncyCastleProvider();
    Security.addProvider(providerBC);

    KeyStore ks = KeyStore.getInstance("PKCS11", providerPKCS11);
    ks.load(null, K.PASS_TOKEN);
    String alias = (String)ks.aliases().nextElement();
    PrivateKey pk = (PrivateKey)ks.getKey(alias, K.PASS_TOKEN);
    Certificate[] chain = ks.getCertificateChain(alias);
    OcspClient ocspClient = new OcspClientBouncyCastle();
    TSAClient tsaClient = null;
    for (int i = 0; i < chain.length; i++) {
        X509Certificate cert = (X509Certificate)chain[i];
        String tsaUrl = CertificateUtil.getTSAURL(cert);
        if (tsaUrl != null) {
            tsaClient = new TSAClientBouncyCastle(tsaUrl);
            break;
        }
    }
    List<CrlClient> crlList = new ArrayList<CrlClient>();
    crlList.add(new CrlClientOnline(chain));
    sign(userFile, userFile_signed, chain, pk, DigestAlgorithms.SHA256, providerPKCS11.getName(),
                 CryptoStandard.CMS, "Test", "Ghent", crlList, ocspClient, tsaClient, 0);

当我运行此代码时,它会生成以下运行时错误:

stack trace: java.security.KeyStoreException: PKCS11 not found
Caused by: java.security.NoSuchAlgorithmException: no such algorithm: 
  PKCS11 for provider SunPKCS11-eToken5100_20130805

This link表示错误的密钥库类型可能会生成此错误。我检查了我的~glassfish/java/jdk7u25/jdk1.7.0_25/jre/lib/security/java.security文件,它使用了jks的密钥库。但是,将其更改为PKCS11会导致我的GlassFish服务器无法重新启动。所以我把它保持为jks

问题1:这会导致问题吗?如果是这样,如何解决?

问题2:我刚刚在上面的java代码中编写了名称eToken5100_20130805 ...但这个名称是否需要在某处匹配其他名称? (上面的Java代码是我输入此名称的唯一地方)。

问题3:我知道库是正确的,但我如何确定插槽号?我手动输入了0到6的插槽(只是猜测),它们每个都产生了上面显示的相同错误。此外,如果我输入插槽号7到10,则每次都会抛出PKCS11Exception CKR_SLOT_ID_INVALID。

我对这个话题知之甚少,但我试图以各种方式确定插槽号:

(A)我不确定以下语法是否正确:

# keytool -v -list -keystore NONE -storetype PKCS11 -providername SunPKCS11-eToken5100_20130805 -J-Djava.security.debug=sunpkcs11,pkcs11

但它返回此输出:

keytool error: java.security.NoSuchProviderException: no such provider: SunPKCS11-eToken5100_20130805
java.security.NoSuchProviderException: no such provider: SunPKCS11-eToken5100_20130805
at sun.security.jca.GetInstance.getService(GetInstance.java:83)
at sun.security.jca.GetInstance.getInstance(GetInstance.java:206)
at java.security.Security.getImpl(Security.java:698)
at java.security.KeyStore.getInstance(KeyStore.java:661)
at sun.security.tools.KeyTool.doCommands(KeyTool.java:765)
at sun.security.tools.KeyTool.run(KeyTool.java:340)
at sun.security.tools.KeyTool.main(KeyTool.java:333) 

(B)我也尝试使用modutil来查找插槽号:

# modutil -list -dbdir $HOME/.mozilla/firefox/*.default

但是我不确定它返回以下内容时意味着什么:

winscard_clnt.c:420:SCardEstablishContextTH() Your pcscd is too old and does not support CMD_VERSION
winscard_clnt.c:420:SCardEstablishContextTH() Your pcscd is too old and does not support CMD_VERSION

  Listing of PKCS #11 Modules
  -----------------------------------------------------------
  1. NSS Internal PKCS #11 Module
slots: 2 slots attached
status: loaded

 slot: NSS Internal Cryptographic Services
token: NSS Generic Crypto Services

 slot: NSS User Private Key and Certificate Services
token: NSS Certificate DB

2. eToken
library name: libeTPkcs11.so
 slots: 6 slots attached
 status: loaded

 slot: 
 token: 

 slot: 
 token: 

 slot: 
 token: 

 slot: 
 token: 

 slot: 
 token: 

 slot: 
 token: 

(C)最后,我试图运行

# pkcs11-tool --module /usr/lib64/libeTPkcs11.so --list-slots 

但这需要首先安装openSC以获取pkcs11-tool。当我尝试使用# yum install opensc.x86_64进行安装时,会出现以下错误:

Transaction Check Error: 
file /usr/lib64/libpcsclite.so.1.0.0 from install of pcsc-lite-libs-1.5.2-13.el6_4.x86_64
conflicts with file from package libpcsclite1-1.4.0-9.el6.x86_64`

我无法弄清楚如何克服这个错误。

我可以使用一些帮助来浏览这些不熟悉的错误,了解问题所在,然后解决问题。是否看起来插槽号确实不正确(插槽号可以很大,比如31310?到目前为止我假设它们是单个数字......),还是别的什么?

----------更新----------

我的linux机器为libpcsclite1安装了一个导致上述错误的软件包。我卸载了SafeNet Auth Client(SAC),删除了这个软件包,安装了pcsc-lite(使用yum),并重新安装了标准SAC。我还使用yum安装了opensc,所以我可以使用pkcs11-tool。我现在可以使用以下方式查看插槽:

# pkcs11-tool --module /usr/lib64/libeToken.so -L
Available slots:
Slot 0 (0x0): AKS xxxx 00 00
 token label:   my label
 token manuf:   SafeNet, Inc.
 token model:   eToken
 token flags:   rng, login required, PIN initialized, token initialized, other flags=0x200
 serial num  :  xxxxxxx
Slot 1 (0x1): 
  (empty)
Slot 2 (0x2): 
  (empty)
Slot 3 (0x3): 
  (empty)
Slot 4 (0x4): 
  (empty)
Slot 5 (0x5): 
  (empty)

现在我可以运行所有内容而不会产生运行时错误。但是,生成的PDF文件显示“至少有一个签​​名无效”。 [更新:我的错误,我正在查看错误的文件。签名的PDF文件显示有效签名。]

2 个答案:

答案 0 :(得分:1)

您的JVM使用的密钥库与您的情况无关。您的密钥存储在您的eToken上。这是您在使用这些行时尝试加载的密钥库:

KeyStore ks = KeyStore.getInstance("PKCS11", providerPKCS11);
ks.load(null, K.PASS_TOKEN);

因此,您的问题1无关紧要。顺便说一下:当我创建KeyStore的实例时,我没有通过提供者。如果您删除providerPKCS11会怎样?当您将提供程序添加到Security实例时,它应该找到提供程序。

在您的配置文件中,您选择了名称eToken5100_20130805,这很好。您可以使用任何您想要的名称。这回答了你的问题2。

关于你的问题3,如果您所引用的书(以及我是作者),如果你需要一个更简单的例子,那么如何找到4.1.2节中所需的插槽的例子,请在StackOverflow上阅读此问题的答案:java keytool with opensc pkcs#11 provider only works with debug option enabled

请注意,您已确定提供程序有效。您的配置文件已正确读取,否则您将看不到在异常中选择的名称(SunPKCS11-eToken5100_20130805)。问题是您无法加载密钥库。我发现了this post这个问题。我不确定它是否有帮助。

在任何情况下:您的尝试(A)永远不会起作用,因为keytool不知道配置文件,因此不知道您选择的名称。由于Linux上的工具之间存在冲突,您的尝试(C)失败。我不知道你在(B)中提到的modutil工具,但如果我看到输出,我会假设正确加载了访问令牌的库,并且服务器中有6个USB插槽,但在任何这些插槽中都没有USB令牌。也许就是这么简单:也许你的USB令牌没有插入服务器的USB插槽,也可能是插入的,但由于硬件驱动程序的问题,它没有被看到。

我先咨询托管公司。

答案 1 :(得分:0)

SlotID(在您的情况下由slot变量指定)是无符号长整数,由PKCS#11库创建者选择将使用的确切值,因此可能很难猜到正确的价值。我不是Java专家,但是通过查看Sun PKCS#11 Provider的文档,我会说你应该使用slotListIndex变量来指定PKCS#11库返回的可用插槽/标记列表中的位置。 / p>