Technisch artikel

Excel-handtekeningen in Delphi verifiëren en hertekenen

HotXLS verifieert en hertekent Excel-digitale handtekeningen in Delphi zonder Excel geïnstalleerd. De unit lxSignatureOpcIntegrity controleert ECMA-376 Part 2-packagehandtekeningen rechtstreeks vanuit de werkboekbytes: RSA-SHA256 over de canonieke SignedInfo, elke SHA-256 Reference-digest, en de exacte onderdelen die elke handtekening selecteert. Certificaatketenvertrouwen is een aparte, expliciete stap, en bewerken of hertekenen publiceert alleen output die opnieuw verifieert

De gebruikelijke aanleiding is een documentworkflow die het bureaublad ontgroeide. Een controller tekent een budgetwerkboek in Excel, een Delphi-service moet later de handtekening bevestigen voordat hij de cijfers importeert, en een derde stap moet één werkblad corrigeren en opnieuw tekenen onder een servicecertificaat. Excel daarvoor op een server automatiseren is fragiel en niet-ondersteund, en de handtekening als ja/nee-vlag behandelen verbergt de twee vragen die er werkelijk toe doen: tot welke bytes heeft de ondertekenaar zich verbonden, en vertrouwt u de persoon die die sleutel vasthoudt

Wat dekt een OPC-packagehandtekening werkelijk?

Een OPC-packagehandtekening dekt alleen de onderdelen die zijn Manifest opsomt, niet het hele XLSX-bestand. ECMA-376 Part 2, §13 (Digital Signatures) plaatst een signature origin part in het package, doorgaans /_xmlsignatures/origin.sigs, met relationships naar een of meer signature parts zoals /_xmlsignatures/sig1.xml. Elke signature part is een W3C XML-DSig Signature waarvan SignedInfo naar een intern idPackageObject verwijst, en dat Object draagt een Manifest van References naar individuele package parts, elk met zijn eigen digest

HotXLS ondersteunt drie manieren waarop een Reference een onderdeel kan binden, en ze zijn niet uitwisselbaar. Een rauwe Reference hasht de opgeslagen part-bytes. Een canonieke XML-Reference hasht de vorm Canonical XML (without comments), zodat attribuutvolgorde en de spelling van namespace-declaraties niet meer tellen. Een relationship-Reference past eerst de OPC RelationshipTransform toe en canoniseert daarna, en die transform houdt alleen de relationships over die door zijn selectors SourceId of SourceType matchen. Een selector authenticeert exact de relationships die hij selecteert: de rest van dat .rels-onderdeel kan vrij veranderen terwijl de handtekening geldig blijft

Anatomie van de HotXLS OPC digitale handtekening voor Delphi: een origin.sigs-part leidt naar XML-DSig-signature parts waarvan het Manifest elk package part bindt met een rauwe, canonieke XML- of RelationshipTransform-reference
Drie Reference-modi zijn niet uitwisselbaar: raw hasht de opgeslagen bytes, canonieke XML negeert attribuutspelling, en relationship-selectors laten de niet-geselecteerde .rels-entries vrij veranderen

Dat laatste punt is de reden dat HotXLS dekking meldt in plaats van één groen vinkje. TXLSSignatureOpcIntegrityResult.Coverage is een array van TXLSSignatureOpcCoverage-records, één per geauthenticeerde Manifest-Reference, elk met ReferenceUri, PartName, ContentType, Transforms, RelationshipSourceIds en RelationshipSourceTypes. Leest uw import sheet3.xml en noemt geen enkele descriptor hem, dan zegt de handtekening niets over de cijfers die u op het punt staat te laden

Handtekeningen verifiëren vanuit een onveranderlijke ZIP-snapshot

HotXLS verifieert een package vanuit één onveranderlijke byte-snapshot, nooit vanuit het bestand op schijf of een levende TWorkbook. XlsVerifySignatureOpcPackage en zijn multi-signature-broers nemen een array TBytes aan, lezen alleen die array en halen nooit externe doelen, certificaten, lokale bestanden of netwerkresources op. Het punt is dat de bytes die u verifieerde de bytes zijn die u daarna parst: wordt het bestand later opnieuw gelezen, dan heeft een ander proces hem intussen misschien vervangen

Canonicalisatie is waar zelfgebouwde verifiers doorgaans de mist in gaan. SignedInfo en het package Object moeten worden gecanoniseerd in de namespace-context van de complete Signature-envelop. Een geïsoleerd fragment uit het document geknipt erft andere namespace-declaraties dan de verifier op zijn plek ziet, dus zijn C14N-bytes verschillen, de digest verschilt, en een volstrekt geldige handtekening faalt. HotXLS gebruikt de volledige envelopcontext aan zowel de verificatie- als de uitgiftekant

