مقاله فنی

signatureهای digital در PAdES در Delphi با PDF Library for Delphi

validate کردن یک signature در PAdES یعنی بررسی سه چیز مستقل، و یک check mark سبز در یک viewer فقط دربارهٔ سومی به شما می‌گوید. اول، array /ByteRange باید byteهای درست را پوشش دهد: spanهایی که name می‌برد باید دقیقاً همان inputای را reconstruct کنند که CMS digest روی آن گرفته شده، بدون اینکه هیچ byte signedای بیرون از آن‌ها بماند. دوم، certificate داخل CMS باید به یک root که به آن اعتماد دارید chain شود و attribute امضا‌شدهٔ signing-certificateای که PAdES لازم دارد را حمل کند. سوم، اگر profile یک timestamp ادعا می‌کند، یک token RFC 3161 باید signature value را به یک نقطه در زمان قبل از انقضای certificate bind کند. Acrobat هر سه را در یک icon collapse می‌کند؛ یک conformance checker آن‌ها را جدا نگه می‌دارد، و codeای که این fileها را تولید می‌کند هم باید همین‌طور. losLab PDF Library (PDF Library for Delphi) طرف signing آن، re-embed کردن timestamp، و callهای audit برای inspect کردن یک ByteRange قبل از اعتماد به آن را به شما می‌دهد

یک تمایز تقریباً هر implementation اولیهٔ PAdES را به‌هم می‌ریزد، پس قبل از هر codeای ارزش بیان شدن دارد. یک signature نوشته‌شده با /SubFilter /adbe.pkcs7.detached یک signature کاملاً سالم ISO 32000-1 §12.8 است که Acrobat آن را به‌عنوان valid گزارش می‌دهد. این همچنین یک signature در PAdES نیست، چون ETSI EN 319 142-1 در هر baseline level به ETSI.CAdES.detached نیاز دارد. یک conformance checker در eIDAS اولی را reject و دومی را می‌پذیرد حتی اگر cryptography یکسان باشد. profile یک claim است که document دربارهٔ خود می‌کند، و درست آوردن آن claim یک call در PDF Library for Delphi است

آنچه یک signature در PDF را به یک signature در PAdES تبدیل می‌کند

ETSI EN 319 142-1 چهار baseline level را روی format در CMS تعریف می‌کند. PAdES-B-B entry point است: یک signature در CAdES در یک signature field در PDF با SubFilter ETSI.CAdES.detached و یک attribute امضا‌شدهٔ signing-certificate. PAdES-B-T یک timestamp RFC 3161 روی signature value اضافه می‌کند، ثابت می‌کند signature قبل از یک نقطه در زمان که هیچ‌کس نمی‌تواند backdate کند وجود داشته. PAdES-B-LT certificateها، CRLها و responseهای OCSP لازم برای validation را در یک Document Security Store embed می‌کند، تا file بعد از بازنشستگی infrastructure توسط CA صادرکننده قابل verify بماند. PAdES-B-LTA با یک document timestamp که evidence انباشته‌شده را هنگام ضعیف‌شدن algorithmها دوباره حفاظت می‌کند، stack را cap می‌کند

PDF Library for Delphi این conceptها را روی sign-process API خود نگاشت می‌کند. profile marker با SetSignProcessCustomSubFilter است. اگر policy شما به یک commitment-type indication نیاز دارد (proof of origin، proof of approval، یا یکی از identifierهای دیگر ETSI با شمارهٔ ۱ تا ۶)، آن از طریق SetSignProcessCommitmentType می‌رود. یک signature policy صریح با SetSignProcessSignaturePolicy attach می‌شود، که policy OID و digest آن را می‌گیرد. یک default توجه می‌طلبد: با digest algorithm روی auto، کتابخانه برای signatureهای ETSI و adbe.pkcs7.detached الگوریتم SHA-256 را انتخاب می‌کند و فقط در مسیر legacy adbe.pkcs7.sha1 به SHA-1 برمی‌گردد. به‌هرحال آن را صریح set کنید. auditorها می‌پرسند از کدام hash استفاده کرده‌اید، و یک مقدار صریح در code آسان‌تر از یک default که باید برای توضیحش manual را بخوانید قابل دفاع است

نردبان سطوح پایه PAdES شامل B-B و B-T و B-LT و B-LTA ساخته‌شده با PDF Library for Delphi که نشان می‌دهد هر سطح چه چیزی به هسته ETSI.CAdES.detached اضافه می‌کند: مهر زمانی، شواهد DSS یا مهر زمانی سند تجدیدپذیر
هر سطح baseline اِتسی یک تضمین دیگر را روی هسته واحد CAdES انباشته می‌کند، از ویژگی‌های امضاشده تا مهر زمانی سندِ تجدیدپذیر

تولید signature baseline

