بيئة العمل التي تربط التحقق من الامتثال بالتوقيع الرقمي يجب أن تنسق أربع خطوات، بهذا الترتيب، وتبقيها مرتبطة بمجموعة واحدة من البايتات طوال الطريق. تقوم بتشغيل فحص ما قبل الطباعة (preflight) لـ PDF/A أو PDF/UA. وتطبق أي إصلاحات تتطلبها النتائج وتحفظ مراجعة مصححة. وتوقع تلك المراجعة الدقيقة. ثم تقرأ الملف الموقع مرة أخرى وتؤكد أن التوقيع يغطيه حقًا. الترتيب ليس تجميليًا. تخطى القراءة مرة أخرى وأنت تثق في مسار الكتابة الخاص بك؛ دع فحص ما قبل الطباعة يعمل ضد المراجعة الخاطئة وسيصف تقرير الامتثال الخاص بك ملفًا لم تقم بشحنه أبدًا
الجزء الذي تخطئ فيه معظم خطوط الأنابيب المطورة محليًا هو التماس بين التحقق من الصحة والتوقيع. قم بتشغيلها كأداتين منفصلتين مع مسار إصلاح بينهما وستظهر ثلاث مراجعات مميزة على الأقل للملف، لكل منها وحدات بايت خاصة بها. تقرير الفحص المبدئي الذي تسلمه لمدقق يصف واحدًا منها. يجمد التوقيع آخر. لا شيء في الملف يذكر أنهما نفس المراجعة، وغالبًا ما يكونان كذلك. يضع PDFlibPas، مكتبة مطوري PDF الخاصة بـ losLab لـ Delphi و C++Builder، الفحص المبدئي وتوقيع PAdES خلف فئة واجهة واحدة، لذلك يمكن أن يعيش التسلسل بأكمله في عملية واحدة لا تفقد أبدًا مسار البايتات التي تتحدث عنها. يوجد كل استدعاء أدناه في المكتبة اليوم، وكذلك كل فخ مذكور إلى جانبه
ثلاث مراجعات لمستند واحد، وكيف تفتح الفجوة
احسب عمليات الحفظ. يصل الأصل من المنبع. يقوم مسار الإصلاح بتحميله، ويقوم بتشغيل وضع الامتثال، ويكتب مراجعة مصححة. يلحق مسار التوقيع توقيعًا كتحديث تدريجي، وهي الكتابة الثالثة. ثلاث عمليات حفظ، وثلاثة تخطيطات بايت، وتقرير فحص مبدئي لا يعني شيئًا ما لم يذكر أيهما يغطيه. SHA-256 للملف، المسجل بجوار كل تشغيل فحص مبدئي وكل توقيع، هو المرساة الرخيصة التي تتيح لك إثبات أن المراجعة التي تحققت من صحتها هي المراجعة التي وقعتها
يؤدي سلوك إحدى المكتبات إلى تشديد هذا الانضباط بشكل أكبر. لا تسري إصلاحات الامتثال المطلوبة من خلال SetPDFAMode أو SetPDFUAMode عند استدعائها. يتم تطبيقها أثناء الحفظ. تصل الإصلاحات التلقائية مثل فرض علامات طباعة التعليقات التوضيحية أو تعيين ترتيب علامة تبويب PDF/UA في ملف الإخراج وليس في أي مكان آخر، لذلك لا يخبرك الفحص الذي يتم إجراؤه ضد المستند الذي قمت "بإصلاحه" للتو في الذاكرة بشيء عن البايتات المتجهة إلى الموقّع. احفظ أولاً، ثم قم بإجراء فحص مبدئي للملف المحفوظ. حالة الذاكرة عبارة عن مسودة؛ الملف الموجود على القرص هو الحقيقي الوحيد
الفحص المبدئي من القرص، والصفر الذي يعني شيئين
نقطة دخول الفحص المبدئي المسطحة هي CheckFileCompliance(FileName, Password, ComplianceTest, Options). يحدد الاختبار 1 PDF/A (ISO 19005)، ويحدد الاختبار 2 PDF/UA (ISO 14289). يفتح الملف من خلال قارئ تدفق المكتبة، لذلك ليست هناك حاجة إلى LoadFromFile أولاً، ويعيد مقبض قائمة سلاسل يحمل نتيجة واحدة لكل إدخال:
var
PDF: TPDFlib;
ListID, I: Integer;
begin
PDF := TPDFlib.Create;
try
ListID := PDF.CheckFileCompliance('invoice-fixed.pdf', '', 1, 0); // 1 = PDF/A
if ListID = 0 then
begin
if PDF.LastErrorCode <> 0 then
raise Exception.Create('Preflight could not read the file')
else
Writeln('No PDF/A findings');
end
else
begin
for I := 0 to PDF.GetStringListCount(ListID) - 1 do
Writeln(PDF.GetStringListItem(ListID, I));
PDF.ReleaseStringList(ListID);
end;
finally
PDF.Free;
end;
end;
يكمن الفخ في القيمة المعادة، وهو من النوع الذي يجتاز كل اختبار مسار سعيد (happy-path). صفر يعني "لا يوجد نتائج." صفر يعني أيضًا "تعذر فتح الملف"، لأن التنفيذ يعيد 0 عندما تعود قائمة النتائج فارغة، بما في ذلك فشل القراءة. ستوافق بيئة العمل التي تقرأ 0 كضوء أخضر بكل سرور على ملف تم قفله بواسطة عملية أخرى. ربط الاستدعاء مع LastErrorCode، كما هو مذكور أعلاه، هو ما يفصل بين الحالتين. يفتح المدقق أيضًا الملف مع وضع مشاركة رفض الكتابة، لذلك إذا كانت خطوة الإصلاح الخاصة بك لا تزال تحمل مقبض كاتب، يفشل الفحص المبدئي لسبب لا علاقة له بالامتثال وكل ما يتعلق بتدفق نسيت تحريره
عندما يحتاج شخص وليس مسار إلى قراءة النتائج، يقوم CreatePreflightReport بتقديمها كتقرير مقروء. يقوم ComparePreflightReports بمقارنة تشغيلين، وهي طريقة مرتبة لإظهار أن الإصلاح قام بمسح النتائج الأصلية دون إدخال نتائج جديدة بهدوء
توقيع المراجعة التي تم التحقق منها باستخدام SignProcess
بمجرد اجتياز المراجعة المحفوظة للفحص المبدئي وتسجيل التجزئة الخاصة بها، قم بتوقيع هذا الملف الدقيق ولا غيره. تُقرأ واجهة برمجة تطبيقات SignProcess كمنشئ. افتح مقبض عملية، وقم بتكوينه سطرًا تلو الآخر، ثم قم بالالتزام، ثم اقرأ رمز النتيجة مرة أخرى
ProcessID := PDF.NewSignProcessFromFile('invoice-fixed.pdf', '');
if ProcessID = 0 then
raise Exception.Create('Cannot open source for signing');
PDF.SetSignProcessField(ProcessID, 'ApprovalSig');
PDF.SetSignProcessPFXFromFile(ProcessID, 'company.pfx', PfxPassword);
PDF.SetSignProcessInfo(ProcessID, 'Invoice approval', 'Berlin', 'billing@example.com');
PDF.SetSignProcessCustomSubFilter(ProcessID, 'ETSI.CAdES.detached'); // PAdES baseline
PDF.SetSignProcessDigestAlgorithm(ProcessID, 2); // SHA-256
PDF.SetSignProcessReserveContentsBytes(ProcessID, 8192); // room for a later timestamp
PDF.EndSignProcessToFile(ProcessID, 'invoice-signed.pdf');
if PDF.GetSignProcessResult(ProcessID) <> 1 then
Writeln('Sign failed, code ', PDF.GetSignProcessResult(ProcessID));
PDF.ReleaseSignProcess(ProcessID);
سطران في هذا التسلسل يحملان وزنًا أكبر مما يبدوان. يختار SetSignProcessCustomSubFilter مع ETSI.CAdES.detached توقيع PAdES كما هو موضح في ETSI EN 319 142-1 بدلاً من عائلة adbe.pkcs7.detached القديمة، وهو الفرق بين التوقيع الذي يقبله المدقق الأوروبي والآخر الذي يضع علامة عليه. يملأ SetSignProcessReserveContentsBytes العنصر النائب /Contents، والحجم الذي تختاره هنا هو قرار يتعلق بالمستقبل: إذا كان سيتبع طابع زمني للتوقيع، فيجب أن يتناسب CMS الموسع مع المساحة التي تحتفظ بها الآن، لأن العنصر النائب لا يمكن أن ينمو لاحقًا بدون إعادة توقيع كل شيء. احجز بسخاء وستهدر بضعة كيلوبايتات. احجز بشكل ضيق للغاية وستفشل خطوة الطابع الزمني بعد أشهر من الآن بفيضان ستكافح من أجل إعادة الاتصال بهذا السطر الواحد
يجيب GetSignProcessResult برمز، وليس قيمة منطقية، والرموز تستحق الاحتفاظ بها. 1 هو النجاح. 4 كلمة مرور PDF خاطئة، 7 كلمة مرور شهادة خاطئة، 9 PFX لا يحمل أي مفتاح خاص، 11 فشل أثناء تطبيق التوقيع. قم بطي تلك الرموز إلى صح/خطأ وستتخلص من المعلومة الوحيدة التي تميز بين حالة دعم كلمة المرور الخاطئة وتلك التي تحتوي على مفتاح بدون جزء خاص. قم بتسجيل العدد الصحيح
القراءة مرة أخرى: تدقيق الملف الذي أنتجته للتو
لا ينبغي لأي بيئة عمل أن تثق في المسار الذي كتب الملف الذي هي على وشك اعتماده. تعيد فئة التدقيق TPDFlibSignDoc فتح الإخراج الموقع وتقرأ إدخالات قاموس التوقيع مباشرة من القرص:
var
Doc: TPDFlibSignDoc;
Names: TStringList;
FS: TFileStream;
I: Integer;
SourceSize, RangeStart, GapStart, TailStart, TailLen: Int64;
begin
// Capture the size before Open: the audit object holds a share lock on the file
FS := TFileStream.Create('invoice-signed.pdf', fmOpenRead or fmShareDenyNone);
SourceSize := FS.Size;
FS.Free;
Doc := TPDFlibSignDoc.Create;
Names := TStringList.Create;
try
if not Doc.Open('invoice-signed.pdf', '', False) then Exit;
Doc.GetSignatureFieldNames(Names);
for I := 0 to Names.Count - 1 do
if Doc.GetSignatureValueObjNum(Names[I]) > 0 then // > 0 means the field is signed
begin
RangeStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 11)));
GapStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 12)));
TailStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 13)));
TailLen := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 14)));
if (RangeStart = 0) and (TailStart + TailLen = SourceSize) then
Writeln(Names[I], ': signature covers the file to EOF')
else
Writeln(Names[I], ': earlier revision, or unusual ByteRange layout');
end;
Doc.Close;
finally
Names.Free;
Doc.Free;
end;
end;
يتم تعيين وسائط ValueKey على إدخالات القاموس. يُرجع المفتاح 0 CMS الخام من /Contents، والمفاتيح 2 و 3 أسماء /Filter و /SubFilter، و 11 إلى 14 أرقام ByteRange الأربعة. تعود القيم النصية من خلال GetSignatureTextValueByName بدلاً من ذلك: المفتاح 0 هو وقت التوقيع المطالب به، ويميز المفتاح 5 Sig العادي عن DocTimeStamp، وهو أمر يهم بمجرد أن يحمل المستند كليهما
التقاط حجم الملف في أعلى ذلك المثال هو لتحمل العبء (load-bearing)، وليس التدبير المنزلي. يحتفظ TPDFlibSignDoc.Open بالملف أسفل قفل مشاركة مقيد طوال فترة حياته، لذلك فإن أي شيء يحتاج إلى البايتات الأولية (تجزئة النطاق الموقع، وإعادة حساب ملخص CMS) يجب عليه قراءة الملف قبل استدعاء Open. يقرأ العرض التوضيحي SigningWorkbench الخاص بالمكتبة الملف بأكمله في الذاكرة أولاً لهذا السبب بالذات، وتفشل بيئة العمل التي تتجاهل الترتيب بشكل متقطع، على أي جهاز يصادف أنه خسر السباق
حسابات ByteRange التي تثبت التغطية
يحتوي ملف التوقيع الفردي السليم على ByteRange بالصيغة [0 a b c]: تبدأ التغطية عند الإزاحة 0، وتتخطى العنصر النائب /Contents السداسي العشري بين a و b، ثم تُستأنف خلال البايت b+c. عندما يساوي b+c حجم الملف، يغطي التوقيع كل شيء حتى نهاية الملف، وهي النتيجة التي تريدها. عندما يقصر، ألحق شخص ما تحديثًا تدريجيًا بعد كتابة التوقيع. يعد هذا مشروعًا تمامًا بموجب ISO 32000-1§12.8، حيث أن عمليات ملء النموذج اللاحقة، والتوقيع الثاني، وقاموس DSS تصل جميعها بهذه الطريقة بالضبط. إنها أيضًا بالتحديد الحقيقة التي يجب أن يسجلها مسار التدقيق في وقت التوقيع بدلاً من إعادة البناء تحت الضغط أثناء النزاع
راقب عرض العدد الصحيح أثناء قيامك بهذه الحسابات. يعيد GetSignProcessByteRange الخاص بـ API المسطح عددًا صحيحًا من 32 بت، ولكن القيم الأساسية هي Int64، لذلك في ملف يتجاوز 2 جيجابايت، يقتطع وصول المسطح بصمت. تواصل مع طبقة الفئة TPDFlibSigner.GetByteRange، التي ترجع Int64، أو قم بتحليل القيم من GetSignatureValueByName بالطريقة التي يقوم بها رمز التدقيق أعلاه
ما تتركه المكتبة لك
من الأفضل تعلم حدودين في وقت التصميم بدلاً من العدو النهائي. لا يحمل TPDFlib API المسطح أي غلاف للتحقق من التوقيع على الإطلاق. يعيش التحقق من التشفير في طبقة واحدة لأسفل، في TPDFlibSignatureVerifier، الذي يجيب VerifySignature الخاص به بـ صالح، غير صالح، أو غير معروف. لا يوجد أيضًا عميل HTTP مضمن لهيئات الطابع الزمني RFC 3161. تحسب المكتبة التجزئة المطلوب إرسالها وتعيد تضمين CMS الموسع بمجرد عودة الرمز المميز، ولكن رحلة الشبكة ذهابًا وإيابًا إلى TSA هي لك لكتابتها. كلاهما واضح للتغليف وغير سار حقًا أن تجده مفقودًا في الأسبوع الذي يسبق الإصدار، لذا صممهما من الرسم الأول
سؤال واحد حول الامتثال يستحق التسوية بوضوح، لأنه يقرر إلى أين تذهب البوابة الأخيرة: هل تؤدي إضافة توقيع إلى كسر PDF/A؟ ليس بمفردها. يصل التوقيع كتحديث تدريجي، وتسمح ISO 19005-2 وما بعدها بشكل صريح بالمستندات الموقعة. الفخ هو مظهر التوقيع، الذي يلعب بنفس قواعد أي محتوى صفحة آخر، والخطوط المضمنة وعدم تضمين لون يعتمد على الجهاز. إذن البوابة النهائية في بيئة العمل هي تشغيل فحص مبدئي إضافي، هذه المرة ضد الإخراج الموقع. تعامل مع CheckFileCompliance كفحص سريع داخل خط الأنابيب واستمر في التحقق من صحة مرشحي الإصدار باستخدام أداة مستقلة مثل veraPDF، نظرًا لأن المدققين ينفذون مجموعات قواعد متداخلة ولكنها غير متطابقة؛ عندما يختلف الاثنان، فإن نص النتيجة عادة ما يذكر البند الذي يجب قراءته
تخرج نقطة تسلسل واحدة من كل هذا. التوقيع والختم الزمني ليسا مسارًا واحدًا: يُكتب توقيع الأساس أولاً، ثم تقوم عملية طابع زمني منفصلة بزيادة CMS داخل مساحة /Contents المحجوزة، وهذا هو بالضبط سبب حمل سطر البايتات المحجوزة في وقت سابق لهذا الوزن الكبير. بالنسبة لطبقات الطابع الزمني والتحقق من الصحة طويلة المدى التي تُبنى على بيئة العمل هذه، يحمل البرنامج التعليمي لتوقيع PAdES والتحقق من صحته التوقيع من الأساس إلى B-LT، ويتعمق نصف الفحص المبدئي في دليل الفحص المبدئي PDF/A و PDF/UA. تتوفر وثائق واجهة برمجة التطبيقات الكاملة وتنزيلات الإصدار التجريبي على صفحة منتج PDFlibPas