Voor packages die meer dan één handtekening kunnen dragen verifieert XlsVerifyAllSignaturesOpcPackage elke bijgevoegde handtekening en houdt hij de individuele uitkomsten vast. De geaggregeerde status is xsoiIntegrityVerified alleen wanneer elke toegelaten handtekening verifieert; anders is het de eerste misser in inventarisvolgorde, en elk rapport vertelt u nog steeds welke signature part faalde

uses
  System.SysUtils, System.IOUtils, lxSignatureOpcIntegrity;

function AllSignaturesIntact(const FileName: string): Boolean;
var
  Bytes: TBytes;
  Reports: TXLSSignatureOpcVerifications;
  Status: TXLSSignatureOpcIntegrityStatus;
  I, J: Integer;
begin
  Bytes := TFile.ReadAllBytes(FileName);  // één snapshot, geverifieerd en geparsed
  Status := XlsVerifyAllSignaturesOpcPackage(Bytes, Reports);
  for I := 0 to High(Reports) do
  begin
    Writeln(String(Reports[I].SignaturePart), ': ', Ord(Reports[I].Status));
    if Reports[I].Status = xsoiIntegrityVerified then
      for J := 0 to High(Reports[I].Integrity.Coverage) do
        Writeln('  covers ', String(Reports[I].Integrity.Coverage[J].PartName));
  end;
  Result := Status = xsoiIntegrityVerified;  // xsoiUnsigned is geen slaag
end;

De statuswaarden zijn specifiek genoeg om op te handelen. xsoiDigestMismatch betekent dat een geselecteerd onderdeel na het ondertekenen veranderde, xsoiInvalidSignature dat de RSA-waarde niet tegen SignedInfo verifieert met de sleutel van het ingebedde certificaat, xsoiPartUnavailable dat een geselecteerd onderdeel ontbreekt, en xsoiUnsupportedProfile of xsoiResourceLimit dat de verifier weigerde in plaats van te gokken. De ZIP-laag eronder past dezelfde fail-closed lezing toe als beschreven in het artikel over ZIP EOCD-validatie voor onbetrouwbare XLSX-bestanden, en part-opzoeking volgt de regels in XLSX OPC relationship resolution

Waarom maakt het bewerken van een ondertekend werkboek de handtekening ongeldig?

Een ondertekend werkboek bewerken maakt de handtekening ongeldig zodra een veranderde byte binnen een ondertekende selectie valt, want de opgeslagen SHA-256-digest voor die Reference matcht niet meer. Een spreadsheet-writer die het werkboek laadt en opnieuw opslaat raakt bijna altijd ondertekende onderdelen: hij serialiseert XML opnieuw, hernummert shared strings, herschrijft workbook.xml en het content type-manifest. Zijn de celwaarden identiek, dan zijn de bytes het niet, en de volgende verificatie geeft xsoiDigestMismatch terug

HotXLS laat u de vraag stellen voordat u aan een bewerking vastzit. XlsEditVerifiedOpcPackage vervangt bestaande onderdelen in een geverifieerd package en verifieert daarna de kandidaat. Met xsoePreserve geeft hij xsoePreserved terug alleen wanneer de handtekening nog verifieert, wat gebeurt wanneer de bewerkte onderdelen buiten elke ondertekende selectie liggen, en anders xsoeWouldInvalidate. Met xsoeInvalidateChanged haalt hij de handtekeningsbinding uit de root-relationships en [Content_Types].xml en geeft hij een expliciet onondertekend package terug als xsoeInvalidated, in plaats van u een bestand aan te reiken dat er nog ondertekend uitziet

function EditKeepsSignature(const Signed, NewSheetXml: TBytes;
  out Edited: TBytes): Boolean;
var
  Edits: TXLSSignatureOpcEdits;
  Details: TXLSSignatureOpcEditResult;
begin
  SetLength(Edits, 1);
  Edits[0].PartName := '/xl/worksheets/sheet2.xml';
  Edits[0].Data := NewSheetXml;
  case XlsEditVerifiedOpcPackage(Signed, Edits, xsoePreserve, Edited, Details) of
    xsoePreserved:       Result := True;   // sheet2 lag buiten de ondertekende selectie
    xsoeWouldInvalidate: Result := False;  // een ondertekende digest dekt sheet2
  else
    raise Exception.CreateFmt('Edit refused, original integrity %d',
      [Ord(Details.OriginalIntegrity)]);
  end;
