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
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
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
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
xsotTrustUnavailableterug 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
xsoiUnsupportedProfileterug xsrvRequireCachedgebruikt cache-only intrekkingsdata; ontbrekende status wijst af, enxsrvNotCheckedgeeft 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