After adding support for reading OOXML signatures in LibreOffice, I continued with implementing OOXML signature export (as in: not only verification, but signing).

By verification, I mean that I count the signature of the input document, then compare it with an existing signature, and if they match, it is verified. This can be also called "import", as I only read an existing signature, I don’t create one. By signing, I mean the creation of a new signature, which is always good — if it isn’t, that’s a programming error. This can be also called "export", as I write the new signature into the document.

First, thanks to the Dutch Ministry of Defense who made this work possible (as part of a project implementing trusted signing and communication in LibreOffice), this included:

signing a previously unsigned document

appending a signature to an already signed document

removing a signature from a document with multiple signatures

removing the last signature of a signed document, turning it into an unsigned one

Obviously the hardest part was the initial success: signing a previously unsigned document, in a way that is accepted by both LibreOffice and MSO. One trick here is that while in ODF the signature stream is simply added to an existing document storage, in OOXML the storage has to refer to the signature sub-storage (it’s not a stream, as it has a stream for each individual signature), then it has to be signed, and finally the signature can be added to the document storage. So instead of reading the document, then appending the signature, here we need to modify the document, and then we can append the signature. By referring the signature sub-storage, I mean it is necessary to modify [Content_Types].xml (so it contains a mime type for both the .sigs extension, and also for the individual /_xmlsignatures/sigN.xml streams) and also the _rels/.rels stream has to refer _xmlsignatures/origin.sigs , which will contain the list of actual signatures. A surprising detail is that the signature is required to contain quite some software and hardware details about your environment, like monitor resolution, Windows version and so on. For a cross-platform project like LibreOffice this isn’t meaningful, not to mention we have no interest in leaking such information. So what I did instead is writing hardcoded values based on what my test environment would produce, just to please MSO. ;-)

After the initial OOXML signature exporter was ready, the next challenge was adding multiple signatures. The problem here is that you have to roundtrip the existing signatures perfectly. And when I write perfectly, I really mean it: if a single character is written differently, then the hash of the signature will be different, so the roundtrip (when we write back an existing and a new signature to the document) will invalidate the signature. And there is no way around that: the very point of the signature is that only the original signer can re-calculate the signature hash. :-) So what we do is simply threating the existing signatures as a byte array, and when writing back, then we don’t try to re-construct the signature stream based on the xmlsecurity data model, but simply write back the byte array. This way it’s enough to extract parts of the signature which are presented to the user (date, certificate, comment), and we don’t need to parse the rest.

Removing one of multiple existing signatures isn’t particularly hard, you just need to update _xmlsignatures/_rels/origin.sigs.rels and [Content_Types].xml which refer each and every signature stream. It’s a good idea to truncate them before writing, otherwise you may get a not even well-formed XML as a result.

Finally removing the last signature is a matter of undoing all changes we did while adding the first signature (the content type list and the toplevel relation list), finally removing the signature sub-storage all-together. I also factored out all this signature management code from DigitalSignaturesDialog (which is a graphical dialog) to DocumentSignatureManager , so that all the above mentioned features can be unit-tested.