end;

Integriteit tegenover vertrouwen: wat bewijst een geverifieerde handtekening?

Een geverifieerde handtekening bewijst dat de geselecteerde onderdelen onveranderd zijn sinds iemand met de privésleutel ze ondertekende, en niets over wie die iemand is. HotXLS houdt de twee lagen bewust uit elkaar: elke TXLSSignatureOpcIntegrityResult draagt Trust = xsstUntrusted, geslaagde controles inbegrepen, en ClaimedSignatureTime is data van de ondertekenaar, geen vertrouwd tijdstempel. Iedereen kan een self-signed certificaat genereren, een werkboek ondertekenen en door de integriteitscontrole komen

HotXLS verifieert Excel OPC package digitale handtekeningen in Delphi in twee lagen: integriteitsdigestcontroles over een onveranderlijke byte-snapshot die altijd xsstUntrusted melden, daarna XlsVerifySignatureOpcPackageWithTrust met een policy van de aanroeper
Een geverifieerde handtekening bewijst dat de geselecteerde onderdelen onveranderd zijn, terwijl vertrouwen uw eigen anchors, een UTC-verificatietijd en de document-signing EKU vereist voordat xsotTrustedSelection iets betekent

Vertrouwen is een aparte aanroep met een policy van de aanroeper. XlsDefaultSignatureOpcTrustPolicy geeft een fail-closed policy terug zonder anchors, zonder verificatietijd, met de document-signing EKU 1.3.6.1.4.1.311.10.3.12 en xsrvRequireCached-intrekking. XlsVerifySignatureOpcPackageWithTrust draait eerst de volledige integriteitsverifier en evalueert daarna het certificaat dat hem werkelijk doorstond; een aanroeper kan geen ander leafcertificaat substitueren

uses
  System.SysUtils, System.DateUtils, lxSignatureOpcIntegrity;

function SignedUnderCorporateCA(const Bytes, RootDer, IssuingCaDer: TBytes): Boolean;
var
  Policy: TXLSSignatureOpcTrustPolicy;
  Trust: TXLSSignatureOpcTrustResult;
begin
  Policy := XlsDefaultSignatureOpcTrustPolicy;
  SetLength(Policy.TrustAnchors, 1);
  Policy.TrustAnchors[0] := RootDer;
  SetLength(Policy.Intermediates, 1);
  Policy.Intermediates[0] := IssuingCaDer;
  Policy.VerificationTimeUtc := TTimeZone.Local.ToUniversalTime(Now);

  Result := XlsVerifySignatureOpcPackageWithTrust(Bytes, Policy, Trust)
    = xsotTrustedSelection;
  if not Result then
    Writeln(Format('trust %d, integrity %d, policy error %.8x, chain errors %.8x',
      [Ord(Trust.Status), Ord(Trust.IntegrityStatus),
       Trust.NativePolicyError, Trust.NativeChainErrors]));
end;

De Windows-backend heeft twee valkuilen die makkelijk te missen zijn wanneer u WinCrypt zelf aanroept. Dat CertVerifyCertificateChainPolicy True teruggeeft betekent alleen dat de aanroep uitvoerde; acceptatie vereist nog steeds een nul-dwError in de policystatus, nul geaggregeerde ketenfouten, een expliciet digitalSignature key usage op het leaf en de vereiste EKU. HotXLS ontdekte dat een certificaat zonder key usage de gewone basispolicy kan passeren, dus hij controleert DigitalSignatureUsage en RequiredEkuPresent zelf. De tweede valkuil: een exclusieve root store combineren met hRestrictedOther in de chain engine-config geeft E_INVALIDARG op de huidige backend, dus HotXLS gebruikt de exclusieve root store van de aanroeper en autoriseert daarna elk element van de teruggegeven primaire keten tegen de DER-bytes die u aanleverde. Een issuer die toevallig in een lokale system store zit wordt nooit vertrouwd

xsotTrustedSelection beschrijft nog steeds alleen die handtekening en zijn gemelde dekking. Niet-ondertekende cellen en niet-geselecteerde relationships kunnen veranderen terwijl de geselecteerde handtekening vertrouwd blijft, en daarom geeft het trust-resultaat ook PackageSha256 en CertificateSha256 terug voor uw auditlog

Hoe hertekent of contratekent u een werkboek zonder Excel?

