PDF への署名は、その後の変更を禁じません。署名はバイト範囲を固定するだけであり、インクリメンタル更新はその後に新しいバイトを追記します。そのため、文書が新しい内容を手に入れても署名は数学的に有効であり続けます。その内容が許容できるかはポリシーの問題であり、DocMDP は作者がポリシーを表明する場所です。変更の完全な禁止、フォーム入力と署名のみ、あるいはそれらに注釈を加えたものです。これを強制するには、実際に何が変わったかを分類することが必要で、それが AnalyzeModifications の仕事です。以前のリビジョンを指定すれば、全体の判定を GetModificationLevel から、差分ごとのレベル、オブジェクト番号、説明を各ファインディングアクセサから読み取れます
これが揃えば、DocMDP の強制は 1 つの比較へ崩れ落ちます。計算されたレベルが、ポリシーが許すレベル以下かどうかです
署名された PDF が変わることを前提とする理由
正当なケースは 3 つあり、目にするものの大部分をカバーします。2 人目の署名者が自分の署名を追加する。受取人が作者が開放しておいたフォームフィールドに入力する。そして長期検証素材が追記される、つまり OCSP レスポンスと CRL が文書セキュリティストアへ書き込まれ、レスポンダーがなくなった後も署名が検証可能であり続けます。最後のものは単に許されるだけでなく、きちんと運営されたアーカイブが署名文書に意図的に行うことです
つまり「署名後にファイルが大きくなった」は何の情報も運びません。問いは常に何が追加されたかであり、答えはバイトを監視するのではなく、文書状態の比較から来なければなりません。追記の仕組み自体はインクリメンタル更新の記事で扱っています
生成元の経路ではなく、オブジェクトの形状で分類する
分類器は、変更後にオブジェクトが何であるかを見ます。どのライブラリ呼び出しが作ったかではありません。これは意図的な設計です。分析は他のソフトウェアが生成したファイルに対して走るためで、そこには調べるべき呼び出し経路が存在しません
認識される形状は 4 つです。文書セキュリティストアと検証関連情報辞書、クロスリファレンスストリームオブジェクト、カタログの metadata エントリ、そしてバイト範囲を持つ署名辞書は長期アーカイブ素材です。フィールド型とフィールド値の両方を運ぶオブジェクトはフォーム入力です。型が annotation であるか、または subtype が ISO 32000-2 の表 168 に列挙されたものであるオブジェクトは、注釈の変更です。それ以外はすべて未分類です
削除は追加よりも厳しく扱われます。削除されたオブジェクトがホワイトリストに入るのは、旧側のオブジェクト自体がアーカイブ素材だったときだけです。これはセキュリティストアが新しいものに置き換わるという通常のケースをカバーします。それ以外の削除はすべて未分類です。署名済み文書からの内容削除は、権限レベルが許可するものではないからです。文書レベルの差分はさらに厳しく、ページ数の変化は個々のオブジェクトを調べずに直ちに未分類へ行きます。どの DocMDP レベルもページの追加や削除を許さないからです
ホワイトリストは拒否の側に倒して誤る
これがすべての境界判断を支配する設計規則です。許可と誤分類された変更は、作者が決して許可していない内容の上で検証が通る署名です。未分類と誤分類された変更は、フラグが立って人の目で確認される文書です。この 2 つの誤りは対称ではないため、ホワイトリストは狭く保たれ、認識できない形状は推測されるのではなく未分類へ落ちます
これには先回りして知っておく価値のある実務上の帰結があります。珍しい生成元のファイルは、調べてみると無害な未分類変更を報告することがあります。正しい対応は、ホワイトリストを広げるのではなく、ファインディングの詳細とオブジェクト番号を見ることです。個々の報告を黙らせるために育ったホワイトリストは、セキュリティ管理ではなくなるからです
uses
PDFlibrary, PDFlibCompare;
var
Pdf: TPDFlib;
I, Level: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.LoadFromFile('contract-countersigned.pdf', '');
if Pdf.AnalyzeModifications('contract-as-signed.pdf', '') < 0 then
raise Exception.Create('the earlier revision could not be loaded');
// TPLModificationLevel は mlNone, mlLTAUpdates, mlFormFilling,
// mlAnnotations, mlUnclassified の順。ゲッターはその序数を返す
Level := Pdf.GetModificationLevel;
// DocMDP の強制はこれでポリシーとの 1 つの比較になる
if Level > Ord(mlFormFilling) then
for I := 0 to Pdf.GetModificationFindingCount - 1 do
Report.Add(Format('object %d, level %d: %s',
[Pdf.GetModificationFindingObjNum(I),
Pdf.GetModificationFindingLevel(I),
Pdf.GetModificationFindingDetail(I)]));
finally
Pdf.Free;
end;
end;
全体のレベルは全ファインディングの最大値です。これが唯一の擁護できる集約です。99 件のアーカイブ追加と 1 件の未分類変更を含む文書は、未分類変更を含む文書です
その内部:暗号学的ハッシュではなくフィンガープリント
CompareWith が公開し、改変分析がその上に構築される比較エンジンは、正規化された本体のフィンガープリントによってオブジェクトを識別します。SHA-256 ではなく非暗号学的な 64 ビットハッシュを使います。これは熟慮された選択です。構造比較に必要なのは決定性です。同じオブジェクト本体は、1 回の実行内で常に同じフィンガープリントを生まなければなりません。衝突耐性は必要ありません。比較の両側を支配する攻撃者はすでに他の手段で勝っており、100 万オブジェクトの文書の全オブジェクトに完全な暗号学的ハッシュを払うのは、利益のない実費だからです
ハッシュの選択よりも重要な正規化規則が 2 つあります。間接参照は、参照先の内容へ展開される代わりにプレースホルダートークンへ折りたたまれます。展開してしまうと共有オブジェクトの本体が参照元すべてへコピーされるため、共有フォントディスクリプタへの 1 つの小さな編集が、それに到達する全オブジェクトのフィンガープリントを無効化し、報告は読めないものになります。さらにオブジェクト番号自体はフィンガープリントから除外されます。書き直しは何も意味を変えずにオブジェクトの番号を付け替えられるからです
マッチングは 2 パスで走ります。まずフィンガープリントで整列し、残りをオブジェクト番号でペアにして、追加と削除の組み合わせではなく変更として識別します。全体を通じて安価な確認が先に来ます。ページ数の差は、オブジェクト走査が始まる前に報告されます
罠:自己比較が同一になるとは限らない
差分エンジンの自然な最初のテストは、ファイルを自分自身と比較して結果が同一であると断言することです。この断言はここでは成立しません。その理由は示唆に富みます。公開のロード経路と低レベルの文書ロード経路では、デコードの設定が同一ではなく、同じファイルを 2 つの経路でロードすると、一部のオブジェクトで異なるフィンガープリントが生じ得ます。エンジンが誤っているのではありません。2 つのロードは実際に異なるメモリ内状態を生んだのです
2 つの経路を無理に合わせる代わりに、比較のセマンティクスは狭く明言されます。分析は現在の文書状態を以前のリビジョンと比較し、2 つのフィンガープリント集合が厳密に一致するときにのみ同一と報告します。これが利用者が実際に問う問題であり、2 つのローダーが交換可能であることを要求しません。比較機能を設計するとき、「同じ」の意味を定義することは、それを計算することより多くの仕事です
どこで使うか
使いどころは 2 つです。1 つは検証レポートの中で、署名チェックと並べて。これによりレビュアーは、署名が暗号学的に無傷かだけでなく、その後に文書へ何が起きたかも見えます。署名側はPAdES 署名と検証で扱っています。もう 1 つは受入口のゲートで、外部から届いた文書を送ったコピーと照合します。注釈が追加されて返ってきた契約書は、ページが編集されて返ってきたものとは異なる扱いになります
適用範囲について 1 つの注意があります。この分析は、同じ文書系統の 2 つのリビジョンの間で何が変わったかを教えます。可視コンテンツが誤解を招くかどうか、フォームフィールドの外観ストリームが値と一致するかどうか、オーバーレイの下に隠されたテキストがコンテンツストリームにまだ存在するかどうかは教えません。これらは別の処理が必要であり、そのうちの内容削除の側面は真の墨消しの記事で扱っています。分析と比較のエントリポイントは、losLab PDF Developer Library の製品ページに文書があります