signature: add Signature class for more Pythonic verification.
[pgp-mime.git] / pgp_mime / pgp.py
index 86a73c9ba0152422210d4378817547090021bb13..fa45bda1370ecb722d8783dd3eee90996a96031a 100644 (file)
@@ -1,4 +1,18 @@
-# Copyright
+# Copyright (C) 2012 W. Trevor King <wking@tremily.us>
+#
+# This file is part of pgp-mime.
+#
+# pgp-mime is free software: you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# pgp-mime 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
+# pgp-mime.  If not, see <http://www.gnu.org/licenses/>.
 
 import copy as _copy
 from email import message_from_bytes as _message_from_bytes
@@ -419,7 +433,7 @@ def verify(message):
     >>> message['To'] = 'pgp-mime-test <pgp-mime@invalid.com>'
     >>> encrypted = sign_and_encrypt(message, signers=['pgp-mime@invalid.com'],
     ...     always_trust=True)
-    >>> decrypted,verified,result = verify(encrypted)
+    >>> decrypted,verified,signatures = verify(encrypted)
     >>> print(decrypted.as_string().replace('\r\n', '\n'))
     ... # doctest: +ELLIPSIS, +REPORT_UDIFF
     Content-Type: text/plain; charset="us-ascii"
@@ -432,37 +446,39 @@ def verify(message):
     Bye
     >>> verified
     False
-    >>> print(str(result, 'utf-8').replace('\x00', ''))
+    >>> for s in signatures:
+    ...     print(s.dumps())  # doctest: +REPORT_UDIFF
     ... # doctest: +REPORT_UDIFF, +ELLIPSIS
-    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-    <gpgme>
-      <verify-result>
-        <signatures>
-          <signature>
-            <summary value="0x0" />
-            <fpr>B2EDBE0E771A4B8708DD16A7511AEDA64332B6E3</fpr>
-            <status value="0x0">Success &lt;Unspecified source&gt;</status>
-            <timestamp unix="..." />
-            <exp-timestamp unix="0i" />
-            <wrong-key-usage value="0x0" />
-            <pka-trust value="0x0" />
-            <chain-model value="0x0" />
-            <validity value="0x0" />
-            <validity-reason value="0x0">Success &lt;Unspecified source&gt;</validity-reason>
-            <pubkey-algo value="0x1">RSA</pubkey-algo>
-            <hash-algo value="0x8">SHA256</hash-algo>
-          </signature>
-        </signatures>
-      </verify-result>
-    </gpgme>
-    <BLANKLINE>
+    B2EDBE0E771A4B8708DD16A7511AEDA64332B6E3 signature:
+      summary:
+        CRL missing: False
+        CRL too old: False
+        bad policy: False
+        green: False
+        key expired: False
+        key missing: False
+        key revoked: False
+        red: False
+        signature expired: False
+        system error: False
+        valid: False
+      status: success
+      timestamp: ...
+      expiration timestamp: None
+      wrong key usage: False
+      pka trust: not available
+      chain model: False
+      validity: unknown
+      validity reason: success
+      public key algorithm: RSA
+      hash algorithm: SHA256
 
     >>> from email.mime.multipart import MIMEMultipart
     >>> message = MIMEMultipart()
     >>> message.attach(encodedMIMEText('Part A'))
     >>> message.attach(encodedMIMEText('Part B'))
     >>> signed = sign(message, signers=['pgp-mime@invalid.com'])
-    >>> decrypted,verified,result = verify(signed)
+    >>> decrypted,verified,signatures = verify(signed)
     >>> decrypted.set_boundary('boundsep')
     >>> print(decrypted.as_string())  # doctest: +ELLIPSIS, +REPORT_UDIFF
     Content-Type: multipart/mixed; boundary="boundsep"
@@ -485,30 +501,113 @@ def verify(message):
     --boundsep--
     >>> verified
     False
-    >>> print(str(result, 'utf-8').replace('\x00', ''))
+    >>> for s in signatures:
+    ...     print(s.dumps())  # doctest: +REPORT_UDIFF
     ... # doctest: +REPORT_UDIFF, +ELLIPSIS