Op byteniveau hertekent HotXLS via expliciete, geautoriseerde transacties, en de signed-save-paden van de Workbook hergebruiken dezelfde geverifieerde pipelines. XlsReplaceVerifiedSignatureOpcPackage wisselt één geverifieerde handtekening om voor een nieuwe, XlsEditAndReplaceSelectedSignatureOpcPackage past part-bewerkingen toe en vervangt één geselecteerde handtekening in dezelfde transactie, en XlsAppendVerifiedSignatureOpcPackage voegt een handtekening toe terwijl elke geverifieerde origineel byte voor byte blijft. De replace-aanroepen nemen een TXLSSignatureOpcReplaceAuthorization aan, de append-aanroep verifieert eerst elke origineel, en allemaal nemen ze een selectie TXLSSignatureOpcIssueOptions en een signer-callback aan; de library raakt uw privésleutel nooit aan

Het autorisatierecord bindt de operatie aan de exacte origineel die u goedkeurde: ExpectedPackageSha256, ExpectedSignaturePart en ExpectedCertificateSha256, allemaal pas vergeleken nadat de oude handtekening volledig heeft geverifieerd. Elk bewerkt onderdeel moet door de nieuwe selectie worden gedekt, elke niet-geselecteerde handtekening moet nog steeds tegen het staged package verifiëren voordat de signer wordt aangeroepen, en anders geeft de aanroep xspsWouldInvalidate terug

HotXLS hertekent een Excel XLSX-package in Delphi via XlsEditAndReplaceSelectedSignatureOpcPackage, dat de oude handtekening verifieert, de verwachte package-, part- en certificaathashes controleert en een signer-callback aanroept
Het autorisatierecord bindt de replace aan de goedgekeurde origineel, niet-geselecteerde handtekeningen moeten nog steeds op het staged package verifiëren, en de privésleutel blijft binnen uw CNG, HSM of smart card
uses
  System.SysUtils, System.DateUtils, lxHash, lxSignatureOpcIntegrity;

type
  TWorkbookResigner = class
  private
    function SignDigest(const Request: TXLSSignatureOpcSelectedReplaceRequest;
      var Signature: TBytes): TXLSSignatureOpcSignerStatus;
  public
    function Resign(const Approved, NewSheetXml, NewCertDer: TBytes;
      const OldCertSha256: WideString; var Output: TBytes): Boolean;
  end;

function TWorkbookResigner.SignDigest(
  const Request: TXLSSignatureOpcSelectedReplaceRequest;
  var Signature: TBytes): TXLSSignatureOpcSignerStatus;
begin
  // Request.NewSignature.Digest is de SHA-256 van 32 bytes die de library berekende;
  // RsaSignSha256Pkcs1 staat voor uw eigen CNG-, HSM- of smart card-wrapper en
  // moet de volledige big-endian RSASSA-PKCS1-v1_5-handtekening teruggeven
  if RsaSignSha256Pkcs1(Request.NewSignature.Digest, Signature) then
    Result := xsosSigned
  else
    Result := xsosFailed;
end;

function TWorkbookResigner.Resign(const Approved, NewSheetXml, NewCertDer: TBytes;
  const OldCertSha256: WideString; var Output: TBytes): Boolean;
var
  Auth: TXLSSignatureOpcReplaceAuthorization;
  Options: TXLSSignatureOpcIssueOptions;
  Edits: TXLSSignatureOpcEdits;
  Details: TXLSSignatureOpcSelectedReplaceResult;
begin
  Auth.ExpectedPackageSha256 := XLSComputeSHA256Hex(Approved); // of de hash vastgelegd bij goedkeuring
  Auth.ExpectedSignaturePart := '/_xmlsignatures/sig1.xml';
  Auth.ExpectedCertificateSha256 := OldCertSha256;

  SetLength(Edits, 1);
  Edits[0].PartName := '/xl/worksheets/sheet1.xml';
  Edits[0].Data := NewSheetXml;

  SetLength(Options.References, 2);
  Options.References[0].PartName := '/xl/workbook.xml';
  Options.References[0].Mode := xsimCanonicalXml;
  Options.References[1].PartName := '/xl/worksheets/sheet1.xml';
  Options.References[1].Mode := xsimCanonicalXml;
  Options.ClaimedSignatureTimeUtc := FormatDateTime('yyyy"-"mm"-"dd"T"hh":"nn":"ss"Z"',
    TTimeZone.Local.ToUniversalTime(Now));

  Result := XlsEditAndReplaceSelectedSignatureOpcPackage(Approved, NewCertDer,
    Auth, Options, Edits, SignDigest, Output, Details) = xspsReplaced;
end;

