前提是公司要弄一个电子签章的系统,目的是对 pdf 文档签名,然后验签。用户用阅读器查看 pdf 时,也要显示证书的有效性,验证时从场景来看,也不能简单把每个人的证书都加到信任列表中,希望能有个根证书一样的东西来验证。
我以前对证书加解密这块不是很懂,https 也是用的 Let's Encrypt 。后面补了相关知识,大致上是 CA 机构用自己的私钥加密我的公钥得到证书,然后客户信任并拥有这个 CA 的公钥,这样客户使用公钥解密加密后的证书,并从证书中得到我的公钥。不知道我理解的对不对?
然后回到 pdf,我想就是私钥签名,公钥验签。
然后查询了相关资料后,我使用 keytool 来生成证书,用 itext7 来对 pdf 文档签名,以下是步骤:
下面是签名的方法,修改的 itext7 的 demo:
public class C4_07_ClientServerSigning {
public static final String DEST = "/Users/xxx/Downloads/";
public static final String SRC = "/Users/xxx/Downloads/ApplicationForm.pdf";
public static final String CERT = "/Users/xxx/Documents/csr/pdf/aaa.cer";
public static final String KEYSTORE = "/Users/xxx/Documents/csr/pdf/aaa.keystore";
public static final char[] PASSWORD = "123456".toCharArray();
public static final String[] RESULT_FILES = new String[] {
"hello_server.pdf"
};
public static void main(String[] args) throws GeneralSecurityException, IOException {
File file = new File(DEST);
file.mkdirs();
CertificateFactory factory = CertificateFactory.getInstance("X.509");
Certificate[] chain = new Certificate[1];
chain[0] = factory.generateCertificate(new FileInputStream(CERT));
new C4_07_ClientServerSigning2().sign(SRC, DEST + RESULT_FILES[0],chain,PdfSigner.CryptoStandard.CMS,
"Test", "Ghent");
}
public void sign(String src, String dest, Certificate[] chain, PdfSigner.CryptoStandard subfilter,
String reason, String location) throws GeneralSecurityException, IOException {
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties());
// Create the signature appearance
Rectangle rect = new Rectangle(36, 648, 200, 100);
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
appearance
.setReason(reason)
.setLocation(location)
.setPageRect(rect)
.setPageNumber(1);
signer.setFieldName("sig");
IExternalDigest digest = new BouncyCastleDigest();
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(KEYSTORE), PASSWORD);
String alias = ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, provider.getName());
// Sign the document using the detached mode, CMS or CAdES equivalent.
signer.signDetached(digest, pks, chain, null, null, null,
0, subfilter);
}
}
现在的问题是,查看 pdf 时,阅读器(测试时用的福昕阅读器),还是显示的 “签名有效性未知,签名者身份未知,因为它和父证书都不包含在信任列表中”。
现在就没有头绪了,我觉得是我想错了,请各位有空帮忙看看,谢谢
1
chixinzei 2021-02-24 17:25:49 +08:00 3
https 原理再看一遍,CA 证书(含公钥)是直接被各大主流浏览器所信任的,所以客户不需要再次信任,你找 CA 买的证书会通过浏览器自行与 CA 交互认证成功。 你自己签的证书(公钥)客户端并没有对应的 CA 可以验证,所以除非客户手动信任该证书,否则证书都是未知的。
|
2
chixinzei 2021-02-24 17:27:52 +08:00
补充,浏览器不会自行跟 CA 交互认证,而是根据内置 CA 证书进行算法验证匹配是否合法。
|
3
chinvo 2021-02-24 17:30:19 +08:00 via iPhone 1
PDF 签名需要 Adobe 认证的 CA 签的证书,否则提示不可信
|
4
matenshi OP @chixinzei 是这样,可是我第 6 步把我这个自签的根证书导出来,并手动添加到阅读器的信任列表和安装到本机的受信任证书里了,这样不算吗。。。
|
5
matenshi OP @chinvo 嗯,现在用的不是 adobe reader 看的,不过如果我把通过 ca 的签名过后的用户证书先手动添加到受信任列表里的话,也是可以的,但是业务场景不允许。
|
6
chinvo 2021-02-24 17:44:03 +08:00 via iPhone
如果不能添加自签名证书(通过安装包安装)的话,一般来说只能找受信任的 CA 合作或者买他们的中间证书。
|
7
matenshi OP @chinvo 不说能不能通过安装包安装,目前设想是可以添加自签名证书的(就是让用户电脑都手动添加这个证书)。 以前是买过安政通的电子签章软件,那个是有个专门的客户端来查看 pdf,现在新项目领导说自己弄个功能简单的。
|
9
joesonw 2021-02-24 18:18:37 +08:00 1
https://helpx.adobe.com/acrobat/kb/approved-trust-list1.html 这里有 Adobe 信任的 CA 商.
|
10
matenshi OP @chinvo 现在的问题就是这个,我通过 ```keytool -exportcert ```命令把我的自签证书导出为 cer 文件,然后在阅读器上把该证书添加为受信任的根证书,某种意义上也算内置了自己的 CA 了吧。然后还是提示上面那个问题,我查看了经过签名的证书,证书链上的顶层确实是我导出的那个已经加入了受信任的根证书,结果还是这样。
|
11
KuroNekoFan 2021-02-24 20:22:31 +08:00 via iPhone 1
好像已经有第三方的系统了
esign.cn |
12
matenshi OP @KuroNekoFan 谢谢,不过第三方的目前先不做考虑,因为要实现的功能比较简单。
|
13
hysys32 2021-02-25 07:45:58 +08:00 via iPhone 1
Adobe 官方有个 echosign……我们公司各种签名都用它
|
14
teawithlife 2021-02-25 08:54:07 +08:00 1
@matenshi #5 从 5 楼的描述来说,是不是这样的情况:
1. 你把 CA 签名过的用户证书加入信任列表,阅读器认证通过 2. 你把 CA 证书加入信任列表,删去用户证书,阅读器认证失败 如果是这样的话,问题应该出在第 4 步后少了一步,你要把用户证书和 CA 证书的公钥合并成一个证书文件,这样才能形成一个证书链,阅读器信任这个合并的证书文件后,才能从 CA 开始进行认证。如果有多级 CA,比如 CA1 签名 CA2,CA2 签名 CA3,CA3 签名用户证书,那这四张证书都要合并成一个文件才行 原理应该就是这样,但是具体的操作你得自己找找 |
15
kikikiabc 2021-02-25 09:01:49 +08:00 via iPhone 1
既然 CA 都自己建了,PDF 阅读器也自己搞一个不就行了。操作系统里面的信任关系,其他软件可以自行选择是否使用。比如 Firefox 浏览器或 AdobePdf 这些都自己搞了一套证书信任体系的。
就像手机上的静音键,理论上是手机操作系统设置了一个应该静音的标记,但 App 是否遵守得靠自觉。 |
16
a1274598858 2021-02-25 09:24:33 +08:00 1
@chinvo adobe 阅读器信不信任根都不影响..只要 CA 证书是国内有资质的公司颁发的,并且 pdf 验签没有被篡改,这份文件都是有效的
|
17
type 2021-02-25 09:36:51 +08:00 3
楼主目前的方案适合公司内部自用,如果涉及与外部相对方的签名,这个是不具备法律效力的
|
18
matenshi OP 解决了,问题有点像#14 楼说的,证书链的问题,不过问题是出在用 itext7 签名的时候。
看代码我构造的数组长度只为 1,并且就导入了导出的二级证书,应该把根证书也导入进来,顺序的话是根证书在后头,这样就可以了。 itext 还有另一个方法,就是通过 KeyStore 对象的 getCertificateChain 方法获取证书链,不过这样就需要把签名(正常的请看就是 CA 认证)后的证书(上面第 4 步生成的)导入到密钥库中才行。 至于电子签章应该有的基本功能如 制章(在我看来就是弄个图片?)、盖章、撤章、验证之类的 itext7 都有提供相关的方法。 最后这就像#17 楼说的,只能内部玩玩,并且 itext7 是 APGL 协议的。 谢谢各位! |