次の参考文献を読んでください。
- iText デジタル署名のホワイト ペーパー、およびC# の例。(具体的には第 4 章) 興味のある方のために、PDF 署名プロセスの 優れた簡潔な要約をもう 1 つ紹介します。
- CAPICOM のドキュメント。
- オンラインの例/質問はこちら、および iText メーリング リストのアーカイブ (こちらやこちらなど) にあります。
ハッシュコード:
BouncyCastle.X509Certificate[] chain = Utils.GetSignerCertChain();
reader = Utils.GetReader();
MemoryStream stream = new MemoryStream();
using (var stamper = PdfStamper.CreateSignature(reader, stream, '\0'))
{
PdfSignatureAppearance sap = stamper.SignatureAppearance;
sap.SetVisibleSignature(
new Rectangle(36, 740, 144, 770),
reader.NumberOfPages,
"SignatureField"
);
sap.Certificate = chain[0];
sap.SignDate = DateTime.Now;
sap.Reason = "testing web context signatures";
PdfSignature pdfSignature = new PdfSignature(
PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED
);
pdfSignature.Date = new PdfDate(sap.SignDate);
pdfSignature.Reason = sap.Reason;
sap.CryptoDictionary = pdfSignature;
Dictionary<PdfName, int> exclusionSizes = new Dictionary<PdfName, int>();
exclusionSizes.Add(PdfName.CONTENTS, SIG_BUFFER * 2 + 2);
sap.PreClose(exclusionSizes);
Stream sapStream = sap.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(
sapStream,
DigestAlgorithms.SHA256
);
// is this needed?
PdfPKCS7 sgn = new PdfPKCS7(
null, chain, DigestAlgorithms.SHA256, true
);
byte[] preSigned = sgn.getAuthenticatedAttributeBytes(
hash, sap.SignDate, null, null, CryptoStandard.CMS
);
var hashedValue = Convert.ToBase64String(preSigned);
}
簡単なテスト - 最初のページ要求でダミーの Pdf ドキュメントが作成され、ハッシュが計算され、非表示の入力フィールドに Base64 エンコードされて配置されます。(hashedValue上記)
次に、クライアント側で CAPICOM を使用してフォームを POST し、ユーザーの署名付き応答を取得します。
PdfSignatureAppearance sap = (PdfSignatureAppearance)TempData[TEMPDATA_SAP];
PdfPKCS7 sgn = (PdfPKCS7)TempData[TEMPDATA_PKCS7];
stream = (MemoryStream)TempData[TEMPDATA_STREAM];
byte[] hash = (byte[])TempData[TEMPDATA_HASH];
byte[] originalText = (Encoding.Unicode.GetBytes(hashValue));
// Oid algorithm verified on client side
ContentInfo content = new ContentInfo(new Oid("RSA"), originalText);
SignedCms cms = new SignedCms(content, true);
cms.Decode(Convert.FromBase64String(signedValue));
// CheckSignature does not throw exception
cms.CheckSignature(true);
var encodedSignature = cms.Encode();
/* tried this too, but no effect on result
sgn.SetExternalDigest(
Convert.FromBase64String(signedValue),
null,
"RSA"
);
byte[] encodedSignature = sgn.GetEncodedPKCS7(
hash, sap.SignDate, null, null, null, CryptoStandard.CMS
);
*/
byte[] paddedSignature = new byte[SIG_BUFFER];
Array.Copy(encodedSignature, 0, paddedSignature, 0, encodedSignature.Length);
var pdfDictionary = new PdfDictionary();
pdfDictionary.Put(
PdfName.CONTENTS,
new PdfString(paddedSignature).SetHexWriting(true)
);
sap.Close(pdfDictionary);
そのため、ハッシュ部分、署名部分、またはその両方を台無しにしているかどうかはわかりません。上記の署名コード スニペットとクライアント コード (表示されていません) では、署名検証コードと思われるものを呼び出していますが、これは私にとって初めてのことなので、これも間違っている可能性があります。PDFを開くと、悪名高い「ドキュメントは署名されてから変更されたか破損しています」という無効な署名メッセージが表示されます。
クライアント側のコード (私が作成したものではありません)は、ここにあります。ソースに変数の命名エラーがあり、修正されました。参考までに、CAPICOM のドキュメントには、署名された応答は PKCS#7 形式であると記載されています。
編集 2015-03-12 :
@mkl からのいくつかの素晴らしい指針とさらなる調査の後、このシナリオでは CAPICOM は実質的に使用できないようです。明確に文書化されていませんが (他に何が新しいのですか?) hereおよびhereによると、CAPICOM はEncoding.Unicodeデジタル署名を作成するための入力として utf16 文字列 (.NET 内) を想定しています。そこから、長さが奇数の場合に受け取ったデータが何であれ、(前の文のどのソースが正しいかに応じて) パディングまたは切り捨てます。つまり、署名の作成は、 PdfSignatureAppearance.GetRangeStream ()Streamによって返される長さが奇数の場合、常に失敗します。多分私はラッキーです作成する必要がありますオプション: 範囲指定されたストリームの長さが偶数の場合は署名し、InvalidOperationException奇数の場合はスローします。(悲しいユーモアの試み)
参考までに、ここにテスト プロジェクトを示します。
編集 2015-03-25 :
このループを閉じるには、VS 2013 ASP.NET MVC プロジェクトへのリンクを次に示します。最善の方法ではないかもしれませんが、問題に対して完全に機能するソリューションを提供します。上記のように、CAPICOM の奇妙で柔軟性のない署名の実装により、考えられる解決策には、2 回目のパスと、PdfSignatureAppearance.GetRangeStream() (再び) の戻り値が奇数の場合に余分なバイトを挿入する方法が必要になる可能性があることがわかっていましたStream.Length。私は、PDF コンテンツをパディングするという長くて難しい方法を試すつもりでしたが、幸いなことに、同僚はパディングがはるかに簡単であることに気付きましたPdfSignatureAppearance.Reason。iText[Sharp] で何かを行うために 2 回目のパスが必要になることは、前例のないことではありません。ドキュメント ページのヘッダー/フッターに x/y ページを追加します。