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.
Putting all of these together, LO can now do all signature add, append, remove
and clean operations a user would expect from what is referred as simply
OOXML signature support. As usual, you can try this right now with a 5.2
daily build. :-)