Teknisk artikel

Verificér og re-signér Excel digitale signaturer i Delphi

HotXLS verificerer og re-signerer Excel digitale signaturer i Delphi uden Excel installeret. Uniten lxSignatureOpcIntegrity tjekker ECMA-376 Part 2 package-signaturer direkte fra projektmappe-bytes: RSA-SHA256 over den kanoniske SignedInfo, hver SHA-256 Reference-digest og de præcise dele, hver signatur vælger. Certifikatkæde-trust er et separat, eksplicit trin, og redigering eller re-signering publicerer kun output, der verificerer igen

Den sædvanlige udløser er et dokumentworkflow, der voksede ud over desktoppen. En controller signerer en budgetprojektmappe i Excel, en Delphi-tjeneste skal senere bekræfte signaturen, før tallene importeres, og et tredje trin behøver at rette ét ark og signere igen under et servicecertifikat. At automatisere Excel på en server til det er skrøbeligt og ikke understøttet, og at behandle signaturen som et ja/nej-flag skjuler de to spørgsmål, der reelt betyder noget: hvilke bytes forpligtede signereren sig til, og stoler du på personen, der holder den nøgle

Hvad dækker en OPC package-signatur egentlig?

En OPC package-signatur dækker kun de dele, dens Manifest lister, ikke hele XLSX-filen. ECMA-376 Part 2, §13 (Digital Signatures) placerer en signature origin-part i pakken, typisk /_xmlsignatures/origin.sigs, med relationships til én eller flere signaturdele som /_xmlsignatures/sig1.xml. Hver signaturdel er en W3C XML-DSig Signature, hvis SignedInfo refererer et internt idPackageObject, og det Object bærer et Manifest af References til enkelte package-dele, hver med sin egen digest

HotXLS understøtter tre måder, en Reference kan binde en del på, og de er ikke udskiftelige. En rå Reference hasher de gemte part-bytes. En kanonisk XML-Reference hasher Canonical XML-formen (uden kommentarer), så attributrækkefølge og namespace-erklæringers stavning ikke længere betyder noget. En relationship-Reference anvender først OPC RelationshipTransform og kanoniserer derefter, og den transform beholder kun de relationships, dens SourceId- eller SourceType-selectors matcher. En selector autentificerer præcis de relationships, den vælger: resten af den .rels-del kan ændre sig frit, mens signaturen forbliver gyldig

HotXLS OPC digital signatur-anatomi til Delphi: en origin.sigs-part fører til XML-DSig-signaturdele, hvis Manifest binder hver package-del med en raw-, kanonisk XML- eller RelationshipTransform-reference
Tre Reference-modes er ikke udskiftelige: raw hasher gemte bytes, kanonisk XML ignorerer attributstavning, og relationship-selectors lader de ikke-valgte .rels-indgange stå frit til at ændre sig

Det sidste punkt er grunden til, at HotXLS rapporterer dækning i stedet for ét enkelt grønt hak. TXLSSignatureOpcIntegrityResult.Coverage er et array af TXLSSignatureOpcCoverage-poster, én pr. autentificeret Manifest-Reference, hver med ReferenceUri, PartName, ContentType, Transforms, RelationshipSourceIds og RelationshipSourceTypes. Læser din import sheet3.xml, og ingen deskriptor navngiver den, siger signaturen intet om de tal, du er ved at loade

Verificering af signaturer fra et uforanderligt ZIP-snapshot

HotXLS verificerer en package fra ét uforanderligt byte-snapshot, aldrig fra filen på disk eller en live TWorkbook. XlsVerifySignatureOpcPackage og dens multi-signatur-søskende tager et TBytes-array, læser kun det array og henter aldrig eksterne mål, certifikater, lokale filer eller netværksressourcer. Pointen er, at de bytes, du verificerede, er de bytes, du parser bagefter: genlæses filen senere, kan en anden proces have erstattet den i mellemtiden

