הגרסה המקומית הזו מתמקדת ב-PAdES Digital Signatures in Delphi: Signing and Validation with PDFlibPas ומשתמשת במאמר האנגלי המעודכן כבסיס טכני לצוותי Delphi, PDF ותוכנות מסמכים
הדף הופך את מאמר הבסיס המעודכן לנקודות בדיקה מעשיות עבור תכנון, יישום ואימות
מה סונכרן מהמאמר האנגלי
מאמר הבסיס באנגלית הורחב בהקשר יישומי, החלטות טכניות ודוגמאות קונקרטיות, ולכן דף זה מיועד לשמש מדריך עבודה ולא תקציר קצר
חלקים חשובים במאמר הבסיס המעודכן:
- השתמשו תחילה בקובצי קלט קטנים שניתן לשחזר
- השאירו ללא שינוי שמות מוצרים, API, קבצים וערכי literal
- שמרו את פלט ה-validator ואת פרטי הגרסאות יחד עם קובץ הדוגמה שנוצר
בחירות מעשיות ביישום
התחילו מסוג הקובץ, הפלט הרצוי ומצב השגיאה שהמשתמש צריך לראות. לאחר מכן קשרו כל קריאת API לתוצאה ניתנת לבדיקה, כדי שאימות, רישום ותמיכה יוכלו לשחזר את תרחיש הלקוח
- השתמשו תחילה בקובצי קלט קטנים שניתן לשחזר
- השאירו ללא שינוי שמות מוצרים, API, קבצים וערכי literal
- שמרו את פלט ה-validator ואת פרטי הגרסאות יחד עם קובץ הדוגמה שנוצר
קוד ונקודות API
דוגמאות הקוד נשמרות ללא שינוי כדי שמפתחים יוכלו להשוות אותן ישירות לפרויקטי Delphi, C++Builder ו-Lazarus/FPC
var
Pdf: TPDFlib;
SignId: Integer;
begin
Pdf := TPDFlib.Create;
try
SignId := Pdf.NewSignProcessFromFile('invoice.pdf', '');
if SignId = 0 then
raise Exception.Create('cannot open source PDF');
Pdf.SetSignProcessField(SignId, 'Sig1');
Pdf.SetSignProcessPFXFromFile(SignId, 'company.pfx', PfxPassword);
Pdf.SetSignProcessInfo(SignId, 'Approved', 'Vienna', 'billing@example.com');
Pdf.SetSignProcessCustomSubFilter(SignId, 'ETSI.CAdES.detached');
Pdf.SetSignProcessDigestAlgorithm(SignId, 2); // SHA-256
Pdf.SetSignProcessReserveContentsBytes(SignId, 8192); // room for a timestamp later
Pdf.EndSignProcessToFile(SignId, 'invoice-signed.pdf');
if Pdf.GetSignProcessResult(SignId) <> 1 then
raise Exception.CreateFmt('signing failed, code %d',
[Pdf.GetSignProcessResult(SignId)]);
Pdf.ReleaseSignProcess(SignId);
finally
Pdf.Free;
end;
end;var
Pdf: TPDFlib;
StsId: Integer;
HashHex, TstDer, TsAttr, AugmentedCms: AnsiString;
begin
Pdf := TPDFlib.Create;
try
StsId := Pdf.NewPAdESSignatureTimeStampProcessFromFile('invoice-signed.pdf', '');
Pdf.SetPAdESSignatureTimeStampField(StsId, 'Sig1');
Pdf.SetPAdESSignatureTimeStampDigestAlgorithm(StsId, 2);
HashHex := Pdf.GetPAdESSignatureValueHashHex(StsId);
// both calls below are application code: an HTTP POST to your TSA,
// and a CMS re-encode that attaches the token as an unsigned attribute
TstDer := RequestTimeStampToken(HashHex);
TsAttr := Pdf.BuildPAdESSignatureTimeStampAttribute(TstDer);
AugmentedCms := AttachUnsignedAttribute(Pdf.GetPAdESSignatureCMSBytes(StsId), TsAttr);
Pdf.SetPAdESSignatureCMSBytes(StsId, AugmentedCms);
Pdf.EndPAdESSignatureTimeStampProcessToFile(StsId, 'invoice-bt.pdf');
if Pdf.GetPAdESSignatureTimeStampProcessResult(StsId) <> 1 then
raise Exception.Create('timestamp embedding failed');
Pdf.ReleasePAdESSignatureTimeStampProcess(StsId);
finally
Pdf.Free;
end;
end;var
Doc: TPDFlibSignDoc;
Names: TStringList;
I: Integer;
B0, B1, B2, B3, FileSize: Int64;
begin
FileSize := TFile.GetSize('invoice-bt.pdf'); // before Open: SignDoc holds a share lock
Doc := TPDFlibSignDoc.Create;
try
if not Doc.Open('invoice-bt.pdf', '', False) then
raise Exception.Create('cannot open for audit');
Names := TStringList.Create;
try
Doc.GetSignatureFieldNames(Names);
for I := 0 to Names.Count - 1 do
if Doc.GetSignatureValueObjNum(Names[I]) > 0 then // >0 means actually signed
begin
B0 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 11)));
B1 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 12)));
B2 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 13)));
B3 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 14)));
if (B0 = 0) and (B2 + B3 = FileSize) then
Writeln(Names[I], ': covers the file to EOF')
else
Writeln(Names[I], ': earlier revision, or unexpected ByteRange layout');
end;
finally
Names.Free;
end;
Doc.Close;
finally
Doc.Free;
end;
end;בדיקה לפני פרסום
בדקו את קובץ הפלט באותם כלים שבהם ישתמש הלקוח או הארכיון. תעדו גרסת רכיב, נתוני בדיקה, גרסת validator ותוצאה נצפית כדי לעקוב במדויק אחר רגרסיה עתידית
הרחבה מעשית
במאמר הזה על עיבוד PDF ב-Delphi, הצפנה, חתימות, תצוגה, פריסה ואימות המטרה אינה רק לתאר מה הרכיב או הזרימה עושים, אלא להראות איך קוראים אותם בתוך עבודה אמיתית: מה בודקים תחילה, איזה מצב כשל צריך להישאר גלוי, ואיך מחברים את הקריאות כך שאפשר יהיה להריץ את אותו מסלול שוב מחר בלי לנחש מה השתנה
כשעובדים עם עיבוד PDF ב-Delphi, הצפנה, חתימות, תצוגה, פריסה ואימות, כדאי להתחיל מקלט קטן ושחזורי, להגדיר תוצאה צפויה אחת ברורה, ורק אחר כך לעבור לזרימה המלאה שבה גודל, תוכן, הרשאות או מבנה פנימי הופכים את הבדיקה לרגישה יותר. כך כל שינוי קטן נשאר ניתן להסבר ולא נעלם בתוך קובץ גדול או דוגמה חצי־מוכנה
- בדקו קלט קטן ושחזורי לפני מעבר לקבצי ייצור
- תעדו גרסאות רכיב, מנוע וכלי אימות יחד עם התוצאה
- חזרו על אותו מסלול קריאה כדי לוודא שאין סטייה