1

次の参考文献を読んでください。

ハッシュコード:

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 ページを追加します

4

1 に答える 1

1

PdfPkcs7の使用

サーバー側のコードには、レンジ ストリーム ダイジェストの計算後、データを Web ページに転送する前に、次のブロックが含まれます。

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);

手元の場合、これは必要ありません。使用する外部署名 API が署名されたダイジェストを返すだけの場合にのみ必要です。その場合、PdfPKCS7インスタンスは CMS/PKCS#7 署名コンテナを構築します。一方、あなたは知っている API を使用します。

CAPICOM のドキュメントによると、署名付きの応答は PKCS#7 形式です。

したがって、インスタンスを使用する必要はなく、(要点を言えば) 使用してはなりません。PdfPKCS7

sign.js は何に署名しますか

サーバー側hash変数の内容は、署名するデータのハッシュ ダイジェスト値です。したがって、フロントエンド、つまりそこで使用される sign.js は、署名に入れるメッセージ ダイジェスト属性値を取得するために再度ハッシュしてはなりません。

しかし、IE の sign.js 署名メソッドは最終的に実行されます

var signedData = new ActiveXObject("CAPICOM.SignedData");

// Set the data that we want to sign
signedData.Content = src;

SignedData.Content、一方、次のように文書化されています

コンテンツ 読み取り/書き込み署名されるデータ。

( msdn: "SignedData オブジェクト" )

したがって、バックエンドからのハッシュは、署名するデータのハッシュとしてではなく、署名するデータとして使用されます。実際に2回ハッシュするため、ハッシュ値が間違っています。

したがって、範囲内のストリーム全体を送信する必要があるように見えますが、これはあまり実用的ではありません...

「しかし、以前は CAPICOM を使用してサンプルに署名していました...」

実際、一部の古い iTextSharp (バージョン 4.x) 署名の例では CAPICOM が使用されていました。しかし、そのコードが機能したのは、PDF 署名タイプadbe.pkcs7.sha1の署名を作成したためです。範囲指定ストリームの SHA1 ハッシュは、PKCS#7 署名によって埋め込まれ、署名されたデータです。

これはもはや本当の選択肢ではありません。

  • 重大なコンテキストでは無効な SHA1 の使用が必要であり、
  • その使用は、少なくとも ISO 32000-1 (2008) 以降は推奨されておらず、ISO 32000-2 (開発中) では公式に廃止される予定です。
于 2015-03-13T14:31:20.640 に答える