Kanonisering er dér, hjemmedyrkede verifikatorer plejer at gå galt. SignedInfo og package-Object'et skal kanoniseres i namespace-konteksten af den komplette Signature-konvolut. Et isoleret fragment skåret ud af dokumentet arver andre namespace-erklæringer, end verifikatoren ser på stedet, så dets C14N-bytes adskiller sig, digesten adskiller sig, og en fuldt ud gyldig signatur fejler. HotXLS bruger den fulde konvolutkontekst på både verifikations- og udstedelsessiden

Til packages, der kan bære mere end én signatur, verificerer XlsVerifyAllSignaturesOpcPackage hver vedhæftet signatur og beholder de enkelte udfald. Aggregatstatussen er xsoiIntegrityVerified kun, når hver tilladt signatur verificerer; ellers er det den første fiasko i inventarrækkefølgen, og hver rapport fortæller stadig, hvilken signaturdel der fejlede

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);  // ét snapshot, verificeret og parset
  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 er ikke et bestået
end;

Statusværdierne er specifikke nok til at handle på. xsoiDigestMismatch betyder, at en valgt del ændrede sig efter signeringen, xsoiInvalidSignature betyder, at RSA-værdien ikke verificerer mod SignedInfo med det indlejrede certifikats nøgle, xsoiPartUnavailable betyder, at en valgt del mangler, og xsoiUnsupportedProfile eller xsoiResourceLimit betyder, at verifikatoren nægtede i stedet for at gætte. ZIP-laget nedenunder anvender samme fail-closed-læsning, som er beskrevet i artiklen om ZIP EOCD-validering for utroværdige XLSX-filer, og part-opslag følger reglerne i XLSX OPC relationship resolution

Hvorfor invaliderer redigering af en signeret projektmappe signaturen?

Redigering af en signeret projektmappe invaliderer signaturen, så snart en ændret byte falder inden for en signeret selektion, fordi den gemte SHA-256-digest for den Reference ikke længere matcher. En regnearkswriter, der loader projektmappen og gemmer den igen, rører næsten altid signede dele: den reserialiserer XML, omnummererer shared strings, omskriver workbook.xml og content type-manifestet. Selv når celleværdierne er identiske, er bytes det ikke, og næste verifikation returnerer xsoiDigestMismatch

HotXLS lader dig stille spørgsmålet, før du forpligter dig til en redigering. XlsEditVerifiedOpcPackage erstatter eksisterende dele i en verificeret package og verificerer derefter kandidaten. Med xsoePreserve returnerer den xsoePreserved kun, når signaturen stadig verificerer, hvilket sker, når de redigerede dele ligger uden for enhver signeret selektion, og xsoeWouldInvalidate ellers. Med xsoeInvalidateChanged fjerner den signaturbindingen fra rod-relationships'erne og [Content_Types].xml og returnerer en eksplicit usigneret package som xsoeInvalidated i stedet for at give dig en fil, der stadig ligner en signeret

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 var uden for den signede selektion
    xsoeWouldInvalidate: Result := False;  // en signeret digest dækker sheet2
  else
    raise Exception.CreateFmt('Edit refused, original integrity %d',
      [Ord(Details.OriginalIntegrity)]);
  end;
end;

Integritet versus trust: hvad beviser en verificeret signatur?

En verificeret signatur beviser, at de valgte dele er uændrede, siden en person med den private nøgle signede dem, og intet om, hvem den person er. HotXLS holder de to lag adskilt med vilje: hver TXLSSignatureOpcIntegrityResult bærer Trust = xsstUntrusted, inklusive vellykkede tjek, og ClaimedSignatureTime er signerer-leverede data, ikke et troværdigt tidsstempel. Enhver kan generere et self-signed certifikat, signere en projektmappe og bestå integritetstjekket

HotXLS verificerer Excel OPC package-digitale signaturer i Delphi i to lag: integritetsdigest-tjek over et uforanderligt byte-snapshot, der altid rapporterer xsstUntrusted, derefter XlsVerifySignatureOpcPackageWithTrust med en kalder-ejet policy
En verificeret signatur beviser, at de valgte dele er uændrede, mens trust kræver dine egne ankre, en UTC-verifikationstid og document-signing-EKU'en, før xsotTrustedSelection betyder noget