flat API signing را به‌شکل یک state machine یک‌شاتی drive می‌کند: یک process روی source file باز کنید، آن را پیکربندی کنید، به یک output file تمام کنید، result code را بخوانید. sequence زیر یک signature PAdES-B-B با SHA-256 تولید می‌کند. خطی که بیشتر از همه اهمیت دارد هیچ ربطی به خود signature ندارد. آن reservation عمداً بزرگ‌نماییشدهٔ /Contents است، چون تنها چیزی است که بعداً نمی‌توانید تغییر دهید اگر تا‌به‌حال یک timestamp قرار است به این signature اضافه شود

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;

NewSignProcessFromFile وقتی source اصلاً نمی‌تواند باز شود ۰ برمی‌گرداند. بعد از آن، GetSignProcessResult حالت‌های شکستی را که واقعاً در production رخ می‌دهند جدا می‌کند: ۴ یعنی یک PDF password اشتباه، ۷ یک PFX password اشتباه، ۹ یک certificate file بدون private key، ۱۰ یک output path غیرقابل‌نوشتن، ۱۱ یک failure هنگام اعمال کردن byteهای signature. log کردن code عددی کنار input file name یک ticket پشتیبانی مبهم را به یک diagnosis یک‌دقیقه‌ای تبدیل می‌کند

افزودن timestamp RFC 3161 که کتابخانه برایتان fetch نمی‌کند

PDF Library for Delphi هیچ TSA clientای ship نمی‌کند، و این یک مرز عمدی به‌جای یک gap است. کتابخانه hashای که timestamp authority باید countersign کند را محاسبه می‌کند و CMS augmented را بعداً re-embed می‌کند؛ exchange در HTTP و surgery در CMS بین آن دو به caller تعلق دارد. یک دلیل فنی سخت برای آن split هست. control در Windows CryptoAPI که nominal unsigned attributeها را اضافه می‌کند، CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR، روی layout detached SignedData که PAdES استفاده می‌کند با CRYPT_E_INVALID_INDEX fail می‌شود. پس CMS enhanced باید از یک CMS encoder تحت کنترل خودتان بیاید. هیچ کتابخانه‌ای نمی‌تواند token را با یک system call بی‌صدا fold کند، و هر آن که ادعا می‌کند surgery را جایی که نمی‌توانید ببینید انجام می‌دهد

خط لوله افزودن مهر زمانی RFC 3161 به امضای PAdES در Delphi: جداسازی هش و تعبیه PDF Library for Delphi از درخواست TSA فراخوان و کدگذاری مجدد CMS درون فضای رزروشده /Contents
کتابخانه هش می‌زند و دوباره درون‌سازی می‌کند، در حالی که کد شما توکن را واکشی و جراحی CMS را انجام می‌دهد، و نتیجه باید درون رزرو 8192 بایتی /Contents فرود بیاید
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);
    // هر دو فراخوانی زیر کد برنامه‌اند: یک HTTP POST به TSA،
    // و یک CMS re-encode که token را به‌عنوان 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;

به result codeها اینجا توجه کنید: ۱۲ یعنی signature field نام‌برده وجود ندارد، ۱۱ که CMS موجود نمی‌توانست parse شود، و ۱۳ که CMS augmented دیگر در placeholder /Contents reserved جا نمی‌شود. code ۱۳ آنی است که آسیب می‌زند، چون تنها fix re-sign کردن است: یک timestamp token معمولی با certificate chainاش ۴ تا ۶ KB است، و reservation ۸۱۹۲-byteای ساخته‌شده در طول step B-B دقیقاً برای آن وجود دارد که این step جایی برای land داشته باشد

validation از ByteRange شروع می‌شود، نه certificate chain

یک check mark سبز در یک viewer یک تصمیم اعتماد در برابر certificate store آن machine است، نه یک verdict ساختاری دربارهٔ file. validation برنامه‌ای باید پایین‌تر شروع شود، با سوالی که incremental updateها subtle می‌کنند: کدام byteها را هر signature واقعاً پوشش می‌دهد؟ هر enhancementای که اینجا بحث شد، چه یک signature دوم، یک dictionary DSS، یا یک document timestamp، از طریق incremental update می‌رسد، و هر update byteهایی بیرون از /ByteRange signature قبلی append می‌کند. آن byteهای append‌شده legit هستند. یک validator همچنان باید آن‌ها را در برابر modification policy document classify کند، و level DocMDP per-field که آن policy در آن زندگی می‌کند با GetSignatureDocMDPLevelByName قابل‌خواندن است

ممیزی چیدمان بایتی یک PDF امضاشده در Delphi: بازه‌های پوشش‌داده‌شده ByteRange، بایت‌های مستثنی /Contents، به‌روزرسانی‌های افزایشی الحاق‌شده بیرون بازه، و رأی پوشش نسبت به حجم فایل
دو بازه پوشش‌داده‌شده با استثناکردن بایت‌های خود امضا، داستان واقعی پوشش را می‌گویند، و به‌روزرسانی‌های ضمیمه‌شده بر پایه سیاست DocMDP طبقه‌بندی می‌شوند، نه اینکه صرفاً ترساننده تلقی شوند
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 یعنی واقعاً امضا شده
        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;