Twee details in die code zijn bewust zo. Output en Details moeten aparte variabelen zijn; een veld van Details als Output meegeven is expliciet niet-ondersteund, want de library kan geen package publiceren en een certificaatrapport in dezelfde opslag houden. En de oude dekking wordt niet geërfd: de nieuwe handtekening authenticeert exact de References die u opsomt, dus beslis opnieuw op welke onderdelen een downstream-consument zal leunen

Contratekenen gebruikt dezelfde append-aanroep. Sinds HotXLS 2.384.296 mag TXLSSignatureOpcIssueOptions.References dat aan XlsAppendVerifiedSignatureOpcPackage wordt meegegeven onderdelen benoemen uit de geverifieerde originele handtekeninginventaris, dus een reviewer kan over een bestaande signature part tekenen, meerdere contratekeningen ketenen, of signature parts en content parts in één selectie mengen. Elke toegevoegde handtekening legt alleen zijn directe selecties vast. De kwalificatie voor die release draaide 222 boundary checks en 122 onafhankelijke checks met de .NET WindowsBase packaging verifier over 7 output-packages, en bevestigde exact één nieuwe handtekening en byte-identieke originele handtekeningen en certificaten

Valkuilen en grenzen die het kennen waard zijn

De meest leerzame bug kwam van een teken dat volstrekt legaal is. OPC Reference-URI's dragen het content type als query, bijvoorbeeld /xl/worksheets/sheet1.xml?ContentType=application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml. Een vroege HotXLS-issuer percent-encodeerde de letterlijke + als %2B. De eigen RSA-, C14N- en Reference-controles van HotXLS accepteerden het resultaat, maar WindowsBase meldde InvalidSignature; een gecontroleerd staal dat alleen die spelling veranderde ging van InvalidSignature naar Success. De issuer houdt nu ondersteunde letterlijke ASCII-query-interpunctie aan terwijl hij XML-attributen nog steeds escaped, en de les voor elke ondertekenaar is om tegen een onafhankelijke native verifier te testen, niet alleen uw eigen

  • De trust-backend is alleen voor Windows; andere doelen geven xsotTrustUnavailable terug nadat de integriteit slaagt
  • De verifier accepteert packages tot 64 MiB, 16 MiB per part, en ten hoogste 32 signature parts onder één origin
  • Uitgeven accepteert tot 256 References en 4096 gecombineerde selectors per handtekening
  • Het ondersteunde profiel is RSA-SHA256, SHA-256-digests en Canonical XML without comments; andere algoritmen, extra application Objects en externe References geven xsoiUnsupportedProfile terug
  • xsrvRequireCached gebruikt cache-only intrekkingsdata; ontbrekende status wijst af, en xsrvNotChecked geeft geen intrekkingszekerheid
  • Een werkboek ondertekenen dat Excel ook moet accepteren loopt nog steeds tegen de package-regels uit waarom Excel een geldige XLSX repareert; een gedupliceerde Override of relationship Id breekt het bestand voordat er ook maar één handtekening wordt gecontroleerd

FAQ

Betekent een HotXLS-integriteitscontrole dat de ondertekenaar vertrouwd is?

Nee. XlsVerifySignatureOpcPackage bewijst dat de geselecteerde onderdelen matchen met hun digests onder de sleutel van het ingebedde certificaat en meldt altijd xsstUntrusted. Gebruik XlsVerifySignatureOpcPackageWithTrust met uw eigen anchors en een UTC-verificatietijd om de certificaatketen te evalueren

Waarom toont Excel mijn werkboekhandtekening als ongeldig na een Delphi-bewerking?

Een opgeslagen werkboek herschrijft ondertekende XML-onderdelen, dus de SHA-256-digest van ten minste één Reference verandert. Gebruik XlsEditVerifiedOpcPackage met xsoePreserve om eerst een bewerking te toetsen, of XlsEditAndReplaceSelectedSignatureOpcPackage om te bewerken en te hertekenen in één geverifieerde transactie

Kan HotXLS tekenen met een sleutel op een smart card of HSM?

Ja, want tekenen is een callback van de aanroeper. De library geeft de canonieke SignedInfo en zijn SHA-256-digest van 32 bytes door, uw code geeft de RSASSA-PKCS1-v1_5-handtekening terug, en de privésleutel verlaat nooit de provider die u beheerst

OPC-handtekeningverificatie, trust-evaluatie, contratekenen en geverifieerd hertekenen maken deel uit van HotXLS, de native Excel-spreadsheetcomponent voor Delphi en C++Builder, die XLS, XLSX, XLSB en ODS leest en schrijft zonder Excel of COM-automatisering