Trust er et separat kald med en kalder-ejet policy. XlsDefaultSignatureOpcTrustPolicy returnerer en fail-closed-policy uden ankre, uden verifikationstid, med document-signing-EKU'en 1.3.6.1.4.1.311.10.3.12 og xsrvRequireCached-tilbagekaldelse. XlsVerifySignatureOpcPackageWithTrust kører først den komplette integritetsverifikator og evaluerer derefter det certifikat, der reelt bestod den; en kalder kan ikke erstatte et andet bladcertifikat

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;

Windows-backenden har to fælder, der er lette at overse, når du kalder WinCrypt selv. At CertVerifyCertificateChainPolicy returnerer True betyder kun, at kaldet blev udført; accept kræver stadig en nul dwError i policystatus, nul aggregerede kædefejl, en eksplicit digitalSignature key usage på bladet og den påkrævede EKU. HotXLS fandt, at et certifikat uden key usage kan bestå den almindelige basepolicy, så den tjekker DigitalSignatureUsage og RequiredEkuPresent selv. Den anden fælde: at kombinere et eksklusivt root store med hRestrictedOther i chain engine-konfigurationen returnerer E_INVALIDARG på den aktuelle backend, så HotXLS bruger det eksklusive kalder-root store og autoriserer derefter hvert element af den returnerede primære kæde mod de DER-bytes, du gav med. En udsteder, der blot tilfældigvis sidder i et lokalt system store, bliver aldrig troværdig

xsotTrustedSelection beskriver stadig kun den signatur og dens rapporterede dækning. Usignerede celler og ikke-valgte relationships kan ændre sig, mens den valgte signatur forbliver troværdig, hvilket er grunden til, at trust-resultatet også returnerer PackageSha256 og CertificateSha256 til dit auditlog

Hvordan re-signerer eller kontra-signerer man en projektmappe uden Excel?

På byte-array-niveau re-signerer HotXLS gennem eksplicitte, autoriserede transaktioner, og projektmappens signede save-stier genbruger de samme verificerede pipelines. XlsReplaceVerifiedSignatureOpcPackage bytter én verificeret signatur med en ny, XlsEditAndReplaceSelectedSignatureOpcPackage anvender partredigeringer og erstatter én valgt signatur i samme transaktion, og XlsAppendVerifiedSignatureOpcPackage tilføjer en signatur, mens den beholder hver verificeret original byte for byte. Replace-kaldene tager en TXLSSignatureOpcReplaceAuthorization, append-kaldet verificerer hver original først, og alle tager en TXLSSignatureOpcIssueOptions-selektion og en signerer-callback; biblioteket rører aldrig din private nøgle

Autorisationsposten binder operationen til den præcise original, du godkendte: ExpectedPackageSha256, ExpectedSignaturePart og ExpectedCertificateSha256, alle sammenlignet først, efter at den gamle signatur har verificeret fuldt ud. Hver redigeret del skal være dækket af den nye selektion, hver ikke-valgt signatur skal stadig verificere mod den stagede package, før signereren kaldes, og kaldet returnerer xspsWouldInvalidate ellers

HotXLS re-signerer en Excel XLSX-package i Delphi gennem XlsEditAndReplaceSelectedSignatureOpcPackage, som verificerer den gamle signatur, tjekker de forventede package-, part- og certifikathashes og kalder en signerer-callback
Autorisationsposten binder erstatningen til den godkendte original, ikke-valgte signaturer skal stadig verificere på den stagede package, og den private nøgle forbliver inde i din CNG, HSM eller 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 er den 32-byte SHA-256, biblioteket beregnede;
  // RsaSignSha256Pkcs1 står for din egen CNG-, HSM- eller smart card-wrapper og
  // skal returnere den fulde big-endian RSASSA-PKCS1-v1_5-signatur
  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); // eller hashen gemt ved godkendelsen
  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;

To detaljer i den kode er bevidste. Output og Details skal være separate variable; at give et felt af Details med som Output er eksplicit ikke-understøttet, fordi biblioteket ikke kan publicere en package og beholde en certifikatrapport i samme lager. Og den gamle dækning nedarves ikke: den nye signatur autentificerer præcis de References, du lister, så beslut igen, hvilke dele en downstream-forbruger vil læne sig op ad