دو تله در این audit path زندگی می‌کنند. TPDFlibSignDoc.Open file را با یک exclusive share lock نگه می‌دارد، پس یک validator که همچنین می‌خواهد byteهای raw file را برای CMS verification هش کند باید file را قبل از باز کردن برای audit در memory بخواند. آن ترتیب را برعکس کنید و read روی lockای که خودتان set کرده‌اید fail می‌شود. تلهٔ دوم بی‌صدا به‌جای پر صدا است: counterpart در flat-API GetSignProcessByteRange Integer برمی‌گرداند در حالی که offsetهای زیرین Int64 هستند، پس بالای ۲ GB flat call بدون شکایت truncate می‌کند، که چرایی آن است که این example offsetها را از طریق class audit می‌کشد. یک غیبت نیز ارزش نام‌بردن دارد. flat layer اصلاً wrapper VerifySignature ندارد. verdictهای cryptographic از TPDFlibSignatureVerifier در level class می‌آیند، که vsValid، vsInvalid یا vsUnknown برمی‌گرداند، یا از یک validator خارجی که policy compliance شما از قبل به آن اعتماد دارد

validation طولانی‌مدت: DSS، VRI و document timestamp

PAdES-B-LT وجود دارد چون infrastructure revocation فانی است. ETSI EN 319 142-1 §5.4.2.2 Document Security Store را مشخص می‌کند: یک dictionary در سطح document که certificateها، CRLها و responseهای OCSP را حمل می‌کند، به‌اختیار per signature از طریق entryهای VRI که بر اساس hash از /Contents هر signature key می‌شوند، index می‌شوند. flow در PDF Library for Delphi design timestamp را بازتاب می‌دهد. NewPAdESDSSProcessFromFile process را باز می‌کند؛ AddPAdESDSSCertificate، AddPAdESDSSCRL و AddPAdESDSSOCSP blobهای DER را می‌پذیرند؛ AddPAdESDSSVRI material انتخابی را به یک signature bind می‌کند؛ EndPAdESDSSProcessToFile همه‌چیز را به‌عنوان یک incremental update می‌نویسد. قسمت سخت سمت شما می‌ماند. fetch کردن material revocation، و قضاوت اینکه آیا به‌اندازهٔ کافی تازه است که ارزش embed شدن داشته باشد، کار caller است. کتابخانه تضمین می‌کند dictionaryها از نظر ساختاری conformant هستند؛ نمی‌تواند تضمین کند responder OCSP شما راست گفته است

endpoint archival، B-LTA، یک document timestamp اضافه می‌کند: یک signature field مجزا که نوع آن به‌جای Sig، DocTimeStamp است، تولیدشده از طریق SetSignProcessDocTimeStamp با یک signature length reserved. این signature timestamp از step B-T را replace نمی‌کند. signature timestamp ثابت می‌کند چه زمانی یک signature خاص وجود داشته؛ document timestamp کل file، شامل evidence در DSS، را حفاظت می‌کند، و عنصری است که یک archive طولانی‌مدت هر چند سال یک‌بار هنگام ضعیف‌شدن algorithmها renew می‌کند. یک profile archival بالغ هر دو را حمل می‌کند. برای readerهایی که پیش از این ساختارها هستند، TPDFlibSignDoc.EnsurePAdESExtensions extension توسعه‌دهندهٔ ESIC را در catalog document ضبط می‌کند، اعلام می‌کند که file از featureهای تعریف‌شدهٔ ETSI استفاده می‌کند

یک واکنش به همهٔ اینها ارزش دفع شدن دارد، چون شبیه یک bug به‌نظر می‌رسد و نیست. یک viewer اغلب روی fileای که structure در PAdES آن کاملاً درست است «validity unknown» گزارش می‌دهد. trust و structure محور مستقل هستند. viewer ساده‌اند نمی‌تواند signer را به rootای روی آن machine chain کند، که با CAهای private و certificateهای test معمول است، حتی وقتی audit ByteRange و verify کردن CMS هر دو pass می‌شوند. fix آن است که root certificate را به‌درستی distribute کنید، یا در برابر trusted listهای اروپایی ارزیابی کنید وقتی status واجد شرایط eIDAS هدف واقعی است، به‌جای دست زدن به code signing

برای دیدگاه سمت audit، یعنی enumerate کردن signature fieldها در سراسر یک corpus، dump کردن layoutهای ByteRange و خواندن levelهای DocMDP به‌صورت bulk، به قطعه همراه دربارهٔ workbench compliance و signing نگاه کنید. documentهای signed که همچنین باید policy archival را برآورده کنند در workflow توصیف‌شده در preflight در PDF/A و PDF/UA در Delphi متعلق هستند. مستندات کامل API و downloadهای evaluation در page محصول losLab PDF Library برای Delphi هستند