HotXLS verifica e rifirma le firme digitali Excel in Delphi senza Excel installato. L'unità lxSignatureOpcIntegrity controlla le firme del pacchetto ECMA-376 Part 2 direttamente dai byte della cartella di lavoro: RSA-SHA256 sul SignedInfo canonico, ogni digest SHA-256 dei Reference, e le parti esatte che ogni firma seleziona. Il trust sulla catena di certificati è un passo separato ed esplicito, e la modifica o la rifirma pubblica solo output che verifica di nuovo
Il grilletto usuale è un flusso documentale cresciuto oltre il desktop. Un controller firma una cartella di budget in Excel, un servizio Delphi in seguito deve confermare la firma prima di importare i numeri, e un terzo passo deve correggere un foglio e rifirmare sotto un certificato di servizio. Automatizzare Excel su un server per questo è fragile e non supportato, e trattare la firma come un flag sì/no nasconde le due domande che contano davvero: a quali byte si è impegnato il firmatario, e se vi fidate della persona che detiene quella chiave
Cosa copre davvero una firma del pacchetto OPC?
Una firma del pacchetto OPC copre solo le parti che il suo Manifest elenca, non l'intero file XLSX. ECMA-376 Part 2, §13 (Digital Signatures) colloca una parte di origine firma nel pacchetto, tipicamente /_xmlsignatures/origin.sigs, con relationship verso una o più parti firma come /_xmlsignatures/sig1.xml. Ogni parte firma è una Signature W3C XML-DSig il cui SignedInfo referenzia un idPackageObject interno, e quell'Object porta un Manifest di Reference verso singole parti del pacchetto, ciascuna con il proprio digest
HotXLS supporta tre modi in cui un Reference può legare una parte, e non sono intercambiabili. Un Reference raw fa l'hash dei byte memorizzati della parte. Un Reference XML canonico fa l'hash della forma Canonical XML (senza commenti), così l'ordine degli attributi e l'ortografia delle dichiarazioni di namespace non contano più. Un Reference relationship applica prima la OPC RelationshipTransform e poi canonizza, e quella transform conserva solo le relationship selezionate dai suoi selettori SourceId o SourceType. Un selettore autentica esattamente le relationship che seleziona: il resto di quella parte .rels può cambiare liberamente mentre la firma resta valida
Quell'ultimo punto è perché HotXLS riporta la copertura invece di un singolo segno di spunta verde. TXLSSignatureOpcIntegrityResult.Coverage è un array di record TXLSSignatureOpcCoverage, uno per Reference autenticato del Manifest, ciascuno con ReferenceUri, PartName, ContentType, Transforms, RelationshipSourceIds e RelationshipSourceTypes. Se il vostro import legge sheet3.xml e nessun descrittore la nomina, la firma non dice nulla sui numeri che state per caricare
Verificare le firme da uno snapshot ZIP immutabile
HotXLS verifica un pacchetto da un solo snapshot di byte immutabile, mai dal file su disco o da un TWorkbook in vita. XlsVerifySignatureOpcPackage e i suoi fratelli multi-firma prendono un array TBytes, leggono solo quell'array, e non recuperano mai target esterni, certificati, file locali o risorse di rete. Il punto è che i byte che avete verificato sono i byte che poi analizzate: se il file viene riletto dopo, un altro processo può averlo sostituito nel frattempo
La canonicalizzazione è dove i verifier fatti in casa di solito sbagliano. SignedInfo e l'Object del pacchetto vanno canonizzati nel contesto di namespace della busta Signature completa. Un frammento isolato ritagliato dal documento eredita dichiarazioni di namespace diverse da quelle che il verifier vede sul posto, così i suoi byte C14N differiscono, il digest differisce, e una firma perfettamente valida fallisce. HotXLS usa il contesto completo della busta sia sul lato verifica sia su quello di emissione
Per i pacchetti che possono portare più di una firma, XlsVerifyAllSignaturesOpcPackage verifica ogni firma allegata e conserva gli esiti individuali. Lo stato aggregato è xsoiIntegrityVerified solo quando ogni firma ammessa verifica; altrimenti è il primo fallimento in ordine di inventario, e ogni report vi dice comunque quale parte firma è fallita
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); // uno snapshot, verificato e analizzato
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 non è un superamento
end;
I valori di stato sono abbastanza specifici da agire. xsoiDigestMismatch significa che una parte selezionata è cambiata dopo la firma, xsoiInvalidSignature significa che il valore RSA non verifica contro SignedInfo con la chiave del certificato incorporato, xsoiPartUnavailable significa che una parte selezionata manca, e xsoiUnsupportedProfile o xsoiResourceLimit significano che il verifier ha rifiutato invece di tirare a indovinare. Il layer ZIP sottostante applica la stessa lettura fail-closed descritta nell'articolo sulla validazione ZIP EOCD per file XLSX non fidati, e la ricerca delle parti segue le regole della risoluzione delle relationship OPC di XLSX
Perché modificare una cartella firmata invalida la firma?
Modificare una cartella firmata invalida la firma non appena un byte cambiato cade dentro una selezione firmata, perché il digest SHA-256 memorizzato per quel Reference non combacia più. Un writer spreadsheet che carica la cartella e la salva di nuovo tocca quasi sempre parti firmate: riserializza l'XML, rinumera le shared strings, riscrive workbook.xml e il manifest dei content type. Anche quando i valori delle celle sono identici, i byte non lo sono, e la verifica successiva restituisce xsoiDigestMismatch
HotXLS vi lascia porre la domanda prima di impegnarvi in una modifica. XlsEditVerifiedOpcPackage sostituisce parti esistenti in un pacchetto verificato e poi verifica il candidato. Con xsoePreserve restituisce xsoePreserved solo quando la firma verifica ancora, il che accade quando le parti modificate stanno fuori da ogni selezione firmata, e xsoeWouldInvalidate altrimenti. Con xsoeInvalidateChanged rimuove il legame di firma dalle relationship della radice e da [Content_Types].xml e restituisce un pacchetto esplicitamente non firmato come xsoeInvalidated, invece di consegnarvi un file che sembra ancora firmato
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 stava fuori dalla selezione firmata
xsoeWouldInvalidate: Result := False; // un digest firmato copre sheet2
else
raise Exception.CreateFmt('Edit refused, original integrity %d',
[Ord(Details.OriginalIntegrity)]);
end;
end;
Integrità contro trust: cosa prova una firma verificata?
Una firma verificata prova che le parti selezionate sono invariate da quando qualcuno in possesso della chiave privata le ha firmate, e nulla su chi sia quel qualcuno. HotXLS tiene i due livelli separati di proposito: ogni TXLSSignatureOpcIntegrityResult porta Trust = xsstUntrusted, controlli riusciti compresi, e ClaimedSignatureTime sono dati forniti dal firmatario, non un timestamp fidato. Chiunque può generare un certificato autofirmato, firmare una cartella di lavoro, e passare il controllo di integrità
Il trust è una chiamata separata con una policy del chiamante. XlsDefaultSignatureOpcTrustPolicy restituisce una policy fail-closed senza anchor, senza tempo di verifica, con l'EKU di firma documento 1.3.6.1.4.1.311.10.3.12 e revoca xsrvRequireCached. XlsVerifySignatureOpcPackageWithTrust esegue prima il verifier di integrità completo e poi valuta il certificato che effettivamente l'ha superato; un chiamante non può sostituire un certificato foglia diverso
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;
Il backend Windows ha due trappole facili da perdere quando chiamate WinCrypt da soli. CertVerifyCertificateChainPolicy che restituisce True significa solo che la chiamata è stata eseguita; l'accettazione richiede comunque uno dwError a zero nello stato della policy, zero errori aggregati di catena, un key usage digitalSignature esplicito sul foglia e l'EKU richiesto. HotXLS ha scoperto che un certificato sprovvisto di key usage può passare la policy base ordinaria, quindi controlla DigitalSignatureUsage e RequiredEkuPresent da sé. La seconda trappola: combinare un root store esclusivo con hRestrictedOther nella configurazione del chain engine restituisce E_INVALIDARG sul backend attuale, così HotXLS usa lo store radice esclusivo del chiamante e poi autorizza ogni elemento della catena primaria restituita contro i byte DER che avete fornito. Un emittente che capita semplicemente di stare in uno store di sistema locale non diventa mai fidato
xsotTrustedSelection descrive comunque solo quella firma e la copertura che riporta. Celle non firmate e relationship non selezionate possono cambiare mentre la firma selezionata resta fidata, ed è per questo che il risultato di trust restituisce anche PackageSha256 e CertificateSha256 per il vostro log di audit
Come si rifirma o controfirma una cartella di lavoro senza Excel?
Al livello di array di byte HotXLS rifirma attraverso transazioni esplicite e autorizzate, e i percorsi di salvataggio firmato del Workbook riusano le stesse pipeline verificate. XlsReplaceVerifiedSignatureOpcPackage scambia una firma verificata con una nuova, XlsEditAndReplaceSelectedSignatureOpcPackage applica modifiche a parti e sostituisce una firma selezionata nella stessa transazione, e XlsAppendVerifiedSignatureOpcPackage aggiunge una firma conservando ogni originale verificato byte per byte. Le chiamate di replace prendono un TXLSSignatureOpcReplaceAuthorization, la chiamata di append verifica ogni originale prima, e tutte prendono una selezione TXLSSignatureOpcIssueOptions e una callback firmataria; la libreria non tocca mai la vostra chiave privata
Il record di autorizzazione lega l'operazione all'esatto originale che avete approvato: ExpectedPackageSha256, ExpectedSignaturePart e ExpectedCertificateSha256, tutti confrontati solo dopo che la vecchia firma ha verificato completamente. Ogni parte modificata deve essere coperta dalla nuova selezione, ogni firma non selezionata deve ancora verificare contro il pacchetto in scena prima che il firmatario venga chiamato, e la chiamata restituisce xspsWouldInvalidate altrimenti
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 è lo SHA-256 di 32 byte calcolato dalla libreria;
// RsaSignSha256Pkcs1 sta per il vostro wrapper CNG, HSM o smart card e
// deve restituire la firma RSASSA-PKCS1-v1_5 completa big-endian
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); // o l'hash memorizzato all'approvazione
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;
Due dettagli in quel codice sono deliberati. Output e Details devono essere variabili separate; passare un campo di Details come Output è esplicitamente non supportato, perché la libreria non può pubblicare un pacchetto e conservare un report di certificato nello stesso storage. E la vecchia copertura non si eredita: la nuova firma autentica esattamente i Reference che elencate, quindi decidete di nuovo su quali parti farà affidamento un consumatore a valle
La controfirma usa la stessa chiamata di append. Da HotXLS 2.384.296, TXLSSignatureOpcIssueOptions.References passato a XlsAppendVerifiedSignatureOpcPackage può nominare parti dall'inventario verificato delle firme originali, così un revisore può firmare sopra una parte firma esistente, incatenare parecchie controfirme, o mischiare parti firma e parti contenuto in una sola selezione. Ogni firma aggiunta registra solo le proprie selezioni dirette. La qualifica per quella release ha eseguito 222 controlli al bordo e 122 controlli indipendenti con il verifier di packaging .NET WindowsBase su 7 pacchetti di output, confermando esattamente una nuova firma e firme e certificati originali byte-identici
Insidie e limiti da conoscere
Il bug più istruttivo è venuto da un carattere perfettamente legale. Gli URI dei Reference OPC portano il content type come query, per esempio /xl/worksheets/sheet1.xml?ContentType=application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml. Un emittente HotXLS precoce codificava in percentuale la + letterale come %2B. I controlli RSA, C14N e Reference dello stesso HotXLS accettavano il risultato, ma WindowsBase riportava InvalidSignature; un campione controllato che cambiava solo quell'ortografia passava da InvalidSignature a Success. L'emittente ora conserva la punteggiatura di query ASCII letterale supportata continuando comunque a fare l'escape degli attributi XML, e la lezione per qualunque firmatario è di testare contro un verifier nativo indipendente, non solo il proprio
- Il backend di trust è solo Windows; gli altri target restituiscono
xsotTrustUnavailabledopo che l'integrità è riuscita - Il verifier accetta pacchetti fino a 64 MiB, 16 MiB per parte, e al massimo 32 parti firma sotto un'origine
- L'emissione accetta fino a 256 Reference e 4096 selettori combinati per firma
- Il profilo supportato è RSA-SHA256, digest SHA-256 e Canonical XML senza commenti; altri algoritmi, Object applicativi extra e Reference esterni restituiscono
xsoiUnsupportedProfile xsrvRequireCachedusa dati di revoca solo da cache; uno stato mancante rifiuta, exsrvNotCheckednon dà alcuna garanzia di revoca- Firmare una cartella che Excel deve pure accettare incappa ancora nelle regole del pacchetto trattate in perché Excel ripara un XLSX valido; un Override duplicato o un id di relationship duplicato rompe il file prima che una firma venga controllata
FAQ
Un controllo di integrità HotXLS significa che il firmatario è fidato?
No. XlsVerifySignatureOpcPackage prova che le parti selezionate combaciano con i loro digest sotto la chiave del certificato incorporato e riporta sempre xsstUntrusted. Usate XlsVerifySignatureOpcPackageWithTrust con i vostri anchor e un tempo di verifica UTC per valutare la catena di certificati
Perché Excel mostra la firma della mia cartella come invalida dopo una modifica Delphi?
Una cartella salvata riscrive le parti XML firmate, quindi il digest SHA-256 di almeno un Reference cambia. Usate XlsEditVerifiedOpcPackage con xsoePreserve per provare prima una modifica, o XlsEditAndReplaceSelectedSignatureOpcPackage per modificare e rifirmare in una sola transazione verificata
HotXLS può firmare con una chiave su smart card o HSM?
Sì, perché la firma è una callback del chiamante. La libreria passa il SignedInfo canonico e il suo digest SHA-256 di 32 byte, il vostro codice restituisce la firma RSASSA-PKCS1-v1_5, e la chiave privata non lascia mai il provider che controllate
Verifica delle firme OPC, valutazione del trust, controfirma e rifirma verificata fanno parte di HotXLS, il componente foglio di calcolo Excel nativo per Delphi e C++Builder, che legge e scrive XLS, XLSX, XLSB e ODS senza Excel né automazione COM