-    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-    <gpgme>
-      <verify-result>
-        <signatures>
-          <signature>
-            <summary value="0x0" />
-            <fpr>B2EDBE0E771A4B8708DD16A7511AEDA64332B6E3</fpr>
-            <status value="0x0">Success &lt;Unspecified source&gt;</status>
-            <timestamp unix="..." />
-            <exp-timestamp unix="0i" />
-            <wrong-key-usage value="0x0" />
-            <pka-trust value="0x0" />
-            <chain-model value="0x0" />
-            <validity value="0x0" />
-            <validity-reason value="0x0">Success &lt;Unspecified source&gt;</validity-reason>
-            <pubkey-algo value="0x1">RSA</pubkey-algo>
-            <hash-algo value="0x2">SHA1</hash-algo>
-          </signature>
-        </signatures>
-      </verify-result>
-    </gpgme>
+    B2EDBE0E771A4B8708DD16A7511AEDA64332B6E3 signature:
+      summary:
+        CRL missing: False
+        CRL too old: False
+        bad policy: False
+        green: False
+        key expired: False
+        key missing: False
+        key revoked: False
+        red: False
+        signature expired: False
+        system error: False
+        valid: False
+      status: success
+      timestamp: ...
+      expiration timestamp: None
+      wrong key usage: False
+      pka trust: not available
+      chain model: False
+      validity: unknown
+      validity reason: success
+      public key algorithm: RSA
+      hash algorithm: SHA1
+
+    Test a message generated by Mutt (for sanity):
+
+    >>> from email import message_from_bytes
+    >>> message_bytes = b'\n'.join([
+    ...   b'Return-Path: <pgp-mime@invalid.com>',
+    ...   b'Received: by invalid; Tue, 24 Apr 2012 19:46:59 -0400',
+    ...   b'Date: Tue, 24 Apr 2012 19:46:59 -0400',
+    ...   b'From: pgp-mime-test <pgp-mime@invalid.com',
+    ...   b'To: pgp-mime@invalid.com',
+    ...   b'Subject: test',
+    ...   b'Message-ID: <20120424233415.GA27788@invalid>',
+    ...   b'MIME-Version: 1.0',
+    ...   b'Content-Type: multipart/signed; micalg=pgp-sha1;',
+    ...   b'  protocol="application/pgp-signature";',
+    ...   b'  boundary="kORqDWCi7qDJ0mEj"',
+    ...   b'Content-Disposition: inline',
+    ...   b'User-Agent: Mutt/1.5.21 (2010-09-15)',
+    ...   b'Content-Length: 740',
+    ...   b'',
+    ...   b'',
+    ...   b'--kORqDWCi7qDJ0mEj',
+    ...   b'Content-Type: text/plain; charset=us-ascii',
+    ...   b'Content-Disposition: inline',
+    ...   b'',
+    ...   b'ping!',
+    ...   b'',
+    ...   b'--kORqDWCi7qDJ0mEj',
+    ...   b'Content-Type: application/pgp-signature; name="signature.asc"',
+    ...   b'Content-Description: OpenPGP digital signature',
+    ...   b'',
+    ...   b'-----BEGIN PGP SIGNATURE-----',
+    ...   b'Version: GnuPG v2.0.17 (GNU/Linux)',
+    ...   b'',
+    ...   b'iQEcBAEBAgAGBQJPlztxAAoJEFEa7aZDMrbjwT0H/i9eN6CJ2FIinK7Ps04XYEbL',
+    ...   b'PSQV1xCxb+2bk7yA4zQnjAKOPSuMDXfVG669Pbj8yo4DOgUqIgh+lK+voec9uwsJ',
+    ...   b'ZgUJcMozSmEFSTPO+Fiyx0S+NjnaLsas6IQrQTVDc6lWiIZttgxuN0crH5DcLomB',
+    ...   b'Ip90+ELbzVN3yBAjMJ1Y6xnKd7C0IOKm7VunYu9eCzJ/Rik5qZ0+IacQQnnrFJEN',
+    ...   b'04nDvDUzfaKy80Ke7VAQBIRi85XCsM2h0KDXOGUZ0xPQ8L/4eUK9tL6DJaqKqFPl',
+    ...   b'zNiwfpue01o6l6kngrQdXZ3tuv0HbLGc4ACzfz5XuGvE5PYTNEsylKLUMiSCIFc=',
+    ...   b'=xP0S',
+    ...   b'-----END PGP SIGNATURE-----',
+    ...   b'',
+    ...   b'--kORqDWCi7qDJ0mEj--',
+    ...   b''])
+    >>> message = message_from_bytes(message_bytes)
+    >>> decrypted,verified,signatures = verify(message)
+    >>> print(decrypted.as_string())  # doctest: +ELLIPSIS, +REPORT_UDIFF
+    Content-Type: text/plain; charset=us-ascii
+    Content-Disposition: inline
     <BLANKLINE>
+    ping!
+    <BLANKLINE>
+    >>> verified
+    False
+    >>> for s in signatures:
+    ...     print(s.dumps())  # doctest: +REPORT_UDIFF
+    ... # doctest: +REPORT_UDIFF, +ELLIPSIS
+    B2EDBE0E771A4B8708DD16A7511AEDA64332B6E3 signature:
+      summary:
+        CRL missing: False
+        CRL too old: False
+        bad policy: False
+        green: False
+        key expired: False
+        key missing: False
+        key revoked: False
+        red: False
+        signature expired: False
+        system error: False
+        valid: False
+      status: success
+      timestamp: Tue Apr 24 23:46:57 2012
+      expiration timestamp: None
+      wrong key usage: False
+      pka trust: not available
+      chain model: False
+      validity: unknown
+      validity reason: success
+      public key algorithm: RSA
+      hash algorithm: SHA1
     """
     ct = message.get_content_type()
     if ct == 'multipart/encrypted':