PDFای که همین حالا امضا کردهاید یک امضای B-B است و بس. ثابت میکند چه کسی امضا کرده و اینکه بایتها جابهجا نشدهاند، اما هیچ مدرکی حمل نمیکند که گواهی امضاکننده در زمان امضا معتبر بوده، پس یک راستیآزماییگر چند سال بعد باید دنبال دادههای ابطال بگردد که شاید دیگر وجود نداشته باشند. بستن این شکاف یعنی نوشتن پاسخهای OCSP و CRLها در Document Security Store سطح سند، و در HotPDF این یک فراخوانی است: PopulatePAdESLTVEvidence هر امضای بارگذاریشده را میپیماید، درخواستهای ابطال را از مجموعه گواهی استخراج میکند، آنها را از طریق ترنسپورتی که شما تأمین میکنید اجرا میکند، و مواد گرفتهشده بهعلاوه زنجیره CMS را در DSS مینویسد. تعداد امضاهایی را برمیگرداند که شواهدشان فرود آمده، یا منفی یک وقتی سند اصلاً هیچ فیلد امضایی ندارد
تصمیم طراحیای که پیش از استفاده ارزش فهمیدن دارد این است که کتابخانه هرگز سوکتی باز نمیکند. هر بایتی که از شبکه میرسد از طریق callbackای میرسد که شما نوشتهاید. این احتیاط برای خودِ احتیاط نیست؛ تنها راهی است که این ویژگی میتواند درون محیطهایی که واقعاً اعتبارسنجی بلندمدت را مطالبه میکنند کار کند
چرا کتابخانه حاضر نیست HTTP خودش را انجام دهد؟
چون جاهایی که امضاهای B-LT را مطالبه میکنند جاهایی هستند که نمیتوان شبکه را به یک کتابخانه سپرد. سرویسهای امضا پشت پروکسیهای احراز هویتکننده با rootهای سازمانی اجرا میشوند. لایههای امضای جدا از هوا هیچ مسیری به یک پاسخدهنده ندارند و باید با شواهد کششده تغذیه شوند. رژیمهای ممیزی میخواهند هر درخواست خروجی توسط برنامه ثبت شود، نه دفن در یک وابستگی. و مجموعههای تست به پاسخهای قطعیتمند نیاز دارند، که اگر کتابخانه خودش شمارهگیری کند ناممکن است
ترنسپورت یک مرجع تابع ساده با یک شکل ثابت است، پس خطمشی مال شما میماند. HotPDF یک رکورد درخواست به شما میدهد که دقیقاً توصیف میکند چه چیزی گرفته شود، از جمله نوع محتوا و سقف اندازه پاسخ، و شما بایتها بهعلاوه یک وضعیت برمیگردانید
function FetchEvidence(const Request: THPDFSignatureEvidenceRequest;
Attempt: Integer; CancellationToken: THPDFCancellationToken;
out Response: TBytes; out RetryAfterMS: Cardinal;
out ErrorMessage: UnicodeString): THPDFSignatureEvidenceTransportStatus;
begin
RetryAfterMS := 0;
try
// Request.Kind میگوید این یک POST مربوط به OCSP است یا یک GET مربوط به CRL؛
// Request.ContentType و Request.Body از قبل آمادهاند،
// و Request.MaxResponseBytes سقفی است که باید رعایت کنید
Response := HttpExchange(Request.URI, Request.ContentType,
Request.Body, Request.MaxResponseBytes);
Result := setsSucceeded;
except
on E: Exception do
begin
ErrorMessage := E.Message;
// setsRetry اجازه میدهد خطمشی تلاش مجدد عقبنشینی کند؛ برای
// یک 404 یا یک URL بد از setsPermanentFailure استفاده کنید
Result := setsRetry;
end;
end;
end;
// ارتقای تکفراخوانی B-B به B-LT برای هر امضا در فایل بارگذاریشده
var
Pdf: THotPDF;
Upgraded: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginIncrementalUpdate('signed.pdf');
Upgraded := Pdf.PopulatePAdESLTVEvidence(FetchEvidence,
THPDFSignatureEvidenceRetryPolicy.Default);
if Upgraded > 0 then
// ذخیره فقط-افزودنی: بایتهایی که امضاهای موجود پوشش میدهند
// عیناً حفظ میشوند
Pdf.SaveIncrementalUpdate('signed-lt.pdf');
finally
Pdf.Free;
end;
end;
شکستها بهازای هر امضاست، نه هر سند. پاسخدهندهای که برای یک امضاکننده timeout میشود مواد آن امضاکننده را رد میکند و بقیه گذر را سالم میگذارد، که رفتاری است که در یک دسته میخواهید: شواهد جزئی بهتر از یک اجرای سقطشده است، و مقدار بازگشت میگوید چند امضا واقعاً بهبود یافتند
زنجیرهای که CMS فراموش کرد همراهش بیاورد
بررسی ابطال به گواهی صادرکننده نیاز دارد، و تعداد شگفتآوری از استکهای امضا میانیها را از ظرف CMS حذف میکنند. مسیر ریکاوری اکستنشن Authority Information Access است، روش دسترسی 1.3.6.1.5.5.7.48.2، که URLای را اعلام میکند که گواهی صادرکننده از آن قابل دانلود است. HPDFFetchAIAIntermediates آن URLها را از طریق همان ترنسپورت میپیماید، DER را از هر پاسخ بیرون میکشد، و فقط گواهیهایی را برمیگرداند که CMS از قبل حمل نمیکرده، با کلید hash مربوط به DER تا تکراریها و حلقهها نچرخند
دو جزئیات تصمیم میگیرند که آیا این در برابر مراجع گواهی واقعی کار میکند. اولی رمزگذاری است: نقاط پایانی CA گواهی را تقریباً به همان اندازه که PEM زرهپوش سرو میکنند بهصورت DER لخت سرو میکنند، و هیچ نوع محتوای قابلاعتمادی برای تفکیکشان نیست. پروب مطمئن متنی است، سپس ساختاری. دنبال نشان -----BEGIN CERTIFICATE----- بگردید، اگر بود زره را بکنید و base64 را رمزگشایی کنید، و در هر دو مسیر تأیید کنید که اولین بایت نتیجه $30 است، تگ DER برای یک SEQUENCE. دومی عمق است: یک میانیِ گرفتهشده میتواند خودش یک URL مربوط به AIA برای صادرکننده خودش اعلام کند، پس پیمایش نامزدهای جدید را به صف میافزاید و زنجیرههایی که دو یا سه پرش کم دارند را کامل میکند. این باید سقف داشته باشد، که کار پارامتر MaxFetch است
مقدار seed امضا چیست، و چرا بیسروصدا شکست میخورد؟
مقدار seed قیدی است که مؤلف سند به یک فیلد امضا میچسباند تا به امضاکننده بگوید چه جور امضایی قابل قبول است: کدام SubFilter، کدام الگوریتم digest، کدام دلیلها، کدام حداقل نسخه PDF، آیا اطلاعات ابطال باید تعبیه شود. در یک دیکشنری /SV روی فیلد زندگی میکند و در ISO 32000-1 §12.7.5.5 تعریف شده است. HotPDF آن را با AttachPAdESSeedValue مینویسد و با CheckLoadedSignatureSeedValue بررسی میکند، که وقتی فیلد بدون قید است یا هر قید حاضر رد میشود True برمیگرداند، و در صورت False نخستین قید مردود را از طریق یک پارامتر خروجی نام میبرد که میتوانید مستقیم در یک پیام خطا بگذارید
مکانیزمی که مقادیر seed را بهآسانی قابل اشتباه میکند entry پرچمهای /Ff است که در §12.7.5.5.3 توصیف شده. یک بیت ستشده قیدش را اجباری علامت میزند: یک ناهمخوانی خطاست و امضاکننده باید رد کند. یک بیت صفر همان قید را ترجیح علامت میزند: مقدار فیلتر میکند که UI چه چیزی پیشنهاد دهد و بس. دو تله از آن پیروی میکنند. اول، /Ff درون دیکشنری /SV زندگی میکند نه روی annotation ویجت، پس کدی که /Ff سطح فیلد را میخواند برای همیشه پاسخ خالی میگیرد و نتیجه میگیرد که هیچچیز اجباری نیست. دوم، تخصیص بیتها یک دنباله ساده یک، دو، چهار، هشت نیست؛ در HotPDF نویسنده 2 را برای SubFilter، 4 را برای MinVersion، 32 را برای AddRevInfo و 64 را برای DigestMethod ساطع میکند. خوانندهای که بیتهای ترتیبی فرض کند هر قیدی را اختیاری رمزگشایی میکند و همه تستها را پاس میکند بهجز همان که اهمیت دارد
var
Violation: AnsiString;
begin
// از فیلد بپرس آیا پروفایلی که در شرف امضا با آن هستیم مجاز است
if not Pdf.CheckLoadedSignatureSeedValue(0, 'ETSI.CAdES.detached',
'SHA256', 'Approved for payment', 1, Violation) then
raise Exception.Create('Signature field rejects this profile: ' +
String(Violation));
// قید برآورده شد: با گذر امضا ادامه بده
end;
تستی که باگ اصلی رمزگشایی را لو داد یک تست مثبت نبود. آن ادعای این بود که یک ناهمخوانی اجباری باید رد شود، و تنها جنس تستی است که میتواند این رده عیب را بگیرد: یک رمزگشا که دیکشنری غلط یا جایگاههای بیت غلط میخواند برای هر ورودی «هیچ قیدی نقض نشده» تولید میکند، که دقیقاً شبیه رفتار درست است تا وقتی عمداً یکی را نقض کنید
این کجای نردبان LTV مینشیند
چهار پله، و هر پله به پله زیرش نیاز دارد. B-B امضای لخت است. B-T یک مهر زمانی مطمئن اضافه میکند که زمان امضا را قفل میکند تا راستیآزماییگر بداند ابطال را نسبت به کدام لحظه بسنجد. B-LT شواهد ابطال را به DSS اضافه میکند، که همان چیزی است که PopulatePAdESLTVEvidence خودکار میکند. B-LTA مهرهای زمانی سند را اضافه میکند که پیش از ضعیفشدن قبلی تمدید میشوند و اعتبار را بینهایت ادامه میدهند؛ HotPDF آن را بهصورت RenewPAdESLTATimestamp آشکار میکند، که یک مهر زمانی جدید را بهعنوان یک revision افزایشی میچسباند و هر امضای پیشین، مهر زمانی و entry مربوط به DSS را دستنخورده نگه میدارد
مدل بهروزرسانی افزایشی تنها راه درست افزودن شواهد به یک سند امضاشده است، چون بازنویسی فایل بازههای بایتی را که امضاهای موجود پوشش میدهند میشکند. اگر لازم است درباره اینکه میان revisionها چه تغییر کرده و آیا آن تغییرها از جنس مجاز امضاست استدلال کنید، آن تحلیل جداگانه در تحلیل revision مربوط به DocMDP و FieldMDP پوشش داده شده است. خط لوله امضا خودش، شامل منابع گواهی و تلههای ترتیب بایت، در آموزش گامبهگام امضای PAdES است، و سمت اعتبارسنجی در تأیید امضاها روی اسناد بارگذاریشده است
یک هشدار عملی درباره ترتیب. شواهد را به محض ممکن پس از امضا جمع کنید، ترجیحاً در همان کار. پاسخدهندههایی که میتوانند از یک گواهی پاسخ بدهند تا وقتی گواهی جاری است آنلایناند و چند سال بعد رفتهاند، پس سندی که خط لوله شما را بهصورت B-B ترک کند شاید دیگر هرگز قابل ارتقا نباشد. HotPDF بهعنوان یک مؤلفه VCL بومی برای Delphi و C++Builder اجرا میشود، و کل گذر شواهد درون-فرایندی است بهجز ترنسپورت خودتان؛ پروفایلهای پشتیبانیشده در صفحه محصول HotPDF Delphi PDF component فهرست شدهاند