Skip to content

Commit

Permalink
First work on signatures.
Browse files Browse the repository at this point in the history
opendocument.save() will now remove signatures that was loaded
  • Loading branch information
sorenroug committed Mar 16, 2009
1 parent 3bee71c commit 6359ddf
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 0 deletions.
14 changes: 14 additions & 0 deletions contrib/odfsign/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
all: odf odfsign.1

txt: odfsign.txt

%.1: %.xml
xmlto man $<

%.txt: %.xml
xmlto txt $<

clean:
rm -f *.txt odf
odf:
ln -s ../../odf
92 changes: 92 additions & 0 deletions contrib/odfsign/odfsign
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2006 Søren Roug, European Environment Agency
#
# This is free software. You may redistribute it under the terms
# of the Apache license and the GNU General Public License Version
# 2 or at your option any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
# Requires PyXML
import sha,base64,zipfile,sys
from xml.dom.ext.reader import PyExpat
from xml.dom.ext.c14n import Canonicalize
import StringIO


def data_digest(data):
return base64.b64encode(sha.new(data).digest())

def xml_digest(dom):
s = StringIO.StringIO()
Canonicalize(dom,s)
canon = s.getvalue().encode('utf-8')
return data_digest(canon)

def get_signatureproperty(dom, ref):
property_list = dom.getElementsByTagNameNS('http://www.w3.org/2000/09/xmldsig#','SignatureProperty')
for p in property_list:
id = p.getAttributeNS(None,'Id')
if ref == id: return p
return None

def exitwithusage(exitcode=2):
sys.stderr.write("Usage: %s inputfile\n" % sys.argv[0])
sys.exit(exitcode)

if __name__ == '__main__':
if len(sys.argv) != 2:
exitwithusage()
z = zipfile.ZipFile(sys.argv[1])
namelist = z.namelist()
if "META-INF/documentsignatures.xml" not in namelist:
print "This document is not signed"
sys.exit()
documentsignatures_xml = z.read('META-INF/documentsignatures.xml')
reader = PyExpat.Reader()
doc = reader.fromString(documentsignatures_xml)

signature_list = doc.getElementsByTagNameNS('http://www.w3.org/2000/09/xmldsig#','Signature')
print "Document has %s signature(s)" % len(signature_list)
signnum = 1
for signature in signature_list:
date = signature.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/","date")[0].firstChild.nodeValue
# print date.__dict__

print "Checking signature #%d - signed on %s" % (signnum, date),
refs = signature.getElementsByTagNameNS('http://www.w3.org/2000/09/xmldsig#','Reference')
for ref in refs:
status = "OK"
uri = ref.getAttributeNS(None,'URI')
digest_value = ref.getElementsByTagNameNS('http://www.w3.org/2000/09/xmldsig#','DigestValue')[0].firstChild.nodeValue
if uri[0] != '#':
document = z.read(uri)
if uri[-4:] == ".xml" and len(document) != 0:
dom = PyExpat.Reader().fromString(document)
digest_actual = xml_digest(dom)
else:
digest_actual = data_digest(document)
else:
# FIXME
# fragments aren't canonicalized. See http://www.w3.org/TR/xmldsig-core/#sec-URI
xmlfragment = get_signatureproperty(doc, uri[1:])
if xmlfragment is None:
continue
digest_actual = digest_value
#digest_actual = xml_digest(xmlfragment)
if digest_value != digest_actual:
status = "failed"

print status
signnum += 1

