PDF長期署名(PAdES)をJavaで行う方法
要素技術
PDF長期署名(PAdES)
PDF長期署名(PAdES)はPDFのための長期署名の仕様です。
標準仕様は以下よりダウンロード可能となっています。
ETSIダウンロードページ
PAdESでは以下の順にPDFに署名を追加していきます(増分更新)。
- ES(署名基本)
- ES-T(署名タイムスタンプ)
- 検証情報の埋め込み
- 長期保管状態
最後の署名に利用した証明書の有効期間が切れ失効する前に、より未来日の有効期間を持つ証明書にて次の署名を行うことによって長期にわたる証明を実現しています。
ES(署名基本)
電子署名のみが付与された状態。誰が何をを証明できる。
(通常証明書の有効期間は1〜3年)
ES-T(署名タイムスタンプ)
電子署名+タイムスタンプが付与された状態。誰が何をに加えていつを証明できる。
ドキュメントタイムスタンプ+検証情報の埋め込み
最後の署名に利用した証明書の有効期間内にドキュメントタイムスタンプ(証明書の有効期間は10年)を付与します。
また、署名に利用した全ての証明書の検証情報を収集し、埋め込みを行います。
長期保管状態
ドキュメントタイムスタンプの有効期間が切れる前に、再度3.ドキュメントタイムスタンプ+検証情報の埋め込みを行うことにより、有効期間の延長を行います。
サンプルソースを元に署名方法を確認する
次はJavaでPDF操作を行うライブラリであるPDFBoxのサンプルソースを元に、
電子署名をどのように行うのかを見ていきたいと思います。
まずは、svnコマンドでPDFBoxのソースを取得してください。
svn checkout https://svn.apache.org/repos/asf/pdfbox/trunk/
電子署名を行うサンプルソースは以下のパッケージに格納されています。
org.apache.pdfbox.examples.signature
前提
PDFBoxでは電子署名を行うためのインターフェイスとして以下を提供しており、署名処理はここに実装します。
package org.apache.pdfbox.pdmodel.interactive.digitalsignature; publicinterface SignatureInterface { byte[] sign(InputStream content) throws IOException; }
ES(署名基本)/ES-T(署名タイムスタンプ)
publicclass CreateSignature extends CreateSignatureBase { // 一部のみ抜粋 public CreateSignature(KeyStore keystore, char[] pin) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException, IOException { super(keystore, pin); } ・ ・ ・ publicvoid signDetached(PDDocument document, OutputStream output) throws IOException { // ①署名の属性を指定、署名の/Filter、/Subfilter等の情報を指定。 PDSignature signature = new PDSignature(); signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); signature.setName("Example User"); signature.setLocation("Los Angeles, CA"); signature.setReason("Testing"); ・ ・ ・ // ②第一引数に署名の属性、第二引数に署名処理を行うクラスを設定している。 // このサンプルソースでは親のCreateSignatureBaseでSignatureInterfaceの実装を提供しているため自分を設定。 document.addSignature(signature, this, signatureOptions); // ③PDFを増分更新している。 document.saveIncremental(output); }
publicabstractclass CreateSignatureBase implements SignatureInterface { // 一部のみ抜粋 @Override publicbyte[] sign(InputStream content) throws IOException { // cannot be done private (interface) try { // ④コンストラクタではKeyStoreが渡され、 // KeyStoreから取得した秘密鍵および証明書がクラス変数として保持されている。 // その値とPDFをもとに署名情報を生成する。 List<Certificate> certList = new ArrayList<>(); certList.addAll(Arrays.asList(certificateChain)); Storecerts = new JcaCertStore(certList); CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate.getInstance(certificateChain[0].getEncoded()); ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey); gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1Signer, new X509CertificateHolder(cert))); gen.addCertificates(certs); CMSProcessableInputStream msg = new CMSProcessableInputStream(content); CMSSignedData signedData = gen.generate(msg, false); // ⑤TSA(タイムスタンプ局)のURLが指定されていた場合は、タイムスタンプ局からタイムスタンプトークンを取得し、署名タイムスタンプを追加。 if (tsaUrl != null && tsaUrl.length() > 0) { ValidationTimeStamp validation = new ValidationTimeStamp(tsaUrl); signedData = validation.addSignedTimeStamp(signedData); } returnsignedData.getEncoded(); } catch (GeneralSecurityException | CMSException | OperatorCreationException e) { thrownew IOException(e); } } }
ドキュメントタイムスタンプ+検証情報の埋め込み
ドキュメントタイムスタンプのサンプルソース
publicclass CreateSignedTimeStamp implements SignatureInterface { // 一部のみ抜粋 public CreateSignedTimeStamp(String tsaUrl) { this.tsaUrl = tsaUrl; } ・ ・ ・ publicvoid signDetached(PDDocument document, OutputStream output) throws IOException { // ①署名の属性を指定、署名の/Type、/Filter、/Subfilter等の情報を指定。 // Type=COSName.DOC_TIME_STAMP // Subfilter=COSName.getPDFName("ETSI.RFC3161”) // を指定している。 PDSignature signature = new PDSignature(); signature.setType(COSName.DOC_TIME_STAMP); signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); signature.setSubFilter(COSName.getPDFName("ETSI.RFC3161")); // No certification allowed because /Reference not allowed in signature directory // see ETSI EN 319 142-1 Part 1 and ETSI TS 102 778-4 // http://www.etsi.org/deliver/etsi_en%5C319100_319199%5C31914201%5C01.01.00_30%5Cen_31914201v010100v.pdf // http://www.etsi.org/deliver/etsi_ts/102700_102799/10277804/01.01.01_60/ts_10277804v010101p.pdf // ②署名の属性と、署名を行うクラスを設定している。 document.addSignature(signature, this); // ③PDFを増分更新している。 document.saveIncremental(output); } @Override publicbyte[] sign(InputStream content) throws IOException { // ④コンストラクタで指定されたTSAのURLを元にタイムスタンプトークンを取得し、ドキュメントタイムスタンプを追加。 ValidationTimeStamp validation; try { validation = new ValidationTimeStamp(tsaUrl); returnvalidation.getTimeStampToken(content); } catch (NoSuchAlgorithmException e) { LOG.error("Hashing-Algorithm not found for TimeStamping", e); } returnnewbyte[] {}; } }
検証情報の埋め込みのサンプルソース
package org.apache.pdfbox.examples.signature.validation; publicclass AddValidationInformation { // 一部のみ抜粋 privatevoid doValidation(String filename, OutputStream output) throws IOException { certInformationHelper = new CertInformationCollector(); CertSignatureInformation certInfo; try { certInfo = certInformationHelper.getLastCertInfo(document, filename); } catch (CertificateProccessingException e) { thrownew IOException("An Error occurred processing the Signature", e); } if (certInfo == null) { thrownew IOException( "No Certificate information or signature found in the given document"); } PDDocumentCatalog docCatalog = document.getDocumentCatalog(); COSDictionary catalog = docCatalog.getCOSObject(); catalog.setNeedToBeUpdated(true); COSDictionary dss = getOrCreateDictionaryEntry(COSDictionary.class, catalog, "DSS"); addExtensions(docCatalog); // 収集した各種検証情報の埋め込みを行っている vriBase = getOrCreateDictionaryEntry(COSDictionary.class, dss, "VRI"); ocsps = getOrCreateDictionaryEntry(COSArray.class, dss, "OCSPs"); crls = getOrCreateDictionaryEntry(COSArray.class, dss, "CRLs"); certs = getOrCreateDictionaryEntry(COSArray.class, dss, "Certs"); addRevocationData(certInfo); addAllCertsToCertArray(); // write incremental document.saveIncremental(output); } }