Kontra-signering bruger samme append-kald. Siden HotXLS 2.384.296 kan TXLSSignatureOpcIssueOptions.References givet til XlsAppendVerifiedSignatureOpcPackage navngive dele fra det verificerede original-signaturinventar, så en reviewer kan signere over en eksisterende signaturdel, kæde flere kontra-signaturer eller blande signaturdele og indholdsdele i én selektion. Hver tilføjet signatur registrerer kun sine direkte selektioner. Kvalificeringen for den release kørte 222 grænsetjek og 122 uafhængige tjek med .NET WindowsBase packaging-verifikatoren hen over 7 output-packages og bekræftede præcis én ny signatur og byte-identiske originale signaturer og certifikater

Fælder og grænser værd at kende

Den mest lærerige fejl kom fra et tegn, der er fuldt lovligt. OPC Reference-URI'er bærer content typen som en query, for eksempel /xl/worksheets/sheet1.xml?ContentType=application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml. En tidlig HotXLS-udsteder percent-encodel den literale + som %2B. HotXLS' egne RSA-, C14N- og Reference-tjek accepterede resultatet, men WindowsBase rapporterede InvalidSignature; en kontrolleret prøve, der kun ændrede den stavning, gik fra InvalidSignature til Success. Udstederen beholder nu understøttet literal ASCII query-tegnsætning, mens den stadig escaper XML-attributter, og lektien til enhver signerer er at teste mod en uafhængig native-verifikator, ikke kun din egen

  • Trust-backenden er kun Windows; andre mål returnerer xsotTrustUnavailable, efter integriteten er bestået
  • Verifikatoren accepterer packages op til 64 MiB, 16 MiB pr. del og højst 32 signaturdele under én origin
  • Udstedelse accepterer op til 256 References og 4096 kombinerede selectors pr. signatur
  • Den understøttede profil er RSA-SHA256, SHA-256-digests og Canonical XML uden kommentarer; andre algoritmer, ekstra application Objects og eksterne References returnerer xsoiUnsupportedProfile
  • xsrvRequireCached bruger cache-only tilbagekaldelsesdata; manglende status afviser, og xsrvNotChecked giver ingen tilbagekaldelsessikkerhed
  • At signere en projektmappe, som Excel også skal acceptere, støder stadig ind i package-reglerne i hvorfor Excel reparerer en gyldig XLSX; et duplikeret Override eller relationship Id knækker filen, før nogen signatur tjekkes

FAQ

Betyder et HotXLS integritetstjek, at signereren er troværdig?

Nej. XlsVerifySignatureOpcPackage beviser, at de valgte dele matcher deres digests under den indlejrede certifikatnøgle og rapporterer altid xsstUntrusted. Brug XlsVerifySignatureOpcPackageWithTrust med dine egne ankre og en UTC-verifikationstid til at evaluere certifikatkæden

Hvorfor viser Excel min projektmappesignatur som ugyldig efter en Delphi-redigering?

En gemt projektmappe omskriver signede XML-dele, så SHA-256-digesten af mindst én Reference ændres. Brug XlsEditVerifiedOpcPackage med xsoePreserve til at teste en redigering først, eller XlsEditAndReplaceSelectedSignatureOpcPackage til at redigere og re-signere i én verificeret transaktion

Kan HotXLS signere med en nøgle gemt på et smart card eller en HSM?

Ja, fordi signering er en kalder-callback. Biblioteket giver den kanoniske SignedInfo og dens 32-byte SHA-256-digest med, din kode returnerer RSASSA-PKCS1-v1_5-signaturen, og den private nøgle forlader aldrig den provider, du styrer

OPC-signaturverificering, trust-evaluering, kontra-signering og verificeret re-signering er en del af HotXLS, den native Excel-regnearkskomponent til Delphi og C++Builder, som læser og skriver XLS, XLSX, XLSB og ODS uden Excel eller COM-automatisering