217 changes: 217 additions & 0 deletions contrib/odfsign/odfsign.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
.\" Title: odfsign
.\" Author: S\(/oren Roug
.\" Generator: DocBook XSL Stylesheets v1.74.0 <http://docbook.sf.net/>
.\" Date: 03/16/2009
.\" Manual: User commands
.\" Source: odfpy
.\" Language: English
.\"
.TH "ODFSIGN" "1" "03/16/2009" "odfpy" "User commands"
.\" -----------------------------------------------------------------
.\" * (re)Define some macros
.\" -----------------------------------------------------------------
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" toupper - uppercase a string (locale-aware)
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.de toupper
.tr aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ
\\$*
.tr aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz
..
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" SH-xref - format a cross-reference to an SH section
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.de SH-xref
.ie n \{\
.\}
.toupper \\$*
.el \{\
\\$*
.\}
..
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" SH - level-one heading that works better for non-TTY output
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.de1 SH
.\" put an extra blank line of space above the head in non-TTY output
.if t \{\
.sp 1
.\}
.sp \\n[PD]u
.nr an-level 1
.set-an-margin
.nr an-prevailing-indent \\n[IN]
.fi
.in \\n[an-margin]u
.ti 0
.HTML-TAG ".NH \\n[an-level]"
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
\." make the size of the head bigger
.ps +3
.ft B
.ne (2v + 1u)
.ie n \{\
.\" if n (TTY output), use uppercase
.toupper \\$*
.\}
.el \{\
.nr an-break-flag 0
.\" if not n (not TTY), use normal case (not uppercase)
\\$1
.in \\n[an-margin]u
.ti 0
.\" if not n (not TTY), put a border/line under subheading
.sp -.6
\l'\n(.lu'
.\}
..
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" SS - level-two heading that works better for non-TTY output
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.de1 SS
.sp \\n[PD]u
.nr an-level 1
.set-an-margin
.nr an-prevailing-indent \\n[IN]
.fi
.in \\n[IN]u
.ti \\n[SN]u
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.ps \\n[PS-SS]u
\." make the size of the head bigger
.ps +2
.ft B
.ne (2v + 1u)
.if \\n[.$] \&\\$*
..
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" BB/BE - put background/screen (filled box) around block of text
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.de BB
.if t \{\
.sp -.5
.br
.in +2n
.ll -2n
.gcolor red
.di BX
.\}
..
.de EB
.if t \{\
.if "\\$2"adjust-for-leading-newline" \{\
.sp -1
.\}
.br
.di
.in
.ll
.gcolor
.nr BW \\n(.lu-\\n(.i
.nr BH \\n(dn+.5v
.ne \\n(BHu+.5v
.ie "\\$2"adjust-for-leading-newline" \{\
\M[\\$1]\h'1n'\v'+.5v'\D'P \\n(BWu 0 0 \\n(BHu -\\n(BWu 0 0 -\\n(BHu'\M[]
.\}
.el \{\
\M[\\$1]\h'1n'\v'-.5v'\D'P \\n(BWu 0 0 \\n(BHu -\\n(BWu 0 0 -\\n(BHu'\M[]
.\}
.in 0
.sp -.5v
.nf
.BX
.in
.sp .5v
.fi
.\}
..
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" BM/EM - put colored marker in margin next to block of text
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.de BM
.if t \{\
.br
.ll -2n
.gcolor red
.di BX
.\}
..
.de EM
.if t \{\
.br
.di
.ll
.gcolor
.nr BH \\n(dn
.ne \\n(BHu
\M[\\$1]\D'P -.75n 0 0 \\n(BHu -(\\n[.i]u - \\n(INu - .75n) 0 0 -\\n(BHu'\M[]
.in 0
.nf
.BX
.in
.fi
.\}
..
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
.\" disable hyphenation
.nh
.\" disable justification (adjust text to left margin only)
.ad l
.\" -----------------------------------------------------------------
.\" * MAIN CONTENT STARTS HERE *
.\" -----------------------------------------------------------------
.SH "Name"
odfsign \- Verify a signed ODF document
.SH "Synopsis"
.fam C
.HP \w'\fBodfsign\fR\ 'u
\fBodfsign\fR \fIpath\fR
.fam
.SH "Description"
.PP
\fBodfsign\fR
checks that the checksums used in the signature are correct\&.
\fIIt does not check that the x509 digest is correct!\fR
Therefore it is currently of little use\&.
.PP
"Path" is assumed to be an OpenDocument file of text, spreadsheet or presentation type\&.
.SH "Example"
.sp
.if n \{\
.RS 4
.\}
.fam C
.ps -1
.nf
.if t \{\
.sp -1
.\}
.BB lightgray adjust-for-leading-newline
.sp -1

odfsign odf\-file
.EB lightgray adjust-for-leading-newline
.if t \{\
.sp 1
.\}
.fi
.fam
.ps +1
.if n \{\
.RE
.\}
.SH "See Also"
.PP
http://www\&.w3\&.org/TR/xmldsig\-core
.SH "Author"
.PP
\fBS\(/oren Roug\fR
.RS 4
Original author
.RE
50 changes: 50 additions & 0 deletions contrib/odfsign/odfsign.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" ?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<refentry id="odfsign">
<refentryinfo>
<productname>odfpy</productname>
<author><firstname>Søren</firstname><surname>Roug</surname>
<contrib>Original author</contrib>
</author>
</refentryinfo>
<refmeta>
<refentrytitle>odfsign</refentrytitle>
<manvolnum>1</manvolnum>
<refmiscinfo class="manual">User commands</refmiscinfo>
</refmeta>
<refnamediv>
<refname>odfsign</refname>
<refpurpose>Verify a signed ODF document</refpurpose>
</refnamediv>

<refsynopsisdiv>
<cmdsynopsis>
<command>odfsign</command>
<arg choice="plain"><replaceable>path</replaceable></arg>
</cmdsynopsis>
</refsynopsisdiv>

<refsect1><title>Description</title>
<para><command>odfsign</command> checks that the checksums
used in the signature are correct.
<emphasis>It does not check that the x509 digest is correct!</emphasis>
Therefore it is currently of little use.
</para>
<para>
&quot;Path&quot; is assumed to be an
OpenDocument file of text, spreadsheet or presentation type.
</para>
</refsect1>
<refsect1><title>Example</title>
<screen>
odfsign odf-file
</screen>
</refsect1>
<refsect1><title>See Also</title>
<para>
http://www.w3.org/TR/xmldsig-core
</para>
</refsect1>
</refentry>

Binary file added contrib/odfsign/testdocs/ClimateChange2.odt
Binary file not shown.
Binary file added contrib/odfsign/testdocs/ClimateChange3.odt
Binary file not shown.
1 change: 1 addition & 0 deletions odf/opendocument.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ def _zipwrite(self, outputfp):

# Write any extra files
for op in self._extra:
if op.filename == "META-INF/documentsignatures.xml": continue # Don't save signatures
self.manifest.addElement(manifest.FileEntry(fullpath=op.filename, mediatype=op.mediatype))
zi = zipfile.ZipInfo(op.filename.encode('utf-8'), self._now)
zi.compress_type = zipfile.ZIP_DEFLATED
Expand Down

0 comments on commit 6359ddf

Please sign in to comment.