Merge branch 'release/0.4.1'

This commit is contained in:
Michael Greene
2017-04-13 14:53:49 -07:00
67 changed files with 2248 additions and 1849 deletions

View File

@@ -1,5 +1,9 @@
os:
- linux
- osx
language: python
python:
- "3.6"
- "3.5"
- "3.4"
- "3.3"
@@ -9,38 +13,50 @@ python:
matrix:
include:
# add a pep8 test
- python: 3.5
- python: 3.6
env: TOXENV=pep8
# test setup.py using each tested version
- python: 3.6
env: TOXENV=setup36
- python: 3.5
env: TOXENV=setup35
- python: 3.4
env: TOXENV=setup34
- python: 3.3
env: TOXENV=setup33
- python: 2.7
env: TOXENV=setup27
allow_failures:
# pep8 failures shouldn't be considered fatal
- env: TOXENV=pep8
# pypy and pypy3 tests are just for fun
- python: "pypy"
- python: "pypy3"
# osx, until it's working
- os: osx
# install requirements
install:
# make sure libffi-dev, gnupg2, and pgpdump are installed
- sudo apt-get update
- sudo apt-get install -y libffi-dev gnupg2 pgpdump
- ./install_dependencies.${TRAVIS_OS_NAME}.sh
# ensure tox and coveralls are installed
- pip install tox python-coveralls
# set TOXENV if it isn't yet
before_script:
- if [[ -z "$TOXENV" ]]; then export TOXENV=py${TRAVIS_PYTHON_VERSION//.}; fi
- if [[ "$TRAVIS_PYTHON_VERSION" == 'pypy' ]]; then export TOXENV=pypy; fi
- if [[ "$TRAVIS_PYTHON_VERSION" == 'pypy3' ]]; then export TOXENV=pypy3; fi
- if [[ "${TRAVIS_PYTHON_VERSION}" == 'pypy' ]]; then export TOXENV=pypy; fi
- if [[ "${TRAVIS_PYTHON_VERSION}" == 'pypy3' ]]; then export TOXENV=pypy3; fi
# run tox
script:
- ./tox.sh
# and report coverage to coveralls, but only if this was a pytest run
after_success:
- if [[ "${TOXENV}" == "py"* ]]; then coveralls; fi

View File

@@ -38,12 +38,19 @@ Documentation
`PGPy Documentation <https://pythonhosted.org/PGPy/>`_
Discussion
----------
Please report any bugs found on the `issue tracker <https://github.com/SecurityInnovation/PGPy/issues>`_
You can also join ``#pgpy`` on Freenode to ask questions or get involved
Requirements
------------
- Python 3 >= 3.3; Python 2 >= 2.7
Tested with: 3.5, 3.4, 3.3, 2.7
Tested with: 3.6, 3.5, 3.4, 3.3, 2.7
- `Cryptography <https://pypi.python.org/pypi/cryptography>`_
@@ -59,3 +66,4 @@ License
-------
BSD 3-Clause licensed. See the bundled `LICENSE <https://github.com/SecurityInnovation/PGPy/blob/master/LICENSE>`_ file for more details.

View File

@@ -1,48 +0,0 @@
# $Id$
# Maintainer: Michael Greene <mgreene@securityinnovation.com>
# Contributor: Michael Greene <mgreene@securityinnovation.com>
pkgbase=python-pgpy
pkgname=('python-pgpy' 'python2-pgpy')
pkgver=0.4.0
pkgrel=1
pkgdesc="Pretty Good Privacy for Python - a pure Python OpenPGP implementation."
arch=('any')
license=('BSD')
url="https://github.com/SecurityInnovation/PGPy"
makedepends=('python-setuptools' 'python-cryptography' 'python-six' 'python-pyasn1'
'python2-setuptools' 'python2-cryptography' 'python2-enum34' 'python2-singledispatch' 'python2-six' 'python2-pyasn1')
source=("https://pypi.python.org/packages/0a/2c/bfe57ac97d31fcd7172df43770d68bab1fbd38d629448ec8013f4714e779/PGPy-0.4.0a.tar.gz")
sha256sums=('0025d65f2db2886868ac5af68a85322d255ed52211756c6141c9d46264091da2')
sha384sums=('4ea26e952e6004778661072dbe4644351716b53122f4e96b8d2c44e57c31e23dc5e62e74f5700f628db2c163e01317c8')
sha512sums=('527625a143e6fc221f7f5d84455546b9687287059c1b28f4e16db46f80a2227e69f0781b95752797c212d3ff788979622331285a55f32bd3bbe4f108328ae2ed')
prepare() {
cp -a PGPy-${pkgver}{,-python2}
}
build() {
# Build Python 3 module
cd ${srcdir}/PGPy-${pkgver}
python3 setup.py build
# Build python2 module
cd ${srcdir}/PGPy-${pkgver}-python2
python2 setup.py build
}
package_python-pgpy() {
depends=('python-cryptography>=1.1.0' 'python-six>=1.9.0' 'python-pyasn1')
cd PGPy-${pkgver}
python3 setup.py install --root="${pkgdir}" --optimize=1 --skip-build
install -D -m 644 LICENSE ${pkgdir}/usr/share/licenses/python-pgpy/LICENSE
}
package_python2-pgpy() {
depends=('python2-cryptography>=1.1.0' 'python2-six>=1.9.0' 'python2-enum34' 'python2-singledispatch' 'python2-pyasn1')
cd PGPy-${pkgver}-python2
python2 setup.py install --root="${pkgdir}" --optimize=1 --skip-build
install -D -m 644 LICENSE ${pkgdir}/usr/share/licenses/python2-pgpy/LICENSE
}

11
debian/changelog vendored
View File

@@ -1,11 +0,0 @@
pgpy (0.4.0a-1) unstable; urgency=low
* Updated to latest release
-- Michael <mgreene@securityinnovation.com> Thu, 21 Apr 2016 21:15:38 -0700
pgpy (0.3.0-1) unstable; urgency=low
* Initial release (Closes: #62)
-- Michael <mgreene@securityinnovation.com> Tue, 07 Oct 2014 16:37:53 -0700

1
debian/compat vendored
View File

@@ -1 +0,0 @@
8

35
debian/control vendored
View File

@@ -1,35 +0,0 @@
Source: pgpy
Section: python
Priority: optional
Maintainer: Michael Greene <mgreene@securityinnovation.com>
Build-Depends: debhelper (>= 8.0.0), cdbs,
python3-all-dev (>= 3.2), python3-setuptools,
python-all-dev (>= 2.7), python-setuptools
Standards-Version: 3.9.2
X-Python-Version: >= 2.7
X-Python3-Version: >= 3.3
Vcs-Git: https://github.com/SecurityInnovation/PGPy.git
Vcs-Browser: https://github.com/SecurityInnovation/PGPy
Package: python-pgpy
Architecture: all
Depends: ${misc:Depends}, ${python:Depends}
Recommends: python-pgpy-doc
Description: Pretty Good Privacy for Python.
A Pure-Python implementation of OpenPGP.
Package: python3-pgpy
Architecture: all
Depends: ${misc:Depends}, ${python3:Depends}, python3-six
Recommends: python-pgpy-doc
Description: Pretty Good Privacy for Python.
A Pure-Python implementation of OpenPGP.
Package: python-pgpy-doc
Section: doc
Architecture: all
Recommends: python3-pgpy-doc, python-pgpy-doc
Description: Pretty Good Privacy for Python (documentation).
A Pure-Python implementation of OpenPGP.
.
This package contains the HTML documentation.

33
debian/copyright vendored
View File

@@ -1,33 +0,0 @@
Format: http://dep.debian.net/deps/dep5
Upstream-Name: PGPy
Source: https://github.com/SecurityInnovation/PGPy
Files: *
Copyright: 2014-2016 Michael Greene, 2014-2016 Security Innovation, Inc
License: BSD 3-Clause
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the {organization} nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
debian/docs vendored
View File

@@ -1 +0,0 @@
README.rst

View File

@@ -1,3 +0,0 @@
./docs/source/api
./docs/source/examples
./docs/source/*.rst

View File

@@ -1 +0,0 @@
usr/lib/python2*

View File

@@ -1,5 +0,0 @@
enum34 python-enum34
singledispatch python-singledispatch
six python-six (>= 1.9.0)
cryptography python-cryptography (>= 1.1.0)
pyasn1 python-pyasn1

View File

@@ -1 +0,0 @@
usr/lib/python3*

View File

@@ -1,5 +0,0 @@
enum34 -3.4 python3-enum34
singledispatch python3-singledispatch
six python3-six (>= 1.9.0)
cryptography python3-cryptography (>= 1.1.0)
pyasn1 python3-pyasn1

8
debian/rules vendored
View File

@@ -1,8 +0,0 @@
#!/usr/bin/make -f
# -*- makefile -*-
SHELL=/bin/bash
DEB_PYTHON_PACKAGES_EXCLUDE = python-pgpy-doc
include /usr/share/cdbs/1/rules/debhelper.mk
include /usr/share/cdbs/1/class/python-distutils.mk

View File

@@ -1 +0,0 @@
3.0 (native)

View File

@@ -8,16 +8,13 @@ Classes
.. autoclass:: PGPKey
:members:
:noindex:
.. py:attribute:: ascii_header
:noindex:
:annotation: = OrderedDict([('Version', 'PGPy v|version|')])
An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object.
.. py:classmethod:: from_file(filename)
:noindex:
Create a new :py:obj:`PGPKey` object, with contents loaded from a file. May be binary or ASCII armored.
@@ -32,7 +29,6 @@ Classes
# others: { (Fingerprint, bool(key.is_public): PGPKey }
.. py:classmethod:: from_blob(blob)
:noindex:
Create a new :py:obj:`PGPKey` object, with contents loaded from a blob. May be binary or ASCII armored.
@@ -55,7 +51,6 @@ Classes
:members:
.. py:attribute:: ascii_header
:noindex:
An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object.
@@ -67,12 +62,10 @@ Classes
:members:
.. py:attribute:: ascii_header
:noindex:
An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object.
.. py:classmethod:: from_file(filename)
:noindex:
Create a new :py:obj:`PGPMessage` object, with contents loaded from a file. May be binary or ASCII armored.
@@ -83,7 +76,6 @@ Classes
:returns: :py:obj:`PGPMessage`
.. py:classmethod:: from_blob(blob)
:noindex:
Create a new :py:obj:`PGPMessage` object, with contents loaded from a blob. May be binary or ASCII armored.
@@ -102,12 +94,10 @@ Classes
:members:
.. py:attribute:: ascii_header
:noindex:
An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object.
.. py:classmethod:: from_file(filename)
:noindex:
Create a new :py:obj:`PGPSignature` object, with contents loaded from a file. May be binary or ASCII armored.
@@ -118,7 +108,6 @@ Classes
:returns: :py:obj:`PGPSignature`
.. py:classmethod:: from_blob(blob)
:noindex:
Create a new :py:obj:`PGPSignature` object, with contents loaded from a blob. May be binary or ASCII armored.

View File

@@ -8,68 +8,53 @@ Constants
.. autoclass:: PubKeyAlgorithm
:no-members:
:noindex:
.. autoattribute:: RSAEncryptOrSign
:noindex:
:annotation:
.. autoattribute:: DSA
:noindex:
:annotation:
.. autoattribute:: ElGamal
:noindex:
:annotation:
.. autoattribute:: ECDH
:noindex:
:annotation:
.. autoattribute:: ECDSA
:noindex:
:annotation:
:py:class:`EllipticCurveOID`
----------------------------
.. autoclass:: EllipticCurveOID
:noindex:
:no-members:
.. autoattribute:: Curve25519
:noindex:
:annotation:
.. autoattribute:: Ed25519
:noindex:
:annotation:
.. autoattribute:: NIST_P256
:noindex:
:annotation:
.. autoattribute:: NIST_P384
:noindex:
:annotation:
.. autoattribute:: NIST_P521
:noindex:
:annotation:
.. autoattribute:: Brainpool_P256
:noindex:
:annotation:
.. autoattribute:: Brainpool_P384
:noindex:
:annotation:
.. autoattribute:: Brainpool_P512
:noindex:
:annotation:
.. autoattribute:: SECP256K1
:noindex:
:annotation:
@@ -78,38 +63,35 @@ Constants
.. autoclass:: SymmetricKeyAlgorithm
:no-members:
:noindex:
.. autoattribute:: IDEA
:noindex:
:annotation:
.. autoattribute:: TripleDES
:noindex:
:annotation:
.. autoattribute:: CAST5
:noindex:
:annotation:
.. autoattribute:: Blowfish
:noindex:
:annotation:
.. autoattribute:: AES128
:noindex:
:annotation:
.. autoattribute:: AES192
:annotation:
.. autoattribute:: AES256
:annotation:
.. autoattribute:: Camellia128
:noindex:
:annotation:
.. autoattribute:: Camellia192
:noindex:
:annotation:
.. autoattribute:: Camellia256
:noindex:
:annotation:
@@ -118,22 +100,17 @@ Constants
.. autoclass:: CompressionAlgorithm
:no-members:
:noindex:
.. autoattribute:: Uncompressed
:noindex:
:annotation:
.. autoattribute:: ZIP
:noindex:
:annotation:
.. autoattribute:: ZLIB
:noindex:
:annotation:
.. autoattribute:: BZ2
:noindex:
:annotation:
:py:class:`HashAlgorithm`
@@ -141,34 +118,26 @@ Constants
.. autoclass:: HashAlgorithm
:no-members:
:noindex:
.. autoattribute:: MD5
:noindex:
:annotation:
.. autoattribute:: SHA1
:noindex:
:annotation:
.. autoattribute:: RIPEMD160
:noindex:
:annotation:
.. autoattribute:: SHA256
:noindex:
:annotation:
.. autoattribute:: SHA384
:noindex:
:annotation:
.. autoattribute:: SHA512
:noindex:
:annotation:
.. autoattribute:: SHA224
:noindex:
:annotation:
@@ -177,62 +146,47 @@ Constants
.. autoclass:: SignatureType
:no-members:
:noindex:
.. autoattribute:: BinaryDocument
:noindex:
:annotation:
.. autoattribute:: CanonicalDocument
:noindex:
:annotation:
.. autoattribute:: Standalone
:noindex:
:annotation:
.. autoattribute:: Generic_Cert
:noindex:
:annotation:
.. autoattribute:: Persona_Cert
:noindex:
:annotation:
.. autoattribute:: Positive_Cert
:noindex:
:annotation:
.. autoattribute:: Subkey_Binding
:noindex:
:annotation:
.. autoattribute:: PrimaryKey_Binding
:noindex:
:annotation:
.. autoattribute:: DirectlyOnKey
:noindex:
:annotation:
.. autoattribute:: KeyRevocation
:noindex:
:annotation:
.. autoattribute:: SubkeyRevocation
:noindex:
:annotation:
.. autoattribute:: CertRevocation
:noindex:
:annotation:
.. autoattribute:: Timestamp
:noindex:
:annotation:
.. autoattribute:: ThirdParty_Confirmation
:noindex:
:annotation:
@@ -241,34 +195,26 @@ Constants
.. autoclass:: KeyFlags
:no-members:
:noindex:
.. autoattribute:: Certify
:noindex:
:annotation:
.. autoattribute:: Sign
:noindex:
:annotation:
.. autoattribute:: EncryptCommunications
:noindex:
:annotation:
.. autoattribute:: EncryptStorage
:noindex:
:annotation:
.. autoattribute:: Split
:noindex:
:annotation:
.. autoattribute:: Authentication
:noindex:
:annotation:
.. autoattribute:: MultiPerson
:noindex:
:annotation:
@@ -277,25 +223,19 @@ Constants
.. autoclass:: RevocationReason
:no-members:
:noindex:
.. autoattribute:: NotSpecified
:noindex:
:annotation:
.. autoattribute:: Superseded
:noindex:
:annotation:
.. autoattribute:: Compromised
:noindex:
:annotation:
.. autoattribute:: Retired
:noindex:
:annotation:
.. autoattribute:: UserID
:noindex:
:annotation:

View File

@@ -1,9 +0,0 @@
Exceptions
==========
.. py:currentmodule:: pgpy.errors
.. automodule:: pgpy.errors
:members:
:undoc-members:
:noindex:

View File

@@ -0,0 +1,36 @@
Exceptions
==========
.. py:currentmodule:: pgpy.errors
:py:class:`PGPError`
--------------------
.. autoexception:: PGPError
:py:class:`PGPEncryptionError`
------------------------------
.. autoexception:: PGPEncryptionError
:py:class:`PGPDecryptionError`
------------------------------
.. autoexception:: PGPDecryptionError
:py:class:`PGPOpenSSLCipherNotSupported`
----------------------------------------
.. autoexception:: PGPOpenSSLCipherNotSupported
:py:class:`PGPInsecureCipher`
-----------------------------
.. autoexception:: PGPInsecureCipher
:py:class:`WontImplementError`
------------------------------
.. autoexception:: WontImplementError

View File

@@ -2,15 +2,8 @@
PGPy API
********
.. toctree::
:hidden:
.. include:: exceptions.rst.inc
exceptions
constants
classes
.. include:: constants.rst.inc
.. include:: exceptions.rst
.. include:: constants.rst
.. include:: classes.rst
.. include:: classes.rst.inc

View File

@@ -4,10 +4,26 @@
Changelog
*********
v0.4.1
======
Released: April 13, 2017
Bugs Fixed
----------
* Fixed an issue with dearmoring ASCII-armored PGP blocks with windows-style newlines (#156)
* Improved the robustness of the code that tunes the hash count for deriving symmetric encryption keys (#157)
* Fixed an issue with how public keys are created from private keys that was causing exports to become malformed (#168)
* Added explicit support for Python 3.6 (#166)
New Features
------------
* Added support for Brainpool Standard curves for users who have OpenSSL 1.0.2 available
v0.4.0
======
Released: |today|
Released: April 21, 2016
Bugs Fixed
----------

View File

@@ -27,6 +27,21 @@ It is possible to generate most types of keys with PGPy now. The process is most
ciphers=[SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.AES192, SymmetricKeyAlgorithm.AES128],
compression=[CompressionAlgorithm.ZLIB, CompressionAlgorithm.BZ2, CompressionAlgorithm.ZIP, CompressionAlgorithm.Uncompressed])
Specifying key expiration can be done using the ``key_expires`` keyword when adding the user id. Expiration can be specified
using a :py:obj:`datetime.datetime` or a :py:obj:`datetime.timedelta` object::
from datetime import timedelta
# in this example, we'll use fewer preferences for the sake of brevity, and set the key to expire in 10 years
key = pgpy.PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 4096)
uid = pgpy.PGPUID.new('Nikola Tesla') # comment and email are optional
# the key_expires keyword accepts a :py:obj:`datetime.datetime`
key.add_uid(uid, usage={KeyFlags.Sign}, hashes=[HashAlgorithm.SHA512, HashAlgorithm.SHA256],
ciphers=[SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.Camellia256],
compression=[CompressionAlgorithm.BZ2, CompressionAlgorithm.Uncompressed],
key_expires=timedelta(days=365))
Generating Sub Keys
^^^^^^^^^^^^^^^^^^^
@@ -35,7 +50,9 @@ Generating a subkey is similar to the process above, except that it requires an
# assuming we already have a primary key, we can generate a new key and add it as a subkey thusly:
subkey = pgpy.PGPKey.new(PubKeyAlgorithm.RSA, 4096)
# preferences that are specific to the subkey can be chosen here, otherwise the key will use the primary key's preferences.
# preferences that are specific to the subkey can be chosen here
# any preference(s) needed for actions by this subkey that not specified here
# will seamlessly "inherit" from those specified on the selected User ID
key.add_subkey(subkey, usage={KeyFlags.Authentication})
Loading Keys
@@ -86,7 +103,7 @@ It is usually recommended to passphrase-protect private keys. Adding a passphras
# key.is_public is False
# key.is_protected is False
key.protect("C0rrectPassphr@se")
key.protect("C0rrectPassphr@se", SymmetricKeyAlgorithm.AES256, HashAlgorithm.SHA256)
# key.is_protected is now True
Unlocking Protected Secret Keys
@@ -106,6 +123,11 @@ Key unlocking is quite simple::
# enc_key.is_unlocked is now True
...
# This form works equivalently, but may be more semantically clear in some cases:
with enc_key.unlock("C0rrectPassphr@se") as ukey:
# ukey is just a reference to enc_key in this case
...
Exporting Keys
^^^^^^^^^^^^^^
@@ -126,4 +148,3 @@ in Python 2::
# ASCII armored
keystr = str(key)

View File

@@ -57,4 +57,3 @@ in Python 2::
# if message is cleartext, this will also properly canonicalize and dash-escape
# the message text
msgstr = str(message)

View File

@@ -2,29 +2,63 @@
Installation
************
.. highlight:: bash
.. highlight:: console
Platform Specific Notes
=======================
Windows
-------
PGPy has not been formally tested on Windows. I see no reason why it wouldn't work, but your mileage may vary.
If you try it out and run into any issues, please submit bug reports on the `issue tracker <https://github.com/SecurityInnovation/PGPy/issues>`_!
Linux
-----
Building PGPy on Linux requires a C compiler, headers for Python, headers for OpenSSL, and libffi.
Debian
^^^^^^
PGPy is now in `Debian Sid <https://packages.debian.org/sid/python3-pgpy>`_, and can be installed simply::
$ sudo apt install python3-pgpy
Arch Linux
^^^^^^^^^^
PGPy is available on the `AUR <https://aur.archlinux.org/packages/python-pgpy/>`_
Gentoo
^^^^^^
There are gentoo ebuilds available in the `gentoo branch <https://github.com/SecurityInnovation/PGPy/tree/gentoo>`_
RedHat/CentOS
^^^^^^^^^^^^^
Coming Soon!
Other Linux
^^^^^^^^^^^
Building PGPy on Linux requires a C compiler, headers for Python, headers for OpenSSL, and libffi, to support building Cryptography.
For Debian/Ubuntu, these requirements can be installed like so::
$ sudo apt-get install build-essential libssl-dev libffi-dev python-dev
$ sudo apt install build-essential libssl-dev libffi-dev python-dev
You may need to install ``python3-dev`` if you are using PGPy on Python 3.
For Fedora/RHEL derivatives, the build requirements can be installed like so::
$ sudo yum install gcc libffi-devel python-devel openssl-devel
Mac OS X
--------
If you are on Mac OS, you may experience more limited functionality without installing a more capable version of OpenSSL.
You may refer to Cryptography's documentation on `Using your own OpenSSL on OS X <https://cryptography.io/en/latest/installation/#using-your-own-openssl-on-os-x>`_ for information on how to do so.
You may refer to Cryptography's documentation on `Building cryptography on OS X <https://cryptography.io/en/latest/installation/#building-cryptography-on-os-x>`_ for information on how to do so.
Installation
@@ -33,4 +67,3 @@ Installation
Once you have the prerequisites specified above, PGPy can be installed from PyPI using pip, like so::
$ pip install PGPy

View File

@@ -277,14 +277,14 @@ This section covers things that are considered extensions to PGP, but are not co
:text: Some additional curves that can be used with ECDSA/ECDH that are not explicitly called out in :rfc:`6637`, but have standardized OIDs and are implemented in other software.
:Curves:
- Curve, False, Brainpool P-256
- Curve, False, Brainpool P-384
- Curve, False, Brainpool P-512
- Curve, True, Brainpool P-256
- Curve, True, Brainpool P-384
- Curve, True, Brainpool P-512
- Curve, False, Curve25519
- Curve, True, SECP256K1
.. note:: Use of Brainpool curves with ECDSA/ECDH
:text: Although these curves are not explicitly mentioned in an RFC for OpenPGP at this point, GnuPG 2.1.x+ does support using them, so I have included them here as well.
Although these curves are not explicitly mentioned in an RFC for OpenPGP at this point, GnuPG 2.1.x+ does support using them, so I have included them here as well.
\* Cipher availability depends on the currently installed OpenSSL being compiled with support for it

View File

@@ -1,33 +0,0 @@
# Copyright 2014 Michael Greene
# Distributed under the terms of the BSD 3-Clause License
# $HEADER: $
EAPI=5
PYTHON_COMPAT=( python{2_7,3_2,3_3,3_4} )
inherit distutils-r1
DESCRIPTION="Pretty Good Privacy for Python - a pure Python OpenPGP implementation."
HOMEPAGE="https://github.com/SecurityInnovation/PGPy"
SRC_URI="mirror://pypi/P/PGPy/PGPy-${PV}.tar.gz"
LICENSE="BSD"
SLOT="0"
KEYWORDS="~amd64"
IUSE=""
DEPEND="dev-python/setuptools[${PYTHON_USEDEP}]"
RDEPEND="dev-python/singledispatch[${PYTHON_USEDEP}]
>=dev-python/six-1.7.2[${PYTHON_USEDEP}]
>=dev-python/cryptography-0.5.4[${PYTHON_USEDEP}]
$(python_gen_cond_dep 'dev-python/enum34[${PYTHON_USEDEP}]' python2_7 python3_2 python3_3)"
DOCS=( README.rst )
src_unpack() {
if [ "${A}" != "" ]; then
unpack ${A}
fi
cd "${WORKDIR}"
mv PGPy-${PV} pgpy-${PV}
}

4
install_dependencies.linux.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
# make sure libffi-dev, gnupg2, and pgpdump are installed
sudo apt-get update
sudo apt-get install -y libffi-dev gnupg2 pgpdump

22
install_dependencies.osx.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# mapping to get from the TRAVIS_PYTHON_VERSION environment variable to something pyenv understands
# this will need to be manually kept up to date until travis support for python on osx improves
declare -A pyver
pyver["2.7"]="2.7.13"
pyver["3.3"]="3.3.6"
pyver["3.4"]="3.4.6"
pyver["3.5"]="3.5.3"
pyver["3.6"]="3.6.0"
pyver["pypy"]="pypy2-5.6.0"
pyver["pypy3"]="pypy3.3-5.5-alpha"
sudo brew update
# travis doesn't natively support python on osx yet, so start by installing pyenv
# also install newer openssl here
sudo brew install -y pyenv openssl
# now install the requested version of python, and set it to local
pyenv install ${pyver[${TRAVIS_PYTHON_VERSION}]}
pyenv local ${pyver[${TRAVIS_PYTHON_VERSION}]}
# make sure libffi-dev, gnupg2, pgpdump, and newer openssl are installed as well
sudo brew install -y libffi-dev gnupg2 pgpdump

View File

@@ -15,4 +15,4 @@ __all__ = ['__author__',
__author__ = "Michael Greene"
__copyright__ = "Copyright (c) 2014 Michael Greene"
__license__ = "BSD"
__version__ = str(LooseVersion("0.4.0"))
__version__ = str(LooseVersion("0.4.1"))

54
pgpy/_curves.py Normal file
View File

@@ -0,0 +1,54 @@
""" _curves.py
specify some additional curves that OpenSSL provides but cryptography doesn't explicitly expose
"""
from cryptography import utils
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.bindings.openssl.binding import Binding
__all__ = tuple()
# TODO: investigate defining additional curves using EC_GROUP_new_curve
# https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography#Defining_Curves
def _openssl_get_supported_curves():
if hasattr(_openssl_get_supported_curves, '_curves'):
return _openssl_get_supported_curves._curves
# use cryptography's cffi bindings to get an array of curve names
b = Binding()
cn = b.lib.EC_get_builtin_curves(b.ffi.NULL, 0)
cs = b.ffi.new('EC_builtin_curve[]', cn)
b.lib.EC_get_builtin_curves(cs, cn)
# store the result so we don't have to do all of this every time
curves = { b.ffi.string(b.lib.OBJ_nid2sn(c.nid)).decode('utf-8') for c in cs }
_openssl_get_supported_curves._curves = curves
return curves
@utils.register_interface(ec.EllipticCurve)
class BrainpoolP256R1(object):
name = 'brainpoolP256r1'
key_size = 256
@utils.register_interface(ec.EllipticCurve)
class BrainpoolP384R1(object):
name = 'brainpoolP384r1'
key_size = 384
@utils.register_interface(ec.EllipticCurve)
class BrainpoolP512R1(object):
name = 'brainpoolP512r1'
key_size = 512
# add these curves to the _CURVE_TYPES list
for curve in [BrainpoolP256R1, BrainpoolP384R1, BrainpoolP512R1]:
if curve.name not in ec._CURVE_TYPES and curve.name in _openssl_get_supported_curves():
ec._CURVE_TYPES[curve.name] = curve

View File

@@ -20,6 +20,7 @@ from cryptography.hazmat.primitives.ciphers import algorithms
from .decorators import classproperty
from .types import FlagEnum
from ._curves import BrainpoolP256R1, BrainpoolP384R1, BrainpoolP512R1
__all__ = ['Backend',
'EllipticCurveOID',
@@ -41,8 +42,8 @@ __all__ = ['Backend',
'TrustFlags']
# this is 100 KiB
_hashtunedata = bytearray([10, 11, 12, 13, 14, 15, 16, 17] * 128 * 100)
# this is 50 KiB
_hashtunedata = bytearray([10, 11, 12, 13, 14, 15, 16, 17] * 128 * 50)
class Backend(Enum):
@@ -71,29 +72,34 @@ class EllipticCurveOID(Enum):
NIST_P521 = ('1.3.132.0.35', ec.SECP521R1)
#: Brainpool Standard Curve, 256-bit
#:
#: .. warning::
#: This curve is not currently usable by PGPy
Brainpool_P256 = ('1.3.36.3.3.2.8.1.1.7', )
#: .. note::
#: Requires OpenSSL >= 1.0.2
Brainpool_P256 = ('1.3.36.3.3.2.8.1.1.7', BrainpoolP256R1)
#: Brainpool Standard Curve, 384-bit
#:
#: .. warning::
#: This curve is not currently usable by PGPy
Brainpool_P384 = ('1.3.36.3.3.2.8.1.1.11', )
#: .. note::
#: Requires OpenSSL >= 1.0.2
Brainpool_P384 = ('1.3.36.3.3.2.8.1.1.11', BrainpoolP384R1)
#: Brainpool Standard Curve, 512-bit
#:
#: .. warning::
#: This curve is not currently usable by PGPy
Brainpool_P512 = ('1.3.36.3.3.2.8.1.1.13', )
#: .. note::
#: Requires OpenSSL >= 1.0.2
Brainpool_P512 = ('1.3.36.3.3.2.8.1.1.13', BrainpoolP512R1)
#: SECG curve secp256k1
SECP256K1 = ('1.3.132.0.10', ec.SECP256K1)
def __new__(cls, oid, curve=None):
# preprocessing stage for enum members:
# - set enum_member.value to ObjectIdentifier(oid)
# - set enum_member.curve to curve
# - if curve is not None and curve.name is in ec._CURVE_TYPES, set enum_member.curve to curve
# - otherwise, set enum_member.curve to None
obj = object.__new__(cls)
obj._value_ = ObjectIdentifier(oid)
obj.curve = curve
obj.curve = None
if curve is not None and curve.name in ec._CURVE_TYPES:
obj.curve = curve
return obj
@property
@@ -110,6 +116,7 @@ class EllipticCurveOID(Enum):
# return the hash algorithm to specify in the KDF fields when generating a key
algs = {256: HashAlgorithm.SHA256,
384: HashAlgorithm.SHA384,
512: HashAlgorithm.SHA512,
521: HashAlgorithm.SHA512}
return algs.get(self.key_size, None)
@@ -119,6 +126,7 @@ class EllipticCurveOID(Enum):
# return the AES algorithm to specify in the KDF fields when generating a key
algs = {256: SymmetricKeyAlgorithm.AES128,
384: SymmetricKeyAlgorithm.AES192,
512: SymmetricKeyAlgorithm.AES256,
521: SymmetricKeyAlgorithm.AES256}
return algs.get(self.key_size, None)
@@ -343,15 +351,24 @@ class HashAlgorithm(IntEnum):
return self._tuned_count
def tune_count(self):
start = time.time()
h = self.hasher
h.update(_hashtunedata)
end = time.time()
start = end = 0
htd = _hashtunedata[:]
while start == end:
# potentially do this multiple times in case the resolution of time.time is low enough that
# hashing 100 KiB isn't enough time to produce a measurable difference
# (e.g. if the timer for time.time doesn't have enough precision)
htd = htd + htd
h = self.hasher
start = time.time()
h.update(htd)
end = time.time()
# now calculate how many bytes need to be hashed to reach our expected time period
# GnuPG tunes for about 100ms, so we'll do that as well
_TIME = 0.100
ct = int(len(_hashtunedata) * (_TIME / (end - start)))
ct = int(len(htd) * (_TIME / (end - start)))
c1 = ((ct >> (ct.bit_length() - 5)) - 16)
c2 = (ct.bit_length() - 11)
c = ((c2 << 4) + c1)
@@ -432,7 +449,7 @@ class KeyFlags(FlagEnum):
Sign = 0x02
#: Signifies that a key may be used to encrypt messages.
EncryptCommunications = 0x04
#: Signifies that a key may be used to encrypt storage. Currently equivalent to :py:obj:~pgpy.constants.EncryptCommunications`.
#: Signifies that a key may be used to encrypt storage. Currently equivalent to :py:obj:`~pgpy.constants.EncryptCommunications`.
EncryptStorage = 0x08
#: Signifies that the private component of a given key may have been split by a secret-sharing mechanism. Split
#: keys are not currently supported by PGPy.

View File

@@ -788,6 +788,17 @@ class String2Key(Field):
def __nonzero__(self):
return self.__bool__()
def __copy__(self):
s2k = String2Key()
s2k.usage = self.usage
s2k.encalg = self.encalg
s2k.specifier = self.specifier
s2k.iv = self.iv
s2k.halg = self.halg
s2k.salt = copy.copy(self.salt)
s2k.count = self._count
return s2k
def parse(self, packet, iv=True):
self.usage = packet[0]
del packet[0]
@@ -985,6 +996,13 @@ class PrivKey(PubKey):
return l
def __copy__(self):
pk = super(PrivKey, self).__copy__()
pk.s2k = copy.copy(self.s2k)
pk.encbytes = copy.copy(self.encbytes)
pk.chksum = copy.copy(self.chksum)
return pk
@abc.abstractmethod
def __privkey__(self):
"""return the requisite *PrivateKey class from the cryptography library"""

View File

@@ -556,7 +556,7 @@ class SKESessionKeyV4(SKESessionKey):
# if there is no ciphertext, then the first session key is the session key being used
if len(self.ct) == 0:
return sk
return self.symalg, sk
# otherwise, we now need to decrypt the encrypted session key
m = bytearray(_decrypt(bytes(self.ct), sk, self.symalg))
@@ -565,7 +565,7 @@ class SKESessionKeyV4(SKESessionKey):
symalg = SymmetricKeyAlgorithm(m[0])
del m[0]
return (symalg, bytes(m))
return symalg, bytes(m)
def encrypt_sk(self, passphrase, sk):
# generate the salt and derive the key to encrypt sk with from it
@@ -867,7 +867,7 @@ class PrivKeyV4(PrivKey, PubKeyV4):
def pubkey(self):
# return a copy of ourselves, but just the public half
pk = PubKeyV4()
pk = PubKeyV4() if not isinstance(self, PrivSubKeyV4) else PubSubKeyV4()
pk.created = self.created
pk.pkalg = self.pkalg

View File

@@ -162,8 +162,8 @@ class ByteFlag(Signature):
_bytes = super(ByteFlag, self).__bytearray__()
_bytes += self.int_to_bytes(sum(self.flags))
# null-pad _bytes if they are not up to the end now
if self.header.length + len(self.header) != len(_bytes):
_bytes += b'\x00' * (len(_bytes) - len(self.header) - 1)
if len(_bytes) < len(self):
_bytes += b'\x00' * (len(self) - len(_bytes))
return _bytes
def parse(self, packet):

View File

@@ -3,6 +3,7 @@
this is where the armorable PGP block objects live
"""
import binascii
import calendar
import collections
import contextlib
import copy
@@ -360,10 +361,9 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
"""
_data += re.subn(br'\r?\n', b'\r\n', subject)[0]
if self.type in [SignatureType.Generic_Cert, SignatureType.Persona_Cert, SignatureType.Casual_Cert,
if self.type in {SignatureType.Generic_Cert, SignatureType.Persona_Cert, SignatureType.Casual_Cert,
SignatureType.Positive_Cert, SignatureType.CertRevocation, SignatureType.Subkey_Binding,
SignatureType.PrimaryKey_Binding, SignatureType.DirectlyOnKey, SignatureType.KeyRevocation,
SignatureType.SubkeyRevocation]:
SignatureType.PrimaryKey_Binding}:
"""
When a signature is made over a key, the hash data starts with the
octet 0x99, followed by a two-octet length of the key, and then body
@@ -385,7 +385,7 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
if len(_s) > 0:
_data += b'\x99' + self.int_to_bytes(len(_s), 2) + _s
if self.type in [SignatureType.Subkey_Binding, SignatureType.PrimaryKey_Binding, SignatureType.SubkeyRevocation]:
if self.type in {SignatureType.Subkey_Binding, SignatureType.PrimaryKey_Binding}:
"""
A subkey binding signature
(type 0x18) or primary key binding signature (type 0x19) then hashes
@@ -400,8 +400,34 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
_data += b'\x99' + self.int_to_bytes(len(_s), 2) + _s
if self.type in [SignatureType.Generic_Cert, SignatureType.Persona_Cert, SignatureType.Casual_Cert,
SignatureType.Positive_Cert, SignatureType.CertRevocation]:
if self.type in {SignatureType.KeyRevocation, SignatureType.SubkeyRevocation, SignatureType.DirectlyOnKey}:
"""
The signature is calculated directly on the key being revoked. A
revoked key is not to be used. Only revocation signatures by the
key being revoked, or by an authorized revocation key, should be
considered valid revocation signatures.
Subkey revocation signature
The signature is calculated directly on the subkey being revoked.
A revoked subkey is not to be used. Only revocation signatures
by the top-level signature key that is bound to this subkey, or
by an authorized revocation key, should be considered valid
revocation signatures.
Signature directly on a key
This signature is calculated directly on a key. It binds the
information in the Signature subpackets to the key, and is
appropriate to be used for subpackets that provide information
about the key, such as the Revocation Key subpacket. It is also
appropriate for statements that non-self certifiers want to make
about the key itself, rather than the binding between a key and a
name.
"""
_s = subject.hashdata
_data += b'\x99' + self.int_to_bytes(len(_s), 2) + _s
if self.type in {SignatureType.Generic_Cert, SignatureType.Persona_Cert, SignatureType.Casual_Cert,
SignatureType.Positive_Cert, SignatureType.CertRevocation}:
"""
A certification signature (type 0x10 through 0x13) hashes the User
ID being bound to the key into the hash context after the above
@@ -421,10 +447,12 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
_s = subject.hashdata
if subject.is_uid:
_data += b'\xb4' + self.int_to_bytes(len(_s), 4) + _s
_data += b'\xb4'
if subject.is_ua:
_data += b'\xd1' + self.int_to_bytes(len(_s), 4) + _s
else:
_data += b'\xd1'
_data += self.int_to_bytes(len(_s), 4) + _s
# if this is a new signature, do update_hlen
if 0 in list(self._signature.signature):
@@ -789,12 +817,18 @@ class PGPMessage(Armorable, PGPObject):
def __str__(self):
if self.type == 'cleartext':
return "-----BEGIN PGP SIGNED MESSAGE-----\n" \
"Hash: {hashes:s}\n\n" \
"{cleartext:s}\n" \
"{signature:s}".format(hashes=','.join(set(s.hash_algorithm.name for s in self.signatures)),
cleartext=self.dash_escape(self.bytes_to_text(self._message)),
signature=super(PGPMessage, self).__str__())
tmpl = u"-----BEGIN PGP SIGNED MESSAGE-----\n" \
u"{hhdr:s}\n" \
u"{cleartext:s}\n" \
u"{signature:s}"
# only add a Hash: header if we actually have at least one signature
hashes = set(s.hash_algorithm.name for s in self.signatures)
hhdr = 'Hash: {hashes:s}\n'.format(hashes=','.join(sorted(hashes))) if hashes else ''
return tmpl.format(hhdr=hhdr,
cleartext=self.dash_escape(self.bytes_to_text(self._message)),
signature=super(PGPMessage, self).__str__())
return super(PGPMessage, self).__str__()
@@ -1207,8 +1241,8 @@ class PGPKey(Armorable, ParentRef, PGPObject):
if self.is_public:
return self._key.__bytearray__()[len(self._key.header):]
publen = len(self._key) - len(self._key.keymaterial) + self._key.keymaterial.publen()
return self._key.__bytearray__()[len(self._key.header):publen]
pub = self._key.pubkey()
return pub.__bytearray__()[len(pub.header):]
@property
def is_expired(self):
@@ -1253,6 +1287,15 @@ class PGPKey(Armorable, ParentRef, PGPObject):
"""The :py:obj:`constants.PubKeyAlgorithm` pertaining to this key"""
return self._key.pkalg
@property
def key_size(self):
"""*new in 0.4.1*
The size pertaining to this key. ``int`` for non-EC key algorithms; :py:obj:`constants.EllipticCurveOID` for EC keys.
"""
if self.key_algorithm in {PubKeyAlgorithm.ECDSA, PubKeyAlgorithm.ECDH}:
return self._key.keymaterial.oid
return next(iter(self._key.keymaterial)).bit_length()
@property
def magic(self):
return '{:s} KEY BLOCK'.format('PUBLIC' if (isinstance(self._key, Public) and not isinstance(self._key, Private)) else
@@ -1282,7 +1325,8 @@ class PGPKey(Armorable, ParentRef, PGPObject):
# copy signatures that weren't copied with uids
for sig in self._signatures:
pub |= copy.copy(sig)
if sig.parent is None:
pub |= copy.copy(sig)
# keep connect the two halves using a weak reference
self._sibling = weakref.ref(pub)
@@ -1425,7 +1469,7 @@ class PGPKey(Armorable, ParentRef, PGPObject):
raise TypeError
def __or__(self, other):
def __or__(self, other, from_sib=False):
if isinstance(other, Key) and self._key is None:
self._key = other
@@ -1451,9 +1495,13 @@ class PGPKey(Armorable, ParentRef, PGPObject):
raise TypeError("unsupported operand type(s) for |: '{:s}' and '{:s}'"
"".format(self.__class__.__name__, other.__class__.__name__))
if isinstance(self._sibling, weakref.ref): # pragma: no cover
if isinstance(self._sibling, weakref.ref) and not from_sib:
sib = self._sibling()
sib |= copy.copy(other)
if sib is None:
self._sibling = None
else: # pragma: no cover
sib.__or__(copy.copy(other), True)
return self
@@ -1461,13 +1509,18 @@ class PGPKey(Armorable, ParentRef, PGPObject):
key = super(PGPKey, self).__copy__()
key._key = copy.copy(self._key)
for id, subkey in self._children.items():
key |= copy.copy(subkey)
for uid in self._uids:
key |= copy.copy(uid)
for id, subkey in self._children.items():
key |= copy.copy(subkey)
for sig in self._signatures:
if sig.embedded:
# embedded signatures don't need to be explicitly copied
continue
print(len(key._signatures))
key |= copy.copy(sig)
return key
@@ -1801,10 +1854,10 @@ class PGPKey(Armorable, ParentRef, PGPObject):
:keyword compression: A list of preferred compression algorithms, as :py:obj:`~constants.CompressionAlgorithm`.
This keyword is ignored for non-self-certifications.
:type compression: ``list``
:keyword key_expires: Specify a key expiration date for when this key should expire, or a
:keyword key_expiration: Specify a key expiration date for when this key should expire, or a
:py:obj:`~datetime.timedelta` of how long after the key was created it should expire.
This keyword is ignored for non-self-certifications.
:type key_expires: :py:obj:`datetime.datetime`, :py:obj:`datetime.timedelta`
:type key_expiration: :py:obj:`datetime.datetime`, :py:obj:`datetime.timedelta`
:keyword keyserver: Specify the URI of the preferred key server of the user.
This keyword is ignored for non-self-certifications.
:type keyserver: ``str``, ``unicode``, ``bytes``
@@ -2049,24 +2102,23 @@ class PGPKey(Armorable, ParentRef, PGPObject):
return [ sig for sig in sigs if sig.signer in _ids ]
# collect signature(s)
if isinstance(signature, PGPSignature):
if signature.signer != self.fingerprint.keyid and signature.signer not in self.subkeys:
raise PGPError("Incorrect key. Expected: {:s}".format(signature.signer))
sspairs.append((signature, subject))
if signature is None:
if isinstance(subject, PGPMessage):
sspairs += [ (sig, subject.message) for sig in _filter_sigs(subject.signatures) ]
if isinstance(subject, PGPMessage):
sspairs += [ (sig, subject.message) for sig in _filter_sigs(subject.signatures) ]
if isinstance(subject, (PGPUID, PGPKey)):
sspairs += [ (sig, subject) for sig in _filter_sigs(subject.__sig__) ]
if isinstance(subject, (PGPUID, PGPKey)):
sspairs += [ (sig, subject) for sig in _filter_sigs(subject.__sig__) ]
if isinstance(subject, PGPKey):
# user ids
sspairs += [ (sig, uid) for uid in subject.userids for sig in _filter_sigs(uid.__sig__) ]
# user attributes
sspairs += [ (sig, ua) for ua in subject.userattributes for sig in _filter_sigs(ua.__sig__) ]
# subkey binding signatures
sspairs += [ (sig, subkey) for subkey in subject.subkeys.values() for sig in _filter_sigs(subkey.__sig__) ]
if isinstance(subject, PGPKey):
# user ids
sspairs += [ (sig, uid) for uid in subject.userids for sig in _filter_sigs(uid.__sig__) ]
# user attributes
sspairs += [ (sig, ua) for ua in subject.userattributes for sig in _filter_sigs(ua.__sig__) ]
# subkey binding signatures
sspairs += [ (sig, subkey) for subkey in subject.subkeys.values() for sig in _filter_sigs(subkey.__sig__) ]
elif signature.signer in {self.fingerprint.keyid} | set(self.subkeys):
sspairs += [(signature, subject)]
if len(sspairs) == 0:
raise PGPError("No signatures to verify")
@@ -2074,7 +2126,7 @@ class PGPKey(Armorable, ParentRef, PGPObject):
# finally, start verifying signatures
sigv = SignatureVerification()
for sig, subj in sspairs:
if self.fingerprint.keyid != sig.signer:
if self.fingerprint.keyid != sig.signer and sig.signer in self.subkeys:
warnings.warn("Signature was signed with this key's subkey: {:s}. "
"Verifying with subkey...".format(sig.signer),
stacklevel=2)

View File

@@ -64,10 +64,10 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
@staticmethod
def is_ascii(text):
if isinstance(text, six.string_types):
return bool(re.match(r'^[ -~\n]+$', text, flags=re.ASCII))
return bool(re.match(r'^[ -~\r\n]+$', text, flags=re.ASCII))
if isinstance(text, (bytes, bytearray)):
return bool(re.match(br'^[ -~\n]+$', text, flags=re.ASCII))
return bool(re.match(br'^[ -~\r\n]+$', text, flags=re.ASCII))
raise TypeError("Expected: ASCII input of type str, bytes, or bytearray") # pragma: no cover
@@ -92,25 +92,24 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
# the re.VERBOSE flag allows for:
# - whitespace is ignored except when in a character class or escaped
# - anything after a '#' that is not escaped or in a character class is ignored, allowing for comments
m = re.match(r"""# This capture group is optional because it will only be present in signed cleartext messages
m = re.search(r"""# This capture group is optional because it will only be present in signed cleartext messages
(^-{5}BEGIN\ PGP\ SIGNED\ MESSAGE-{5}(?:\r?\n)
(Hash:\ (?P<hashes>[A-Za-z0-9\-,]+)(?:\r?\n){2})?
(?P<cleartext>(.*\n)+)(?:\r?\n)
)?
# armor header line; capture the variable part of the magic text
^-{5}BEGIN\ PGP\ (?P<magic>[A-Z0-9 ,]+)-{5}$(?:\r?\n)
^-{5}BEGIN\ PGP\ (?P<magic>[A-Z0-9 ,]+)-{5}(?:\r?\n)
# try to capture all the headers into one capture group
# if this doesn't match, m['headers'] will be None
((?P<headers>(^.+:\ .+$(?:\r?\n))+))?(?:\r?\n)?
(?P<headers>(^.+:\ .+(?:\r?\n))+)?(?:\r?\n)?
# capture all lines of the body, up to 76 characters long,
# including the newline, and the pad character(s)
(?P<body>([A-Za-z0-9+/]{1,75}={,2}(?:\r?\n))+)
# capture the armored CRC24 value
^=(?P<crc>[A-Za-z0-9+/]{4})$(?:\r?\n)
^=(?P<crc>[A-Za-z0-9+/]{4})(?:\r?\n)
# finally, capture the armor tail line, which must match the armor header line
^-{5}END\ PGP\ (?P=magic)-{5}$(?:\r?\n)?
""",
text, flags=re.MULTILINE | re.VERBOSE)
^-{5}END\ PGP\ (?P=magic)-{5}(?:\r?\n)?
""", text, flags=re.MULTILINE | re.VERBOSE)
if m is None: # pragma: no cover
raise ValueError("Expected: ASCII-armored PGP data")
@@ -638,15 +637,20 @@ class FlagEnumMeta(EnumMeta):
return self & other
class FlagEnum(six.with_metaclass(FlagEnumMeta, IntEnum)):
pass
if six.PY2:
class FlagEnum(IntEnum):
__metaclass__ = FlagEnumMeta
else:
namespace = FlagEnumMeta.__prepare__('FlagEnum', (IntEnum,))
FlagEnum = FlagEnumMeta('FlagEnum', (IntEnum,), namespace)
class Fingerprint(str):
"""
A subclass of ``str``. Can be compared using == and != to ``str``, ``unicode``, and other :py:obj:`Fingerprint` instances.
Primarily used as a key for internal dictionaries, so it ignores spaces when comparing and
Primarily used as a key for internal dictionaries, so it ignores spaces when comparing and hashing
"""
@property
def keyid(self):

View File

@@ -1,5 +1,6 @@
-r requirements.txt
pytest
pytest-cov
pytest-ordering
flake8
pep8-naming

View File

@@ -52,6 +52,7 @@ setup(
'Operating System :: Microsoft :: Windows',
'Intended Audience :: Developers',
'Programming Language :: Python',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.3',

View File

@@ -1,11 +1,11 @@
"""PGPy conftest"""
import pytest
import contextlib
import functools
import glob
import os
import re
import six
import select
import subprocess
import sys
import time
@@ -56,81 +56,74 @@ def _run(bin, *binargs, **pkw):
return cmdo, cmde
# now import stuff from fixtures so it can be imported by test modules
# from fixtures import TestFiles, gpg_getfingerprint, pgpdump, gpg_verify, gpg_fingerprint
class CWD_As(object):
def __init__(self, newwd):
if not os.path.exists(newwd):
raise FileNotFoundError(newwd + " not found within " + os.getcwd())
self.oldwd = os.getcwd()
self.newwd = newwd
def __call__(self, func):
@functools.wraps(func)
def setcwd(*args, **kwargs):
# set new working directory
os.chdir(self.newwd)
# fallback value
fo = None
try:
fo = func(*args, **kwargs)
finally:
# always return to self.oldwd even if there was a failure
os.chdir(self.oldwd)
return fo
return setcwd
_gpg_bin = _which('gpg2')
_gpg_args = ['--options', './pgpy.gpg.conf', '--expert', '--status-fd', '1']
_gpg_env = os.environ.copy()
_gpg_args = ('--options', './pgpy.gpg.conf', '--expert', '--status-fd')
_gpg_env = {}
_gpg_env['GNUPGHOME'] = os.path.abspath(os.path.abspath('tests/testdata'))
_gpg_kwargs = dict()
_gpg_kwargs['cwd'] = 'tests/testdata'
_gpg_kwargs['env'] = _gpg_env
_gpg_kwargs['stdout'] = subprocess.PIPE
_gpg_kwargs['stderr'] = subprocess.STDOUT
_gpg_kwargs['close_fds'] = False
# GPG boilerplate function
def _gpg(*gpg_args, **popen_kwargs):
# gpgfd is our "read" end of the pipe
# _gpgfd is gpg's "write" end
gpgfd, _gpgfd = os.pipe()
# on python >= 3.4, we need to set _gpgfd as inheritable
# older versions do not have this function
if sys.version_info >= (3, 4):
os.set_inheritable(_gpgfd, True)
args = (_gpg_bin,) + _gpg_args + (str(_gpgfd),) + gpg_args
kwargs = _gpg_kwargs.copy()
kwargs.update(popen_kwargs)
try:
# use this as the buffer for collecting status-fd output
c = bytearray()
cmd = subprocess.Popen(args, **kwargs)
while cmd.poll() is None:
while gpgfd in select.select([gpgfd,], [], [], 0)[0]:
c += os.read(gpgfd, 1)
else:
# sleep for a bit
time.sleep(0.010)
# finish reading if needed
while gpgfd in select.select([gpgfd,], [], [], 0)[0]:
c += os.read(gpgfd, 1)
# collect stdout and stderr
o, e = cmd.communicate()
finally:
# close the pipes we used for this
os.close(gpgfd)
os.close(_gpgfd)
return c.decode('latin-1'), (o or b'').decode('latin-1'), (e or b'').decode('latin-1')
# fixtures
@pytest.fixture()
def write_clean():
@contextlib.contextmanager
def _write_clean(fpath, mode='w', data=''):
with open(fpath, mode) as wf:
wf.write(data)
wf.flush()
try:
yield
finally:
os.remove(fpath)
return _write_clean
@pytest.fixture()
def gpg_import():
@contextlib.contextmanager
def _gpg_import(*keypaths):
gpg_args = _gpg_args + ['--import', ] + list(keypaths)
gpg_kwargs = _gpg_kwargs.copy()
gpgo, _ = _run(_gpg_bin, *gpg_args, **gpg_kwargs)
# if GPG version is 2.1 or newer, we need to add a setup/teardown step in creating the keybox folder
if gpg_ver >= '2.1':
if not os.path.exists('tests/testdata/private-keys-v1.d'):
os.mkdir('tests/testdata/private-keys-v1.d')
time.sleep(5)
time.sleep(0.5)
gpgc, gpgo, gpge = _gpg('--batch', '--import', *list(keypaths))
try:
yield gpgo
@@ -139,7 +132,6 @@ def gpg_import():
[os.remove(f) for f in glob.glob('tests/testdata/testkeys.*')]
if gpg_ver >= '2.1':
[os.remove(f) for f in glob.glob('tests/testdata/private-keys-v1.d/*')]
# os.rmdir('tests/testdata/private-keys-v1.d')
time.sleep(0.5)
@@ -149,9 +141,7 @@ def gpg_import():
@pytest.fixture()
def gpg_check_sigs():
def _gpg_check_sigs(*keyids):
gpg_args = _gpg_args + ['--check-sigs'] + list(keyids)
gpg_kwargs = _gpg_kwargs.copy()
gpgo, _ = _run(_gpg_bin, *gpg_args, **gpg_kwargs)
gpgc, gpgo, gpge = _gpg('--check-sigs', *keyids)
return 'sig-' not in gpgo
return _gpg_check_sigs
@@ -163,11 +153,11 @@ def gpg_verify():
r'^\[GNUPG:\] VALIDSIG (?:[0-9A-F]{,24})\1', flags=re.MULTILINE | re.DOTALL)
def _gpg_verify(gpg_subjpath, gpg_sigpath=None, keyid=None):
gpg_args = _gpg_args + [ a for a in ['--verify', gpg_sigpath, gpg_subjpath] if a is not None ]
gpg_kwargs = _gpg_kwargs.copy()
gpgo, _ = _run(_gpg_bin, *gpg_args, **gpg_kwargs)
rargs = [gpg_sigpath, gpg_subjpath] if gpg_sigpath is not None else [gpg_subjpath,]
sigs = [ sv.group('keyid') for sv in sfd_verify.finditer(gpgo) ]
gpgc, gpgo, gpge = _gpg('--verify', *rargs)
sigs = [ sv.group('keyid') for sv in sfd_verify.finditer(gpgc) ]
if keyid is not None:
return keyid in sigs
@@ -181,53 +171,56 @@ def gpg_verify():
def gpg_decrypt():
sfd_decrypt = re.compile(r'^\[GNUPG:\] BEGIN_DECRYPTION\n'
r'^\[GNUPG:\] DECRYPTION_INFO \d+ \d+\n'
r'^\[GNUPG:\] PLAINTEXT \d+ \S+ \n'
r'^\[GNUPG:\] PLAINTEXT (?:62|74|75) (?P<tstamp>\d+) (?P<fname>.*)\n'
r'^\[GNUPG:\] PLAINTEXT_LENGTH \d+\n'
r'(?P<text>(?:.|\n)*)'
r'\[GNUPG:\] DECRYPTION_OKAY\n'
r'^\[GNUPG:\] GOODMDC\n'
r'(?:^\[GNUPG:\] GOODMDC\n)?'
r'^\[GNUPG:\] END_DECRYPTION', flags=re.MULTILINE)
def _gpg_decrypt(encmsgpath, passphrase=None, keyid=None):
gpg_args = [_gpg_bin] + _gpg_args[:]
gpg_kwargs = _gpg_kwargs.copy()
gpg_kwargs['stderr'] = subprocess.PIPE
_comargs = ()
a = []
if passphrase is not None:
gpg_args += ['--batch', '--passphrase-fd', '0']
gpg_kwargs['stdin'] = subprocess.PIPE
_comargs = (passphrase.encode(),)
# create a pipe to send the passphrase to GnuPG through
pfdr, pfdw = os.pipe()
if keyid is not None:
gpg_args += ['--recipient', keyid]
# write the passphrase to the pipe buffer right away
os.write(pfdw, passphrase.encode())
os.write(pfdw, b'\n')
gpg_args += ['--decrypt', encmsgpath]
# on python >= 3.4, we need to set pfdr as inheritable
# older versions do not have this function
if sys.version_info >= (3, 4):
os.set_inheritable(pfdr, True)
gpgdec = subprocess.Popen(gpg_args, **gpg_kwargs)
gpgo, gpge = gpgdec.communicate(*_comargs)
gpgdec.wait()
a.extend(['--batch', '--passphrase-fd', str(pfdr)])
return sfd_decrypt.search(gpgo.decode()).group('text')
elif keyid is not None:
a.extend(['--recipient', keyid])
# return gpgo.decode() if gpgo is not None else gpge
a.extend(['--decrypt', encmsgpath])
gpgc, gpgo, gpge = _gpg(*a, stderr=subprocess.PIPE)
status = sfd_decrypt.match(gpgc)
return gpgo
return _gpg_decrypt
@pytest.fixture
def gpg_print():
sfd_text = re.compile(r'^\[GNUPG:\] PLAINTEXT (?:62|74|75) .*\n'
r'^\[GNUPG:\] PLAINTEXT_LENGTH (?P<len>\d+)\n'
r'^(?P<text>(.|\n)*)', re.MULTILINE)
sfd_text = re.compile(r'^\[GNUPG:\] PLAINTEXT (?:62|74|75) (?P<tstamp>\d+) (?P<fname>.*)\n'
r'^\[GNUPG:\] PLAINTEXT_LENGTH (?P<len>\d+)\n', re.MULTILINE)
gpg_text = re.compile(r'(?:- gpg control packet\n)?(?P<text>.*)', re.MULTILINE | re.DOTALL)
def _gpg_print(infile):
gpg_args = _gpg_args + ['-o-', infile]
gpg_kwargs = _gpg_kwargs.copy()
gpg_kwargs['stderr'] = subprocess.PIPE
gpgc, gpgo, gpge = _gpg('-o-', infile, stderr=subprocess.PIPE)
status = sfd_text.match(gpgc)
tlen = len(gpgo) if status is None else int(status.group('len'))
gpgo, _ = _run(_gpg_bin, *gpg_args, **gpg_kwargs)
return sfd_text.match(gpgo).group('text')
return gpg_text.match(gpgo).group('text')[:tlen]
return _gpg_print
@@ -235,12 +228,8 @@ def gpg_print():
@pytest.fixture
def gpg_keyid_file():
def _gpg_keyid_file(infile):
gpg_args = _gpg_args + ['--list-packets', infile]
gpg_kwargs = _gpg_kwargs.copy()
gpgo, _ = _run(_gpg_bin, *gpg_args, **gpg_kwargs)
return re.findall(r'^\s+keyid: ([0-9A-F]+)', gpgo, flags=re.MULTILINE)
c, o, e = _gpg('--list-packets', infile)
return re.findall(r'^\s+keyid: ([0-9A-F]+)', o, flags=re.MULTILINE)
return _gpg_keyid_file
@@ -257,6 +246,8 @@ def pgpdump():
# pytest_configure
# called after command line options have been parsed and all plugins and initial conftest files been loaded.
def pytest_configure(config):
print("== PGPy Test Suite ==")
# ensure commands we need exist
for cmd in ['gpg2', 'pgpdump']:
if _which(cmd) is None:
@@ -270,25 +261,9 @@ def pytest_configure(config):
v, _ = _run(_which('pgpdump'), '-v', stderr=subprocess.STDOUT)
pgpdump_ver.parse(v.split(' ')[2].strip(','))
# display the working directory and the OpenSSL version
# display the working directory and the OpenSSL/GPG/pgpdump versions
print("Working Directory: " + os.getcwd())
print("Using OpenSSL " + str(openssl_ver))
print("Using GnuPG " + str(gpg_ver))
print("Using pgpdump " + str(pgpdump_ver))
print("")
# pytest_generate_tests
# called when each test method is collected to generate parametrizations
def pytest_generate_tests(metafunc):
if metafunc.cls is not None and hasattr(metafunc.cls, 'params'):
funcargs = [ (k, v) for k, v in metafunc.cls.params.items() if k in metafunc.fixturenames ]
args = [','.join(k for k, _ in funcargs),
list(zip(*[v for _, v in funcargs])) if len(funcargs) > 1 else [vi for _, v in funcargs for vi in v]]
kwargs = {}
if hasattr(metafunc.cls, 'ids') and metafunc.function.__name__ in metafunc.cls.ids:
kwargs['ids'] = metafunc.cls.ids[metafunc.function.__name__]
metafunc.parametrize(*args, **kwargs)

View File

@@ -19,43 +19,24 @@ modules = ['pgpy.constants',
'pgpy.packet.subpackets.types',
'pgpy.packet.subpackets.userattribute']
def get_module_objs(module):
# return a set of strings that represent the names of objects defined in that module
return { n for n, o in inspect.getmembers(module) if (inspect.getmodule(o) is module) }
return { n for n, o in inspect.getmembers(module, lambda m: inspect.getmodule(m) is module) } | ({'FlagEnum',} if module is importlib.import_module('pgpy.types') else set()) # dirty workaround until six fixes metaclass stuff to support EnumMeta in Python >= 3.6
def get_module_all(module):
return set(getattr(module, '__all__', set()))
def test_pgpy_all():
import pgpy
# just check that everything in pgpy.__all__ is actually there
assert set(pgpy.__all__) <= { n for n, _ in inspect.getmembers(pgpy) }
@pytest.mark.parametrize('modname', modules)
def test_exports(modname):
module = importlib.import_module(modname)
modall = getattr(module, '__all__', None)
if modall is None:
pytest.skip('__all__ not defined')
assert set(modall) == get_module_objs(module)
# check_modules = [pgpy.constants,
# pgpy.decorators,
# pgpy.errors,
# pgpy.pgp,
# pgpy.symenc,
# pgpy.types,
#
# pgpy.packet.fields,
# pgpy.packet.packets,
# pgpy.packet.types,
#
# pgpy.packet.subpackets.signature,
# pgpy.packet.subpackets.types,
# pgpy.packet.subpackets.userattribute,]
#
# for module in check_modules:
# if getattr(module, '__all__', None) is not None:
# assert set(module.__all__) == get_module_objs(module)
assert get_module_all(module) == get_module_objs(module)

View File

@@ -1,52 +1,69 @@
""" test field parsing
"""
from itertools import product
import pytest
import itertools
from pgpy.constants import HashAlgorithm
from pgpy.constants import String2KeyType
from pgpy.constants import SymmetricKeyAlgorithm
from pgpy.packet.types import Header
from pgpy.packet.fields import String2Key
from pgpy.packet.types import Header
from pgpy.packet.subpackets import Signature
from pgpy.packet.subpackets import UserAttribute
from pgpy.packet.subpackets.types import Header as HeaderSP
from pgpy.packet.subpackets.types import Opaque as OpaqueSP
_trailer = b'\xde\xca\xff\xba\xdd'
_tag = bytearray(b'\xc2')
pkt_headers = [
# new format
# 1 byte length - 191
_tag + b'\xbf' + (b'\x00' * 191) + _trailer,
# 2 byte length - 192
_tag + b'\xc0\x00' + (b'\x00' * 192) + _trailer,
# 2 byte length - 8383
_tag + b'\xdf\xff' + (b'\x00' * 8383) + _trailer,
# 5 byte length - 8384
_tag + b'\xff\x00\x00 \xc0' + (b'\x00' * 8384) + _trailer,
# old format
# 1 byte length - 255
bytearray(b'\x88') + b'\xff' + (b'\x00' * 255) + _trailer,
# 2 byte length - 256
bytearray(b'\x89') + b'\x01\x00' + (b'\x00' * 256) + _trailer,
# 4 byte length - 65536
bytearray(b'\x8a') + b'\x00\x01\x00\x00' + (b'\x00' * 65536) + _trailer,
]
subpkt_headers = [
# 1 byte length - 191
bytearray(b'\xbf' + b'\x00' + (b'\x00' * 190)),
# 2 byte length - 192
bytearray(b'\xc0\x00' + b'\x00' + (b'\x00' * 191)),
# 2 byte length - 8383
bytearray(b'\xdf\xff' + b'\x00' + (b'\x00' * 8382)),
# 5 byte length - 8384
bytearray(b'\xff\x00\x00 \xc0' + b'\x00' + (b'\x00' * 0x8383)),
# 5 byte length - 65535
bytearray(b'\xff\x00\x00\xff\xff' + b'\x00' + (b'\x00' * 65534)),
]
class TestHeaders(object):
params = {
'pheader': [# new format
# 1 byte length - 191
bytearray(b'\xc2' + b'\xbf' + (b'\x00' * 191) + b'\xca\xfe\xba\xbe'),
# 2 byte length - 192
bytearray(b'\xc2' + b'\xc0\x00' + (b'\x00' * 192) + b'\xca\xfe\xba\xbe'),
# 2 byte length - 8383
bytearray(b'\xc2' + b'\xdf\xff' + (b'\x00' * 8383) + b'\xca\xfe\xba\xbe'),
# 5 byte length - 8384
bytearray(b'\xc2' + b'\xff\x00\x00 \xc0' + (b'\x00' * 8384) + b'\xca\xfe\xba\xbe'),
# old format
# 1 byte length - 255
bytearray(b'\x88' + b'\xff' + (b'\x00' * 255) + b'\xca\xfe\xba\xbe'),
# 2 byte length - 256
bytearray(b'\x89' + b'\x01\x00' + (b'\x00' * 256) + b'\xca\xfe\xba\xbe'),
# 4 byte length - 65536
bytearray(b'\x8a' + b'\x00\x01\x00\x00' + (b'\x00' * 65536) + b'\xca\xfe\xba\xbe'),
],
'spheader': [# 1 byte length - 191
bytearray(b'\xbf' + b'\x00' + (b'\x00' * 190)),
# 2 byte length - 192
bytearray(b'\xc0\x00' + b'\x00' + (b'\x00' * 191)),
# 2 byte length - 8383
bytearray(b'\xdf\xff' + b'\x00' + (b'\x00' * 8382)),
# 5 byte length - 8384
bytearray(b'\xff\x00\x00 \xc0' + b'\x00' + (b'\x00' * 0x8383)),
# 5 byte length - 65535
bytearray(b'\xff\x00\x00\xff\xff' + b'\x00' + (b'\x00' * 65534)),
]
}
@pytest.mark.parametrize('pheader', pkt_headers)
def test_packet_header(self, pheader):
b = pheader[:]
h = Header()
h.parse(pheader)
assert h.tag == 0x02
assert h.length == len(pheader) - len(_trailer)
assert pheader[h.length:] == _trailer
assert len(h) == len(b) - len(pheader)
assert h.__bytes__() == b[:len(h)]
@pytest.mark.parametrize('spheader', subpkt_headers)
def test_subpacket_header(self, spheader):
h = HeaderSP()
h.parse(spheader)
@@ -54,16 +71,6 @@ class TestHeaders(object):
assert 65537 > h.length > 1
assert len(h) == len(h.__bytes__())
def test_packet_header(self, pheader):
b = pheader[:]
h = Header()
h.parse(pheader)
assert h.tag == 0x02
assert h.length == len(pheader) - 4
assert pheader[h.length:] == b'\xca\xfe\xba\xbe'
assert len(h) == len(b) - len(pheader)
assert h.__bytes__() == b[:len(h)]
_sspclasses = {
# 0x00: 'Opaque',
@@ -112,155 +119,158 @@ _sspclasses = {
0x6d: 'Opaque',
0x6e: 'Opaque',
}
_uaspclasses = {
0x01: 'Image'
}
_ssps = [
# 0x02 - creation time
b'\x05\x02?z\xf7\x13',
# 0x03 - expiration time
b'\x05\x03\x00\x12u\x00',
# 0x04 - exportable certification
b'\x02\x04\x00',
# 0x05
b'\x03\x05\x01x',
# 0x06
b'\x1d\x06<[^>]+[@.]liebenzell\\.org>$\x00',
# 0x07
b'\x02\x07\x00',
# 0x08
# 0x09
b'\x05\t\x01\xe13\x80',
# 0x0a
b'\x17\n\x00\x11M,\x9e,\xee~&\rK\xbd\x9b[\x1b`\xbcu\x0c\xefW\x06',
# 0x0b
b'\x05\x0b\x07\n\x03\x04',
# 0x0c
b'\x17\x0c\x80\x119\x06\xf8\xf6\x98d\x9e\xbePG\xd0\xba\x11\xed\xa7\xd0!<\xa1\x1b',
# 0x10
b"\t\x10\n'Z\xb6\xb4\xbc\xa5\xd7",
# 0x12
b'\x05\x12R/\xe2d',
# 0x14
b'\x87\x14\x80\x00\x00\x00\x00\x10\x00nsignotes@grep.be"http://www.grep.be/gpg/CF62318D5BBE'
b'D48F33ACD5431B0006256FB29164/0138DA92EDFFB27DD270F86DB475E207BAB58229.asc"',
# 0x15
b'\x03\x15\x03\x02',
# 0x16
b'\x03\x16\x02\x01',
# 0x17
b'\x02\x17\x80',
# 0x18
b'\x19\x18hkp://fakekey.server.tld',
# 0x19
b'\x02\x19\x01',
# 0x1a
b'\x15\x1ahttp://www.blaap.org',
# 0x1b
b'\x02\x1b#',
# 0x1c
b' \x1cSander Temme <sander@temme.net>',
# 0x1d
b'\x02\x1d\x00',
# 0x1e
b'\x02\x1e\x01',
# 0x20
b"\xc0] \x04\x19\x01\n\x00\x06\x05\x02S\x9a6\x06\x00\n\t\x10\x19q\xf7\xb8\x80g\xdd\x07\xd20"
b"\x07\xfd\x19\xbb\xea;6|\xdb1\xf3\xbc\xfbZ\x1d\xb6\xcfY\xe6&\xe9\xed\xf1O\xdc\x84\xdd\xe1"
b"\x88\xff\xb9\xba\x1a\xe9\x8d\x16K\xd2\xb4\xf49\x7f(\xc9\xe8/\xf6\x87\x0f\xef\xb7*\xf9'r{E"
b"\xf3\x07?\xcb\xffm\x87\x86&H\xee\xc4\xbc\xf1L\x177\x92\xdb\xf9I\x16Q\xf6\x9ei\xf56z\x0f\xff"
b"^\x92\x88Kh\xbd; \x86\xa5\xbaL\xa2\xda\x93\xae\x10\xd1Y\xa5\xa7\xb4)*\xf6\xa1,]\xd1\xe3\\"
b"\xc3l3\xecA\xec&\x145\xe1\xc4\xd0\x15y\xb2\xf8\x0c\x0e\xd3_[\x1f\x0fM\x98\xa8J\xb3\xd9?\xa4"
b"\xb3\x16\xee8\xad/\x07\xea\x7f\xad\x1a\x0f\xbe\x06\x94\xa51\xf6@\xae\xcdy\x92B\x1c\xd5\x04z"
b"\xbf\xe9\xbc\x9c\xac\x99W6\x81\xad\xe0\x81\xb4\x89n\xd0_\x1c\x92\xbe\xf6\x1cmn\xe92_\x86\xcf"
b"\xb0v\x1f\x9dk%\xbd<\x0c\x1e\x91\x0c\xec\\\xdc\x8cCu\xd8N\xf2\x82E\x00\xc8rnSY\x1b\xa0%\x13"
b"\xc0$Q+\xd3\xd0\xd8 \x0c\xe9\xafI5&\xe5\xc1!\xaf",
# 0x65
b'\x07eGPG\x00\x01\x01',
]
sig_subpkts = [bytearray(sp) + _trailer for sp in _ssps]
class TestSignatureSubPackets(object):
params = {
'sigsubpacket': [ bytearray(sp) + b'\xca\xfe\xba\xbe' for sp in
[ # 0x02 - creation time
b'\x05\x02?z\xf7\x13',
# 0x03 - expiration time
b'\x05\x03\x00\x12u\x00',
# 0x04 - exportable certification
b'\x02\x04\x00',
# 0x05
b'\x03\x05\x01x',
# 0x06
b'\x1d\x06<[^>]+[@.]liebenzell\\.org>$\x00',
# 0x07
b'\x02\x07\x00',
# 0x08
# 0x09
b'\x05\t\x01\xe13\x80',
# 0x0a
b'\x17\n\x00\x11M,\x9e,\xee~&\rK\xbd\x9b[\x1b`\xbcu\x0c\xefW\x06',
# 0x0b
b'\x05\x0b\x07\n\x03\x04',
# 0x0c
b'\x17\x0c\x80\x119\x06\xf8\xf6\x98d\x9e\xbePG\xd0\xba\x11\xed\xa7\xd0!<\xa1\x1b',
# 0x10
b"\t\x10\n'Z\xb6\xb4\xbc\xa5\xd7",
# 0x12
b'\x05\x12R/\xe2d',
# 0x14
b'\x87\x14\x80\x00\x00\x00\x00\x10\x00nsignotes@grep.be"http://www.grep.be/gpg/CF62318D5BBE'
b'D48F33ACD5431B0006256FB29164/0138DA92EDFFB27DD270F86DB475E207BAB58229.asc"',
# 0x15
b'\x03\x15\x03\x02',
# 0x16
b'\x03\x16\x02\x01',
# 0x17
b'\x02\x17\x80',
# 0x18
b'\x19\x18hkp://fakekey.server.tld',
# 0x19
b'\x02\x19\x01',
# 0x1a
b'\x15\x1ahttp://www.blaap.org',
# 0x1b
b'\x02\x1b#',
# 0x1c
b' \x1cSander Temme <sander@temme.net>',
# 0x1d
b'\x02\x1d\x00',
# 0x1e
b'\x02\x1e\x01',
# 0x20
b"\xc0] \x04\x19\x01\n\x00\x06\x05\x02S\x9a6\x06\x00\n\t\x10\x19q\xf7\xb8\x80g\xdd\x07\xd20"
b"\x07\xfd\x19\xbb\xea;6|\xdb1\xf3\xbc\xfbZ\x1d\xb6\xcfY\xe6&\xe9\xed\xf1O\xdc\x84\xdd\xe1"
b"\x88\xff\xb9\xba\x1a\xe9\x8d\x16K\xd2\xb4\xf49\x7f(\xc9\xe8/\xf6\x87\x0f\xef\xb7*\xf9'r{E"
b"\xf3\x07?\xcb\xffm\x87\x86&H\xee\xc4\xbc\xf1L\x177\x92\xdb\xf9I\x16Q\xf6\x9ei\xf56z\x0f\xff"
b"^\x92\x88Kh\xbd; \x86\xa5\xbaL\xa2\xda\x93\xae\x10\xd1Y\xa5\xa7\xb4)*\xf6\xa1,]\xd1\xe3\\"
b"\xc3l3\xecA\xec&\x145\xe1\xc4\xd0\x15y\xb2\xf8\x0c\x0e\xd3_[\x1f\x0fM\x98\xa8J\xb3\xd9?\xa4"
b"\xb3\x16\xee8\xad/\x07\xea\x7f\xad\x1a\x0f\xbe\x06\x94\xa51\xf6@\xae\xcdy\x92B\x1c\xd5\x04z"
b"\xbf\xe9\xbc\x9c\xac\x99W6\x81\xad\xe0\x81\xb4\x89n\xd0_\x1c\x92\xbe\xf6\x1cmn\xe92_\x86\xcf"
b"\xb0v\x1f\x9dk%\xbd<\x0c\x1e\x91\x0c\xec\\\xdc\x8cCu\xd8N\xf2\x82E\x00\xc8rnSY\x1b\xa0%\x13"
b"\xc0$Q+\xd3\xd0\xd8 \x0c\xe9\xafI5&\xe5\xc1!\xaf",
# 0x65
b'\x07eGPG\x00\x01\x01',
]
]
}
@pytest.mark.parametrize('sigsubpacket', sig_subpkts)
def test_load(self, sigsubpacket):
spb = sigsubpacket[:]
sp = Signature(spb)
spb = sigsubpacket[:]
sp = Signature(spb)
assert spb == b'\xca\xfe\xba\xbe'
assert len(sp) == len(sigsubpacket) - 4
assert len(sp) == len(sp.__bytes__())
assert sp.__bytes__() == bytes(sigsubpacket[:-4])
assert spb == _trailer
assert len(sp) == len(sigsubpacket) - len(_trailer)
assert len(sp) == len(sp.__bytes__())
assert sp.__bytes__() == bytes(sigsubpacket[:-len(_trailer)])
if sp.header.typeid in _sspclasses:
assert sp.__class__.__name__ == _sspclasses[sp.header.typeid]
if sp.header.typeid in _sspclasses:
assert sp.__class__.__name__ == _sspclasses[sp.header.typeid]
else:
assert isinstance(sp, OpaqueSP)
else:
assert isinstance(sp, OpaqueSP)
_uassps = [
# 0x01
b'\xc3\xfd\x01\x10\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xd8\xff'
b'\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00\x84\x00\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\xff\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\x11\x08\x00x'
b'\x00x\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x01\xa2\x00\x00\x01\x05\x01\x01\x01'
b'\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x10'
b'\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01}\x01\x02\x03\x00\x04\x11\x05'
b'\x12!1A\x06\x13Qa\x07"q\x142\x81\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\t\n\x16\x17'
b'\x18\x19\x1a%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85\x86\x87\x88\x89\x8a'
b'\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5'
b'\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9'
b'\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\x01'
b'\x00\x03\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05'
b'\x06\x07\x08\t\n\x0b\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04\x00\x01\x02w\x00'
b'\x01\x02\x03\x11\x04\x05!1\x06\x12AQ\x07aq\x13"2\x81\x08\x14B\x91\xa1\xb1\xc1\t#3R\xf0\x15'
b'br\xd1\n\x16$4\xe1%\xf1\x17\x18\x19\x1a&\'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x82'
b'\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6'
b'\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca'
b'\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5'
b'\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x92\x80\n\x00('
b'\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n'
b'\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80'
b'\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02'
b'\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0'
b'\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x04\xc8\x1dH\xa0\x03'
b'#\xd4~t\x00\xb4\x00P\x02d\x0e\xa6\x80\x0c\x83\xd0\x8a\x00Z\x00:P\x02dz\x8f\xccP\x02\xd0'
b'\x01@\x05\x00\x14\x00P\x01@\x11cs\x1c\xfb\xd0\x03\xb6\x0fS@\r\x19V\xc7j\x00{\x1c\x0f~\xd4'
b'\x00\xc0\xb9\xe4\x9e\xb4\x00\xa5;\x8c\xd0\x02\xa1\xc8\xe7\xb5\x005\xb9`>\x9f\xce\x80\x1d'
b'\xb0z\x9a\x00i\x05\x0eA\xa0\t\x01\xc8\x07\xd6\x80\x16\x80\n\x00(\x00\xa0\x08\x81\x01\x89>'
b'\xff\x00\xce\x80\x1d\xbdh\x01\xbfy\xb3\xd8P\x00\xfdG\xd2\x80%\xa0\x06\xeeQ\xde\x80\x14\x10'
b'zP\x04m\xf7\x87\xe1\xfc\xe8\x01\xfb\xd6\x80\x18\xc7v\x00\xa0\x05q\xf2\x8fj\x00z\x9c\x80h'
b'\x01\x87\xe6\x7f\xa7\xf4\xa0\t(\x00\xa0\x08\x80\x05\x8e}\xff\x00\x9d\x00I\xb5}\x05\x00/N'
b'\x94\x01\x1b\x8e\x87\xf0\xa0\x07\x83\x91\x9a\x00B\xab\xd4\x8a\x00ju4\x00\x8d\xf7\x87\xe1'
b'\xfc\xe8\x02M\xab\xe8(\x00\x00\x0e\x82\x80\x14\xf21@\x11)\xdb\xb8\x1f\xf2h\x01\xc8:\x9fZ'
b'\x00}\x00\x14\x00\xc0\xa41=\xb9\xfdh\x01\xf4\x00P\x02\x11\x9e\r\x003k\x0e\x86\x80\x0c9\xeb'
b'\xd3\xfc\xfaP\x02\xa8 \x9c\xd0\x00T\x96\x07\xb7\x1f\xa5\x00>\x80\n\x00(\x02\'\x1f7\x1d\xe8'
b'\x02@01@\x0b@\x05\x00\x14\x00P\x01@\x05\x00\x14\x00P\x01@\x05\x00\x14\x00P\x01@\t\x81\x9c'
b'\xf7\xa0\x05\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02'
b'\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0'
b'\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00'
b'\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00('
b'\x00\xa0\x02\x80?\xff\xd9',
]
ua_subpkts = [bytearray(sp) + _trailer for sp in _uassps]
class TestUserAttributeSubPackets(object):
params = {
'uasubpacket': [ bytearray(sp) + b'\xca\xfe\xba\xbe' for sp in
[ # 0x01
b'\xc3\xfd\x01\x10\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xd8\xff'
b'\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00\x84\x00\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\xff\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\x11\x08\x00x'
b'\x00x\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x01\xa2\x00\x00\x01\x05\x01\x01\x01'
b'\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x10'
b'\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01}\x01\x02\x03\x00\x04\x11\x05'
b'\x12!1A\x06\x13Qa\x07"q\x142\x81\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\t\n\x16\x17'
b'\x18\x19\x1a%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85\x86\x87\x88\x89\x8a'
b'\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5'
b'\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9'
b'\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\x01'
b'\x00\x03\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05'
b'\x06\x07\x08\t\n\x0b\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04\x00\x01\x02w\x00'
b'\x01\x02\x03\x11\x04\x05!1\x06\x12AQ\x07aq\x13"2\x81\x08\x14B\x91\xa1\xb1\xc1\t#3R\xf0\x15'
b'br\xd1\n\x16$4\xe1%\xf1\x17\x18\x19\x1a&\'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x82'
b'\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6'
b'\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca'
b'\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5'
b'\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x92\x80\n\x00('
b'\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n'
b'\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80'
b'\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02'
b'\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0'
b'\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x04\xc8\x1dH\xa0\x03'
b'#\xd4~t\x00\xb4\x00P\x02d\x0e\xa6\x80\x0c\x83\xd0\x8a\x00Z\x00:P\x02dz\x8f\xccP\x02\xd0'
b'\x01@\x05\x00\x14\x00P\x01@\x11cs\x1c\xfb\xd0\x03\xb6\x0fS@\r\x19V\xc7j\x00{\x1c\x0f~\xd4'
b'\x00\xc0\xb9\xe4\x9e\xb4\x00\xa5;\x8c\xd0\x02\xa1\xc8\xe7\xb5\x005\xb9`>\x9f\xce\x80\x1d'
b'\xb0z\x9a\x00i\x05\x0eA\xa0\t\x01\xc8\x07\xd6\x80\x16\x80\n\x00(\x00\xa0\x08\x81\x01\x89>'
b'\xff\x00\xce\x80\x1d\xbdh\x01\xbfy\xb3\xd8P\x00\xfdG\xd2\x80%\xa0\x06\xeeQ\xde\x80\x14\x10'
b'zP\x04m\xf7\x87\xe1\xfc\xe8\x01\xfb\xd6\x80\x18\xc7v\x00\xa0\x05q\xf2\x8fj\x00z\x9c\x80h'
b'\x01\x87\xe6\x7f\xa7\xf4\xa0\t(\x00\xa0\x08\x80\x05\x8e}\xff\x00\x9d\x00I\xb5}\x05\x00/N'
b'\x94\x01\x1b\x8e\x87\xf0\xa0\x07\x83\x91\x9a\x00B\xab\xd4\x8a\x00ju4\x00\x8d\xf7\x87\xe1'
b'\xfc\xe8\x02M\xab\xe8(\x00\x00\x0e\x82\x80\x14\xf21@\x11)\xdb\xb8\x1f\xf2h\x01\xc8:\x9fZ'
b'\x00}\x00\x14\x00\xc0\xa41=\xb9\xfdh\x01\xf4\x00P\x02\x11\x9e\r\x003k\x0e\x86\x80\x0c9\xeb'
b'\xd3\xfc\xfaP\x02\xa8 \x9c\xd0\x00T\x96\x07\xb7\x1f\xa5\x00>\x80\n\x00(\x02\'\x1f7\x1d\xe8'
b'\x02@01@\x0b@\x05\x00\x14\x00P\x01@\x05\x00\x14\x00P\x01@\x05\x00\x14\x00P\x01@\t\x81\x9c'
b'\xf7\xa0\x05\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02'
b'\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0'
b'\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00'
b'\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00(\x00\xa0\x02\x80\n\x00('
b'\x00\xa0\x02\x80?\xff\xd9'
]
],
}
@pytest.mark.parametrize('uasubpacket', ua_subpkts)
def test_load(self, uasubpacket):
spb = uasubpacket[:]
sp = UserAttribute(spb)
assert spb == b'\xca\xfe\xba\xbe'
assert len(sp) == len(uasubpacket) - 4
assert spb == _trailer
assert len(sp) == len(uasubpacket) - len(_trailer)
assert len(sp) == len(sp.__bytes__())
assert sp.__bytes__() == uasubpacket[:-4]
assert sp.__bytes__() == uasubpacket[:-len(_trailer)]
if sp.header.typeid in _uaspclasses:
assert sp.__class__.__name__ == _uaspclasses[sp.header.typeid]
@@ -269,31 +279,32 @@ class TestUserAttributeSubPackets(object):
assert isinstance(sp, OpaqueSP)
_s2k_parts = [
# usage byte is always \xff
b'\xff',
# symmetric cipher algorithm list
b'\x01\x02\x03\x04\x07\x08\x09\x0B\x0C\x0D',
# specifier
# b'\x00', (simple)
# b'\x01', (iterated)
# b'\x03', (salted)
# hash algorithm list
b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B',
]
_iv = b'\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF'
_salt = b'\xC0\xDE\xC0\xDE\xC0\xDE\xC0\xDE'
_count = b'\x10' # expands from 0x10 to 2048
# simple S2Ks
sis2ks = [bytearray(i) + _iv for i in itertools.product(*(_s2k_parts[:2] + [b'\x00'] + _s2k_parts[2:]))]
# salted S2Ks
sas2ks = [bytearray(i) + _salt + _iv for i in itertools.product(*(_s2k_parts[:2] + [b'\x01'] + _s2k_parts[2:]))]
# iterated S2Ks
is2ks = [bytearray(i) + _salt + _count + _iv for i in itertools.product(*(_s2k_parts[:2] + [b'\x03'] + _s2k_parts[2:]))]
class TestString2Key(object):
params = {'sis2k': [ (bytearray(i) +
b'\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF') # iv
for i in product(b'\xff', # usage
b'\x01\x02\x03\x04\x07\x08\x09\x0B\x0C\x0D', # symmetric cipher algorithm
b'\x00', # specifier (simple)
b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B') # hash algorithm
],
'sas2k': [ (bytearray(i) +
b'\xCA\xFE\xBA\xBE\xCA\xFE\xBA\xBE' + # salt
b'\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF') # iv
for i in product(b'\xff', # usage
b'\x01\x02\x03\x04\x07\x08\x09\x0B\x0C\x0D', # symmetric cipher algorithm
b'\x01', # specifier (simple)
b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B') # hash algorithm
],
'is2k': [ (bytearray(i) +
b'\xCA\xFE\xBA\xBE\xCA\xFE\xBA\xBE' + # salt
b'\x10' + # count
b'\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF') # iv
for i in product(b'\xff', # usage
b'\x01\x02\x03\x04\x07\x08\x09\x0B\x0C\x0D', # symmetric cipher algorithm
b'\x03', # specifier (simple)
b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B') # hash algorithm
]}
@pytest.mark.parametrize('sis2k', sis2ks)
def test_simple_string2key(self, sis2k):
b = sis2k[:]
s = String2Key()
@@ -307,9 +318,9 @@ class TestString2Key(object):
assert s.halg in HashAlgorithm
assert s.encalg in SymmetricKeyAlgorithm
assert s.specifier == String2KeyType.Simple
assert s.iv == b'\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF'
assert s.iv == _iv
@pytest.mark.parametrize('sas2k', sas2ks)
def test_salted_string2key(self, sas2k):
b = sas2k[:]
s = String2Key()
@@ -323,9 +334,10 @@ class TestString2Key(object):
assert s.halg in HashAlgorithm
assert s.encalg in SymmetricKeyAlgorithm
assert s.specifier == String2KeyType.Salted
assert s.salt == b'\xCA\xFE\xBA\xBE\xCA\xFE\xBA\xBE'
assert s.iv == b'\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF'
assert s.salt == _salt
assert s.iv == _iv
@pytest.mark.parametrize('is2k', is2ks)
def test_iterated_string2key(self, is2k):
b = is2k[:]
s = String2Key()
@@ -339,20 +351,6 @@ class TestString2Key(object):
assert s.halg in HashAlgorithm
assert s.encalg in SymmetricKeyAlgorithm
assert s.specifier == String2KeyType.Iterated
assert s.salt == b'\xCA\xFE\xBA\xBE\xCA\xFE\xBA\xBE'
assert s.salt == _salt
assert s.count == 2048
assert s.iv == b'\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF'
# TODO: this
# class TestKeyMaterial(object):
# params = {
# 'pkt': [],
# }
#
# ids = {
# 'test_keymaterial': [],
# }
#
# def test_keymaterial(self, pkt):
# pass
assert s.iv == _iv

View File

@@ -3,22 +3,7 @@
"""
import pytest
import glob
from pgpy.types import Armorable, PGPObject
# read txt files in tests/testdata/text/*.txt and yield ids and strings
# TODO: figure out how to set ids
# @pytest.yield_fixture
def get_text():
for tf in sorted(glob.glob('tests/testdata/text/*.txt')):
with open(tf, 'r') as f:
for line in f:
# skip comments and blank lines
if line.startswith('#') or line == "":
continue
yield line.split(': ')
from pgpy.types import PGPObject
text = {
# some basic utf-8 test strings - these should all pass
@@ -62,22 +47,15 @@ class FakePGPObject(PGPObject):
class TestPGPObject(object):
params = {
'text': [ v for _, v in sorted(text.items()) ],
'encoded_text': [ v for _, v in sorted(encoded_text.items()) ],
}
ids = {
'test_text_to_bytes': [ k for k, _ in sorted(text.items()) ],
'test_text_to_bytes_encodings': [ k for k, _ in sorted(encoded_text.items()) ],
}
@pytest.mark.regression(issue=154)
@pytest.mark.parametrize('text', [v for _, v in sorted(text.items())], ids=sorted(text.keys()))
def test_text_to_bytes(self, text):
pgpo = FakePGPObject.new(text)
assert pgpo.__bytearray__() == bytearray(b'_fake_') + bytearray(text, 'utf-8')
@pytest.mark.regression(issue=154)
@pytest.mark.parametrize('encoded_text', [v for _, v in sorted(encoded_text.items())], ids=sorted(encoded_text.keys()))
def test_text_to_bytes_encodings(self, encoded_text):
pgpo = FakePGPObject.new(encoded_text)
# this should fail

View File

@@ -5,14 +5,13 @@ import pytest
import glob
import os
from pgpy.packet import Packet
from pgpy.packet import PubKeyV4, PubSubKeyV4, PrivKeyV4, PrivSubKeyV4
from pgpy.packet import Opaque
import pgpy.packet.fields
# import pgpy.packet.fields
_trailer = b'\xde\xca\xff\xba\xdd'
_pclasses = {
(0x01, 3): 'PKESessionKeyV3',
(0x02, 4): 'SignatureV4',
@@ -36,39 +35,35 @@ _pclasses = {
def binload(f):
with open(f, 'rb') as ff:
return bytearray(ff.read())
buf = bytearray(os.fstat(ff.fileno()).st_size)
ff.readinto(buf)
return buf
skip_files = {'tests/testdata/packets/{:s}'.format(pkt) for pkt in ['11.literal.partial']}
skip_files = {'tests/testdata/packets/{:s}'.format(pkt) for pkt in ['11.partial.literal']}
pktfiles = sorted(glob.glob('tests/testdata/packets/[0-9]*'))
class TestPacket(object):
params = {
# 'packet': sorted([f for f in glob.glob('tests/testdata/packets/[0-9]*') if f not in skip_files])
'packet': sorted([f for f in glob.glob('tests/testdata/packets/[0-9]*')])
}
ids = {
'test_load': sorted([os.path.basename(f).replace('.', '_') for f in glob.glob('tests/testdata/packets/[0-9]*')])
}
@pytest.mark.parametrize('packet', pktfiles, ids=[os.path.basename(f) for f in pktfiles])
def test_load(self, packet):
if packet in skip_files:
pytest.skip("not implemented yet")
b = binload(packet) + b'\xca\xfe\xba\xbe'
b = binload(packet) + _trailer
_b = b[:]
p = Packet(_b)
# parsed all bytes
assert _b == b'\xca\xfe\xba\xbe'
assert _b == _trailer
# length is computed correctly
assert p.header.length + len(p.header) == len(p)
assert len(p) == len(b) - 4
assert len(p.__bytes__()) == len(b) - 4
assert len(p) == len(b) - len(_trailer)
assert len(p.__bytes__()) == len(b) - len(_trailer)
# __bytes__ output is correct
assert p.__bytes__() == b[:-4]
assert p.__bytes__() == b[:-len(_trailer)]
# instantiated class is what we expected
if hasattr(p.header, 'version') and (p.header.tag, p.header.version) in _pclasses:

View File

@@ -4,227 +4,301 @@ import pytest
import glob
import os
from datetime import datetime
from pgpy.constants import CompressionAlgorithm
from pgpy.constants import HashAlgorithm
from pgpy.constants import KeyFlags
from pgpy.constants import PubKeyAlgorithm
from pgpy.constants import SignatureType
from pgpy.constants import SymmetricKeyAlgorithm
from pgpy.pgp import PGPKey
from pgpy.pgp import PGPMessage
from pgpy.pgp import PGPSignature
blocks = sorted(glob.glob('tests/testdata/blocks/*.asc'))
block_attrs = {
'tests/testdata/blocks/message.ascii.asc':
[('encrypters', set()),
('filename', 'ascii'),
('is_compressed', False),
('is_encrypted', False),
('is_signed', False),
('issuers', set()),
('message', "This is stored, textually!\r\n"),
('signers', set()),
('type', 'literal'),],
'tests/testdata/blocks/message.compressed.asc':
[('encrypters', set()),
('filename', 'lit'),
('is_compressed', True),
('is_encrypted', False),
('is_signed', False),
('issuers', set()),
('message', b"This is stored, literally\\!\n\n"),
('signers', set()),
('type', 'literal'),],
'tests/testdata/blocks/message.literal.asc':
[('encrypters', set()),
('filename', 'lit'),
('is_compressed', False),
('is_encrypted', False),
('is_signed', False),
('issuers', set()),
('message', b"This is stored, literally\\!\n\n"),
('signers', set()),
('type', 'literal'),],
'tests/testdata/blocks/message.onepass.asc':
[('encrypters', set()),
('filename', 'lit'),
('is_compressed', False),
('is_encrypted', False),
('is_signed', True),
('issuers', {'2A834D8E5918E886'}),
('message', b"This is stored, literally\\!\n\n"),
('signers', {'2A834D8E5918E886'}),
('type', 'literal'),],
'tests/testdata/blocks/message.two_onepass.asc':
[('encrypters', set()),
('filename', 'lit'),
('is_compressed', False),
('is_encrypted', False),
('is_signed', True),
('issuers', {'2A834D8E5918E886', 'A5DCDC966453140E'}),
('message', b"This is stored, literally\\!\n\n"),
('signers', {'2A834D8E5918E886', 'A5DCDC966453140E'}),
('type', 'literal'),],
'tests/testdata/blocks/message.signed.asc':
[('encrypters', set()),
('filename', 'lit'),
('is_compressed', False),
('is_encrypted', False),
('is_signed', True),
('issuers', {'2A834D8E5918E886'}),
('message', b"This is stored, literally\\!\n\n"),
('signers', {'2A834D8E5918E886'}),
('type', 'literal'),],
'tests/testdata/blocks/cleartext.asc':
[('encrypters', set()),
('is_compressed', False),
('is_encrypted', False),
('is_signed', True),
('issuers', {'2A834D8E5918E886'}),
('message', "This is stored, literally\\!\n"),
('signers', {'2A834D8E5918E886'}),
('type', 'cleartext'),],
'tests/testdata/blocks/cleartext.twosigs.asc':
[('encrypters', set()),
('is_compressed', False),
('is_encrypted', False),
('is_signed', True),
('issuers', {'2A834D8E5918E886', 'A5DCDC966453140E'}),
('message', "This is stored, literally\\!\n"),
('signers', {'2A834D8E5918E886', 'A5DCDC966453140E'}),
('type', 'cleartext'),],
'tests/testdata/blocks/message.encrypted.asc':
[('encrypters', {'EEE097A017B979CA'}),
('is_compressed', False),
('is_encrypted', True),
('is_signed', False),
('issuers', {'EEE097A017B979CA'}),
('signers', set()),
('type', 'encrypted')],
'tests/testdata/blocks/message.encrypted.signed.asc':
[('encrypters', {'EEE097A017B979CA'}),
('is_compressed', False),
('is_encrypted', True),
('is_signed', False),
('issuers', {'EEE097A017B979CA'}),
('signers', set()),
('type', 'encrypted')],
'tests/testdata/blocks/message.ecc.encrypted.asc':
[('encrypters', {'77CEB7A34089AB73'}),
('is_compressed', False),
('is_encrypted', True),
('is_signed', False),
('issuers', {'77CEB7A34089AB73'}),
('signers', set()),
('type', 'encrypted')],
'tests/testdata/blocks/revochiio.asc':
[('created', datetime(2014, 9, 11, 22, 55, 53)),
('fingerprint', "AE15 9FF3 4C1A 2426 B7F8 0F1A 560C F308 EF60 CFA3"),
('expires_at', datetime(2018, 9, 12, 1, 0, 59)),
('is_expired', False),
('is_primary', True),
('is_protected', False),
('is_public', True),
('is_unlocked', True),
('key_algorithm', PubKeyAlgorithm.RSAEncryptOrSign),
('magic', "PUBLIC KEY BLOCK"),
('parent', None),
('signers', {'560CF308EF60CFA3'}),],
'tests/testdata/blocks/expyro.asc':
[('created', datetime(1970, 1, 1)),
('expires_at', datetime(1970, 1, 2)),
('fingerprint', '24EB C1B0 29B1 FCF8 29A5 C150 1A48 291A FB91 A533'),
('is_expired', True),],
'tests/testdata/blocks/rsapubkey.asc':
[('created', datetime(2014, 7, 23, 21, 19, 24)),
('expires_at', None),
('fingerprint', "F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36"),
('is_expired', False),
('is_primary', True),
('is_protected', False),
('is_public', True),
('is_unlocked', True),
('key_algorithm', PubKeyAlgorithm.RSAEncryptOrSign),
('magic', "PUBLIC KEY BLOCK"),
('parent', None),
('signers', set()),],
'tests/testdata/blocks/rsaseckey.asc':
[('created', datetime(2014, 7, 23, 21, 19, 24)),
('expires_at', None),
('fingerprint', "F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36"),
('is_expired', False),
('is_primary', True),
('is_protected', False),
('is_public', False),
('is_unlocked', True),
('key_algorithm', PubKeyAlgorithm.RSAEncryptOrSign),
('magic', "PRIVATE KEY BLOCK"),
('parent', None),
('signers', set()),],
'tests/testdata/blocks/rsasignature.asc':
[('__sig__', b'\x70\x38\x79\xd0\x58\x70\x58\x7b\x50\xe6\xab\x8f\x9d\xc3\x46\x2c\x5a\x6b\x98\x96\xcf'
b'\x3b\xa3\x79\x13\x08\x6d\x90\x9d\x67\xd2\x48\x7d\xd7\x1a\xa5\x98\xa7\x8f\xca\xe3\x24'
b'\xd4\x19\xab\xe5\x45\xc5\xff\x21\x0c\x72\x88\x91\xe6\x67\xd7\xe5\x00\xb3\xf5\x55\x0b'
b'\xd0\xaf\x77\xb3\x7e\xa4\x79\x59\x06\xa2\x05\x44\x9d\xd2\xa9\xcf\xb1\xf8\x03\xc1\x90'
b'\x81\x87\x36\x1a\xa6\x5c\x79\x98\xfe\xdb\xdd\x23\x54\x69\x92\x2f\x0b\xc4\xee\x2a\x61'
b'\x77\x35\x59\x6e\xb2\xe2\x1b\x80\x61\xaf\x2d\x7a\x64\x38\xfe\xe3\x95\xcc\xe8\xa4\x05'
b'\x55\x5d'),
('cipherprefs', []),
('compprefs', []),
('created', datetime.utcfromtimestamp(1402615373)),
('embedded', False),
('exportable', True),
('features', set()),
('hash2', b'\xc4\x24'),
('hashprefs', []),
('hash_algorithm', HashAlgorithm.SHA512),
('is_expired', False),
('key_algorithm', PubKeyAlgorithm.RSAEncryptOrSign),
('key_flags', set()),
('keyserver', ''),
('keyserverprefs', []),
('magic', "SIGNATURE"),
('notation', {}),
('policy_uri', ''),
('revocable', True),
('revocation_key', None),
('signer', 'FCAE54F74BA27CF7'),
('type', SignatureType.BinaryDocument)],
'tests/testdata/blocks/eccpubkey.asc':
[('created', datetime(2010, 9, 17, 20, 33, 49)),
('expires_at', None),
('fingerprint', "502D 1A53 65D1 C0CA A699 4539 0BA5 2DF0 BAA5 9D9C"),
('is_expired', False),
('is_primary', True),
('is_protected', False),
('is_public', True),
('is_unlocked', True),
('key_algorithm', PubKeyAlgorithm.ECDSA),
('magic', "PUBLIC KEY BLOCK"),
('parent', None),
('signers', set()),],
'tests/testdata/blocks/eccseckey.asc':
[('created', datetime(2010, 9, 17, 20, 33, 49)),
('expires_at', None),
('fingerprint', "502D 1A53 65D1 C0CA A699 4539 0BA5 2DF0 BAA5 9D9C"),
('is_expired', False),
('is_primary', True),
('is_protected', True),
('is_public', False),
('is_unlocked', False),
('key_algorithm', PubKeyAlgorithm.ECDSA),
('magic', "PRIVATE KEY BLOCK"),
('parent', None),
('signers', set()),],
'tests/testdata/blocks/dsaseckey.asc':
[('created', datetime(2017, 2, 21, 19, 21, 41)),
('expires_at', None),
('fingerprint', "2B5B BB14 3BA0 B290 DCEE 6668 B798 AE89 9087 7201"),
('is_expired', False),
('is_primary', True),
('is_protected', True),
('is_public', False),
('is_unlocked', False),
('key_algorithm', PubKeyAlgorithm.DSA),],
'tests/testdata/blocks/dsapubkey.asc':
[('created', datetime(2017, 2, 21, 19, 21, 41)),
('expires_at', None),
('fingerprint', "2B5B BB14 3BA0 B290 DCEE 6668 B798 AE89 9087 7201"),
('is_expired', False),
('is_primary', True),
('is_protected', False),
('is_public', True),
('is_unlocked', True),
('key_algorithm', PubKeyAlgorithm.DSA),],
'tests/testdata/blocks/openpgp.js.pubkey.asc':
[('created', datetime(2016, 6, 2, 21, 57, 13)),
('expires_at', None),
('fingerprint', "C7C3 8ECE E94A 4AD3 2DDB 064E 14AB 44C7 4D1B DAB8"),
('is_expired', False),
('is_primary', True),
('is_protected', False),
('is_public', True),
('is_unlocked', True),
('key_algorithm', PubKeyAlgorithm.RSAEncryptOrSign),
('magic', "PUBLIC KEY BLOCK"),
('parent', None),
('signers', set()), ],
'tests/testdata/blocks/openpgp.js.seckey.asc':
[('created', datetime(2016, 6, 2, 21, 57, 13)),
('expires_at', None),
('fingerprint', "C7C3 8ECE E94A 4AD3 2DDB 064E 14AB 44C7 4D1B DAB8"),
('is_expired', False),
('is_primary', True),
('is_protected', False),
('is_public', False),
('is_unlocked', True),
('key_algorithm', PubKeyAlgorithm.RSAEncryptOrSign),
('magic', "PRIVATE KEY BLOCK"),
('parent', None),
('signers', set()), ],
'tests/testdata/blocks/signature.expired.asc':
[('created', datetime(2014, 9, 28, 20, 54, 42)),
('is_expired', True),],
'tests/testdata/blocks/signature.non-exportable.asc':
[('created', datetime(2017, 2, 21, 20, 43, 34)),
('exportable', False),]
}
# generic block tests
class TestBlocks(object):
params = {
'block': sorted(glob.glob('tests/testdata/blocks/*.asc'))
}
ids = {
'test_load': [ os.path.basename(fn).replace('.', '_') for fn in sorted(glob.glob('tests/testdata/blocks/*.asc')) ]
}
attrs = {
'tests/testdata/blocks/message.compressed.asc':
[('encrypters', set()),
('filename', 'lit'),
('is_compressed', True),
('is_encrypted', False),
('is_signed', False),
('issuers', set()),
('message', b"This is stored, literally\\!\n\n"),
('signers', set()),
('type', 'literal'),],
'tests/testdata/blocks/message.literal.asc':
[('encrypters', set()),
('filename', 'lit'),
('is_compressed', False),
('is_encrypted', False),
('is_signed', False),
('issuers', set()),
('message', b"This is stored, literally\\!\n\n"),
('signers', set()),
('type', 'literal'),],
'tests/testdata/blocks/message.onepass.asc':
[('encrypters', set()),
('filename', 'lit'),
('is_compressed', False),
('is_encrypted', False),
('is_signed', True),
('issuers', {'2A834D8E5918E886'}),
('message', b"This is stored, literally\\!\n\n"),
('signers', {'2A834D8E5918E886'}),
('type', 'literal'),],
'tests/testdata/blocks/message.two_onepass.asc':
[('encrypters', set()),
('filename', 'lit'),
('is_compressed', False),
('is_encrypted', False),
('is_signed', True),
('issuers', {'2A834D8E5918E886', 'A5DCDC966453140E'}),
('message', b"This is stored, literally\\!\n\n"),
('signers', {'2A834D8E5918E886', 'A5DCDC966453140E'}),
('type', 'literal'),],
'tests/testdata/blocks/message.signed.asc':
[('encrypters', set()),
('filename', 'lit'),
('is_compressed', False),
('is_encrypted', False),
('is_signed', True),
('issuers', {'2A834D8E5918E886'}),
('message', b"This is stored, literally\\!\n\n"),
('signers', {'2A834D8E5918E886'}),
('type', 'literal'),],
'tests/testdata/blocks/cleartext.asc':
[('encrypters', set()),
('is_compressed', False),
('is_encrypted', False),
('is_signed', True),
('issuers', {'2A834D8E5918E886'}),
('message', "This is stored, literally\\!\n"),
('signers', {'2A834D8E5918E886'}),
('type', 'cleartext'),],
'tests/testdata/blocks/cleartext.twosigs.asc':
[('encrypters', set()),
('is_compressed', False),
('is_encrypted', False),
('is_signed', True),
('issuers', {'2A834D8E5918E886', 'A5DCDC966453140E'}),
('message', "This is stored, literally\\!\n"),
('signers', {'2A834D8E5918E886', 'A5DCDC966453140E'}),
('type', 'cleartext'),],
'tests/testdata/blocks/message.encrypted.asc':
[('encrypters', {'EEE097A017B979CA'}),
('is_compressed', False),
('is_encrypted', True),
('is_signed', False),
('issuers', {'EEE097A017B979CA'}),
('signers', set()),
('type', 'encrypted')],
'tests/testdata/blocks/message.encrypted.signed.asc':
[('encrypters', {'EEE097A017B979CA'}),
('is_compressed', False),
('is_encrypted', True),
('is_signed', False),
('issuers', {'EEE097A017B979CA'}),
('signers', set()),
('type', 'encrypted')],
'tests/testdata/blocks/message.ecc.encrypted.asc':
[('encrypters', {'77CEB7A34089AB73'}),
('is_compressed', False),
('is_encrypted', True),
('is_signed', False),
('issuers', {'77CEB7A34089AB73'}),
('signers', set()),
('type', 'encrypted')],
'tests/testdata/blocks/revochiio.asc':
[('created', datetime(2014, 9, 11, 22, 55, 53)),
('fingerprint', "AE15 9FF3 4C1A 2426 B7F8 0F1A 560C F308 EF60 CFA3"),
('expires_at', datetime(2018, 9, 12, 1, 0, 59)),
('is_expired', False),
('is_primary', True),
('is_protected', False),
('is_public', True),
('is_unlocked', True),
('key_algorithm', PubKeyAlgorithm.RSAEncryptOrSign),
('magic', "PUBLIC KEY BLOCK"),
('parent', None),
('signers', {'560CF308EF60CFA3'}),],
'tests/testdata/blocks/expyro.asc':
[('created', datetime(1970, 1, 1)),
('expires_at', datetime(1970, 1, 2)),
('fingerprint', '24EB C1B0 29B1 FCF8 29A5 C150 1A48 291A FB91 A533'),
('is_expired', True),],
'tests/testdata/blocks/rsapubkey.asc':
[('created', datetime(2014, 7, 23, 21, 19, 24)),
('expires_at', None),
('fingerprint', "F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36"),
('is_expired', False),
('is_primary', True),
('is_protected', False),
('is_public', True),
('is_unlocked', True),
('key_algorithm', PubKeyAlgorithm.RSAEncryptOrSign),
('magic', "PUBLIC KEY BLOCK"),
('parent', None),
('signers', set()),],
'tests/testdata/blocks/rsaseckey.asc':
[('created', datetime(2014, 7, 23, 21, 19, 24)),
('expires_at', None),
('fingerprint', "F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36"),
('is_expired', False),
('is_primary', True),
('is_protected', False),
('is_public', False),
('is_unlocked', True),
('key_algorithm', PubKeyAlgorithm.RSAEncryptOrSign),
('magic', "PRIVATE KEY BLOCK"),
('parent', None),
('signers', set()),],
'tests/testdata/blocks/rsasignature.asc':
[('__sig__', b'\x70\x38\x79\xd0\x58\x70\x58\x7b\x50\xe6\xab\x8f\x9d\xc3\x46\x2c\x5a\x6b\x98\x96\xcf'
b'\x3b\xa3\x79\x13\x08\x6d\x90\x9d\x67\xd2\x48\x7d\xd7\x1a\xa5\x98\xa7\x8f\xca\xe3\x24'
b'\xd4\x19\xab\xe5\x45\xc5\xff\x21\x0c\x72\x88\x91\xe6\x67\xd7\xe5\x00\xb3\xf5\x55\x0b'
b'\xd0\xaf\x77\xb3\x7e\xa4\x79\x59\x06\xa2\x05\x44\x9d\xd2\xa9\xcf\xb1\xf8\x03\xc1\x90'
b'\x81\x87\x36\x1a\xa6\x5c\x79\x98\xfe\xdb\xdd\x23\x54\x69\x92\x2f\x0b\xc4\xee\x2a\x61'
b'\x77\x35\x59\x6e\xb2\xe2\x1b\x80\x61\xaf\x2d\x7a\x64\x38\xfe\xe3\x95\xcc\xe8\xa4\x05'
b'\x55\x5d'),
('cipherprefs', []),
('compprefs', []),
('created', datetime.utcfromtimestamp(1402615373)),
('embedded', False),
('exportable', True),
('features', set()),
('hash2', b'\xc4\x24'),
('hashprefs', []),
('hash_algorithm', HashAlgorithm.SHA512),
('is_expired', False),
('key_algorithm', PubKeyAlgorithm.RSAEncryptOrSign),
('key_flags', set()),
('keyserver', ''),
('keyserverprefs', []),
('magic', "SIGNATURE"),
('notation', {}),
('policy_uri', ''),
('revocable', True),
('revocation_key', None),
('signer', 'FCAE54F74BA27CF7'),
('type', SignatureType.BinaryDocument)],
'tests/testdata/blocks/eccpubkey.asc':
[('created', datetime(2010, 9, 17, 20, 33, 49)),
('expires_at', None),
('fingerprint', "502D 1A53 65D1 C0CA A699 4539 0BA5 2DF0 BAA5 9D9C"),
('is_expired', False),
('is_primary', True),
('is_protected', False),
('is_public', True),
('is_unlocked', True),
('key_algorithm', PubKeyAlgorithm.ECDSA),
('magic', "PUBLIC KEY BLOCK"),
('parent', None),
('signers', set()),],
'tests/testdata/blocks/eccseckey.asc':
[('created', datetime(2010, 9, 17, 20, 33, 49)),
('expires_at', None),
('fingerprint', "502D 1A53 65D1 C0CA A699 4539 0BA5 2DF0 BAA5 9D9C"),
('is_expired', False),
('is_primary', True),
('is_protected', True),
('is_public', False),
('is_unlocked', False),
('key_algorithm', PubKeyAlgorithm.ECDSA),
('magic', "PRIVATE KEY BLOCK"),
('parent', None),
('signers', set()),],
'tests/testdata/blocks/signature.expired.asc':
[('created', datetime(2014, 9, 28, 20, 54, 42)),
('is_expired', True),],
}
def test_load(self, block):
@pytest.mark.parametrize('block', blocks, ids=[os.path.basename(f) for f in blocks])
def test_load_blob(self, block):
with open(block) as bf:
bc = bf.read()
@@ -238,7 +312,7 @@ class TestBlocks(object):
p = PGPMessage()
else:
pytest.skip("not ready for this one")
pytest.skip("not ready for file '{}'".format(os.path.basename(block)))
assert False
# load ASCII
@@ -248,8 +322,8 @@ class TestBlocks(object):
# assert str(p) == bc
# now check attrs
assert block in self.attrs
for attr, val in self.attrs[block]:
assert block in block_attrs
for attr, val in block_attrs[block]:
attrval = getattr(p, attr)
# this is probably more helpful than just 'assert attrval == val'
if attrval != val:

View File

@@ -1,14 +1,10 @@
""" test the functionality of PGPKeyring
"""
import pytest
import glob
import os
import six
from pgpy.packet import Packet
from pgpy import PGPKey
from pgpy import PGPKeyring
from pgpy import PGPMessage
@@ -16,25 +12,8 @@ from pgpy import PGPSignature
from pgpy import PGPUID
from pgpy.types import Fingerprint
@pytest.fixture
def un():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'))
from conftest import gpg_ver
@pytest.fixture
def unc():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'), comment=six.u('\u2603'))
@pytest.fixture
def une():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'), email='snowman@not.an.email.addre.ss')
@pytest.fixture
def unce():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'), comment=six.u('\u2603'), email='snowman@not.an.email.addre.ss')
@pytest.fixture
def abe():
return PGPUID.new('Abraham Lincoln', comment='Honest Abe', email='abraham.lincoln@whitehouse.gov')
@pytest.fixture
def abe_image():
@@ -45,14 +24,11 @@ def abe_image():
return PGPUID.new(abebytes)
class TestPGPMessage(object):
params = {
'msgfile': sorted(glob.glob('tests/testdata/messages/*.asc')),
}
ids = {
'test_load_from_file': [ os.path.basename(f) for f in params['msgfile'] ],
}
_msgfiles = sorted(glob.glob('tests/testdata/messages/*.asc'))
class TestPGPMessage(object):
@pytest.mark.parametrize('msgfile', _msgfiles, ids=[os.path.basename(f) for f in _msgfiles])
def test_load_from_file(self, msgfile):
# TODO: figure out a good way to verify that all went well here, because
# PGPy reorders signatures sometimes, and also unwraps compressed messages
@@ -65,6 +41,31 @@ class TestPGPMessage(object):
assert len(str(msg)) == len(mt)
@pytest.fixture
def un():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'))
@pytest.fixture
def unc():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'), comment=six.u('\u2603'))
@pytest.fixture
def une():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'), email='snowman@not.an.email.addre.ss')
@pytest.fixture
def unce():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'), comment=six.u('\u2603'), email='snowman@not.an.email.addre.ss')
@pytest.fixture
def abe():
return PGPUID.new('Abraham Lincoln', comment='Honest Abe', email='abraham.lincoln@whitehouse.gov')
class TestPGPUID(object):
def test_userid(self, abe):
assert abe.name == 'Abraham Lincoln'
@@ -88,36 +89,72 @@ class TestPGPUID(object):
assert six.u("{:s}").format(unce) == six.u('Temperair\xe9e Youx\'seur (\u2603) <snowman@not.an.email.addre.ss>')
_keyfiles = sorted(glob.glob('tests/testdata/blocks/*key*.asc'))
class TestPGPKey(object):
kf = next(iter(sorted(glob.glob('tests/testdata/keys/*.pub.asc'))))
@pytest.mark.parametrize('kf', _keyfiles, ids=[os.path.basename(f) for f in _keyfiles])
def test_load_from_file(self, kf, gpg_keyid_file):
key, _ = PGPKey.from_file(kf)
def test_load_from_file(self, gpg_keyid_file):
key, _ = PGPKey.from_file(self.kf)
# TODO: maybe store the fingerprint instead of relying on a particular version of GnuPG...?
if 'ecc' in kf and gpg_ver < '2.1':
assert key.fingerprint
assert key.fingerprint.keyid in gpg_keyid_file(self.kf.replace('tests/testdata/', ''))
else:
assert key.fingerprint.keyid in gpg_keyid_file(kf.replace('tests/testdata/', ''))
def test_load_from_str(self, gpg_keyid_file):
with open(self.kf, 'r') as tkf:
@pytest.mark.parametrize('kf', _keyfiles, ids=[os.path.basename(f) for f in _keyfiles])
def test_load_from_str(self, kf, gpg_keyid_file):
with open(kf, 'r') as tkf:
key, _ = PGPKey.from_blob(tkf.read())
assert key.fingerprint.keyid in gpg_keyid_file(self.kf.replace('tests/testdata/', ''))
# TODO: maybe store the fingerprint instead of relying on a particular version of GnuPG...?
if 'ecc' in kf and gpg_ver < '2.1':
assert key.fingerprint
else:
assert key.fingerprint.keyid in gpg_keyid_file(kf.replace('tests/testdata/', ''))
@pytest.mark.regression(issue=140)
def test_load_from_bytes(self, gpg_keyid_file):
with open(self.kf, 'rb') as tkf:
@pytest.mark.parametrize('kf', _keyfiles, ids=[os.path.basename(f) for f in _keyfiles])
def test_load_from_bytes(self, kf, gpg_keyid_file):
with open(kf, 'rb') as tkf:
key, _ = PGPKey.from_blob(tkf.read())
assert key.fingerprint.keyid in gpg_keyid_file(self.kf.replace('tests/testdata/', ''))
# TODO: maybe store the fingerprint instead of relying on a particular version of GnuPG...?
if 'ecc' in kf and gpg_ver < '2.1':
assert key.fingerprint
else:
assert key.fingerprint.keyid in gpg_keyid_file(kf.replace('tests/testdata/', ''))
@pytest.mark.regression(issue=140)
def test_load_from_bytearray(self, gpg_keyid_file):
tkb = bytearray(os.stat(self.kf).st_size)
with open(self.kf, 'rb') as tkf:
@pytest.mark.parametrize('kf', _keyfiles, ids=[os.path.basename(f) for f in _keyfiles])
def test_load_from_bytearray(self, kf, gpg_keyid_file):
tkb = bytearray(os.stat(kf).st_size)
with open(kf, 'rb') as tkf:
tkf.readinto(tkb)
key, _ = PGPKey.from_blob(tkb)
assert key.fingerprint.keyid in gpg_keyid_file(self.kf.replace('tests/testdata/', ''))
# TODO: maybe store the fingerprint instead of relying on a particular version of GnuPG...?
if 'ecc' in kf and gpg_ver < '2.1':
assert key.fingerprint
else:
assert key.fingerprint.keyid in gpg_keyid_file(kf.replace('tests/testdata/', ''))
@pytest.mark.parametrize('kf', sorted(filter(lambda f: not f.endswith('enc.asc'), glob.glob('tests/testdata/keys/*.asc'))))
def test_save(self, kf):
# load the key and export it back to binary
key, _ = PGPKey.from_file(kf)
pgpyblob = key.__bytes__()
# try loading the exported key
reloaded, _ = PGPKey.from_file(kf)
assert pgpyblob == reloaded.__bytes__()
@pytest.fixture(scope='module')

View File

@@ -7,7 +7,6 @@ import copy
import glob
import inspect
import os.path
import six
import pgpy
@@ -15,6 +14,10 @@ import pgpy
from pgpy import PGPSignature, PGPUID, PGPMessage, PGPKey
_keys = glob.glob('tests/testdata/keys/*.1.pub.asc') + glob.glob('tests/testdata/keys/*.1.sec.asc')
_msgs = [ 'tests/testdata/messages/message.{}.asc'.format(f) for f in ['signed', 'rsa.cast5.no-mdc', 'rsa.dsa.pass.aes']]
def sig():
return PGPSignature.from_file('tests/testdata/blocks/rsasignature.asc')
@@ -42,65 +45,58 @@ def walk_obj(obj, prefix=""):
yield n, v
_keys = glob.glob('tests/testdata/keys/*.1.pub.asc') + glob.glob('tests/testdata/keys/*.1.sec.asc')
_msgs = [ 'tests/testdata/messages/message.{}.asc'.format(f) for f in ['signed', 'rsa.cast5.no-mdc', 'rsa.dsa.pass.aes']]
def check_id(obj):
from datetime import datetime
from enum import Enum
# do some type checking to determine if we should check the identity of an object member
# these types are singletons
if isinstance(obj, (Enum, bool, type(None))):
return False
# these types are immutable
if isinstance(obj, (six.string_types, datetime)):
return False
# integers are kind of a special case.
# ints that do not exceed sys.maxsize are singletons, and in either case are immutable
# this shouldn't apply to MPIs, though, which are subclasses of int
if isinstance(obj, int) and not isinstance(obj, pgpy.packet.types.MPI):
return False
return True
class TestCopy(object):
params = {
'obj': [sig(), uid()] + [ PGPMessage.from_file(m) for m in _msgs ] + [ key(fn) for fn in _keys ],
}
ids = {
'test_copy_obj': ['sig' , 'uid'] + [ '-'.join(os.path.basename(fn).split('.')[:3]) for fn in _msgs ] + [ '-'.join(os.path.basename(fn).split('.')[:3]) for fn in _keys ],
}
def ksort(key):
# return a tuple of key, key.count('.') so we get a descending alphabetical, ascending depth ordering
return key, key.count('.')
@staticmethod
def check_id(obj):
from datetime import datetime
from enum import Enum
# do some type checking to determine if we should check the identity of an object member
# these types are singletons
if isinstance(obj, (Enum, bool, type(None))):
return False
objs = [sig(), uid(),] + [PGPMessage.from_file(m) for m in _msgs] + [key(f) for f in _keys]
cids = ['sig', 'uid',] + [os.path.basename(m) for m in _msgs] + [os.path.basename(f) for f in _keys]
# these types are immutable
if isinstance(obj, (six.string_types, datetime)):
return False
# integers are kind of a special case.
# ints that do not exceed sys.maxsize are singletons, and in either case are immutable
# this shouldn't apply to MPIs, though, which are subclasses of int
if isinstance(obj, int) and not isinstance(obj, pgpy.packet.types.MPI):
return False
@pytest.mark.parametrize('obj', objs, ids=cids)
def test_copy_obj(request, obj):
obj2 = copy.copy(obj)
return True
objflat = {name: val for name, val in walk_obj(obj, '{}.'.format(request.node.callspec.id))}
obj2flat = {name: val for name, val in walk_obj(obj2, '{}.'.format(request.node.callspec.id))}
@staticmethod
def ksort(key):
# return a tuple of key, key.count('.') so we get a descending alphabetical, ascending depth ordering
return key, key.count('.')
for k in sorted(objflat, key=ksort):
print("checking attribute: {} ".format(k), end="")
if isinstance(objflat[k], pgpy.types.SorteDeque):
print("[SorteDeque] ", end="")
assert len(objflat[k]) == len(obj2flat[k])
def test_copy_obj(self, request, obj):
obj2 = copy.copy(obj)
if not isinstance(objflat[k], (pgpy.types.PGPObject, pgpy.types.SorteDeque)):
print("[{} ]".format(type(objflat[k])), end="")
assert objflat[k] == objflat[k], k
objflat = {name: val for name, val in walk_obj(obj, '{}.'.format(request.node.callspec.id))}
obj2flat = {name: val for name, val in walk_obj(obj2, '{}.'.format(request.node.callspec.id))}
# check identity, but only types that should definitely be copied
if check_id(objflat[k]):
print("[id] {}".format(type(objflat[k])))
assert objflat[k] is not obj2flat[k], "{}: {}".format(type(objflat[k]), k)
for k in sorted(objflat, key=self.ksort):
print("checking attribute: {} ".format(k), end="")
if isinstance(objflat[k], pgpy.types.SorteDeque):
print("[SorteDeque] ", end="")
assert len(objflat[k]) == len(obj2flat[k])
if not isinstance(objflat[k], (pgpy.types.PGPObject, pgpy.types.SorteDeque)):
print("[{} ]".format(type(objflat[k])), end="")
assert objflat[k] == objflat[k], k
# check identity, but only types that should definitely be copied
if self.check_id(objflat[k]):
print("[id] {}".format(type(objflat[k])))
assert objflat[k] is not obj2flat[k], "{}: {}".format(type(objflat[k]), k)
else:
print()
else:
print()

File diff suppressed because it is too large Load Diff

View File

@@ -3,29 +3,24 @@
import pytest
import glob
from pgpy import PGPKey
from pgpy import PGPKeyring
from pgpy import PGPMessage
from pgpy import PGPSignature
from pgpy import PGPUID
from pgpy.packet import Packet
from pgpy.types import Armorable
from pgpy.types import PGPObject
from pgpy.types import Fingerprint
from pgpy.types import SignatureVerification
from pgpy.constants import EllipticCurveOID
from pgpy.constants import HashAlgorithm
from pgpy.constants import KeyFlags
from pgpy.constants import PubKeyAlgorithm
from pgpy.constants import SymmetricKeyAlgorithm
from pgpy.packet import Packet
from pgpy.types import Armorable
from pgpy.types import PGPObject
from pgpy.types import Fingerprint
from pgpy.types import SignatureVerification
from pgpy.errors import PGPError
from pgpy.errors import PGPDecryptionError
from pgpy.errors import PGPEncryptionError
from pgpy.errors import PGPError
from pgpy.errors import PGPInsecureCipher
@@ -63,6 +58,7 @@ def targette_pub():
def temp_subkey():
return PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 512)
@pytest.fixture(scope='module')
def temp_key():
u = PGPUID.new('User')
@@ -85,6 +81,7 @@ key_algs_badsizes = {
PubKeyAlgorithm.ECDSA: [curve for curve in EllipticCurveOID if not curve.can_gen],
PubKeyAlgorithm.ECDH: [curve for curve in EllipticCurveOID if not curve.can_gen],
}
badkeyspec = [ (alg, size) for alg in key_algs_badsizes.keys() for size in key_algs_badsizes[alg] ]
class TestArmorable(object):
@@ -133,7 +130,6 @@ class TestMetaDispatchable(object):
else:
Packet(d)
# ensure the base packet works, first
Packet(data[:])
@@ -150,25 +146,6 @@ class TestMetaDispatchable(object):
class TestPGPKey(object):
params = {
# 'key_alg': key_algs,
'badkey': [ (alg, size) for alg in key_algs_badsizes.keys() for size in key_algs_badsizes[alg] ],
'key_alg_unim': key_algs_unim,
'key_alg_rsa_depr': key_algs_rsa_depr,
}
ids = {
# 'test_new_key_invalid_size': [ str(ka).split('.')[-1] for ka in key_algs ],
'test_new_key_invalid_size': [ '{}-{}'.format(ka.name, ks.name if not isinstance(ks, int) else ks) for ka, kss in key_algs_badsizes.items() for ks in kss],
'test_new_key_unimplemented_alg': [ str(ka).split('.')[-1] for ka in key_algs_unim ],
'test_new_key_deprecated_rsa_alg': [ str(ka).split('.')[-1] for ka in key_algs_rsa_depr ],
}
key_badsize = {
PubKeyAlgorithm.RSAEncryptOrSign: 256,
PubKeyAlgorithm.DSA: 512,
PubKeyAlgorithm.ECDSA: 1,
PubKeyAlgorithm.ECDH: 1,
}
def test_unlock_pubkey(self, rsa_pub, recwarn):
with rsa_pub.unlock("QwertyUiop") as _unlocked:
assert _unlocked is rsa_pub
@@ -304,15 +281,18 @@ class TestPGPKey(object):
with pytest.raises(PGPError):
key.sign('asdf')
@pytest.mark.parametrize('badkey', badkeyspec, ids=['{}:{}'.format(alg.name, size if isinstance(size, int) else size.name) for alg, size in badkeyspec])
def test_new_key_invalid_size(self, badkey):
key_alg, key_size = badkey
with pytest.raises(ValueError):
PGPKey.new(key_alg, key_size)
@pytest.mark.parametrize('key_alg_unim', key_algs_unim, ids=[alg.name for alg in key_algs_unim])
def test_new_key_unimplemented_alg(self, key_alg_unim):
with pytest.raises(NotImplementedError):
PGPKey.new(key_alg_unim, 512)
@pytest.mark.parametrize('key_alg_rsa_depr', key_algs_rsa_depr, ids=[alg.name for alg in key_algs_rsa_depr])
def test_new_key_deprecated_rsa_alg(self, key_alg_rsa_depr, recwarn):
k = PGPKey.new(key_alg_rsa_depr, 512)
@@ -405,9 +385,10 @@ class TestPGPMessage(object):
class TestPGPSignature(object):
def test_or_typeerror(self):
@pytest.mark.parametrize('inp', [12, None])
def test_or_typeerror(self, inp):
with pytest.raises(TypeError):
PGPSignature() | 12
PGPSignature() | inp
def test_parse_wrong_magic(self):
sigtext = _read('tests/testdata/blocks/signature.expired.asc').replace('SIGNATURE', 'SIGANTURE')
@@ -421,12 +402,6 @@ class TestPGPSignature(object):
with pytest.raises(ValueError):
sig.parse(notsigtext)
def test_or_typeerror(self):
sig = PGPSignature()
with pytest.raises(TypeError):
sig |= None
class TestPGPUID(object):
def test_or_typeerror(self):

View File

@@ -1,149 +1,195 @@
""" I've got 99 problems but regression testing ain't one
"""
import pytest
import tempfile
import warnings
class TestRegressions(object):
# regression tests for actions
def test_reg_bug_56(self, write_clean, gpg_import, gpg_verify):
# some imports only used by this regression test
import hashlib
from datetime import datetime
@pytest.mark.regression(issue=56)
def test_reg_bug_56(gpg_import, gpg_verify):
# some imports only used by this regression test
import hashlib
from datetime import datetime
from pgpy.pgp import PGPKey
from pgpy.pgp import PGPSignature
from pgpy.pgp import PGPKey
from pgpy.pgp import PGPSignature
from pgpy.constants import HashAlgorithm
from pgpy.constants import PubKeyAlgorithm
from pgpy.constants import SignatureType
from pgpy.constants import HashAlgorithm
from pgpy.constants import PubKeyAlgorithm
from pgpy.constants import SignatureType
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
# do a regression test on issue #56
# re-create a signature that would have been encoded improperly as with issue #56
# and see if it fails to verify or not
# do a regression test on issue #56
# re-create a signature that would have been encoded improperly as with issue #56
# and see if it fails to verify or not
# this was the old seckeys/TestRSA-2048.key
sec = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" \
"\n" \
"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\n" \
"iz54ZFyNdQ==\n" \
"=WLpc\n" \
"-----END PGP PRIVATE KEY BLOCK-----\n"
# this was the old seckeys/TestRSA-2048.key
sec = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" \
"\n" \
"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\n" \
"iz54ZFyNdQ==\n" \
"=WLpc\n" \
"-----END PGP PRIVATE KEY BLOCK-----\n"
pub = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" \
"\n" \
"mQENBFOaNYoBCAC9FDOrASOxJoo3JhwCCln4wPy+A/UY1H0OYlc+MMolSwev2uDj\n" \
"nnwmt8ziNTjLuLEh3AnKjDWA9xvY5NW2ZkB6Edo6HQkquKLH7AkpLd82kiTWHdOw\n" \
"OH7OWQpz7z2z6e40wwHEduhyGcZ/Ja/A0+6GIb/2YFKlkwfnT92jtB94W//mL6wu\n" \
"LOkZMoU/CS/QatervzAf9VCemvAR9NI0UJc7Y0RC1B/1cBTQAUg70EhjnmJkqyYx\n" \
"yWqaXyfX10dsEX3+MyiP1kvUDfFwhdeL7E2H9sbFE5+MC9Eo99/Qezv3QoXzH2Tj\n" \
"bTun+QMVkbM92dj70KiExAJya9lSLZGCoOrDABEBAAG0M1Rlc3RSU0EtMjA0OCAo\n" \
"VEVTVElORy1VU0UtT05MWSkgPGVtYWlsQGFkZHJlc3MudGxkPokBNwQTAQoAIQUC\n" \
"U5o1igIbIwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRDA8iEODxk9zYQPB/4+\n" \
"kZlIwLE27J7IiZSkk+4T5CPrASxoSsRMadUvoHc0eiZIlQD2Gu05oQcm4kZojJAz\n" \
"Mv12rLtk+ZPwVOZU/TUxPYwuEyJP4keFJEW9P0GiURAvYQRQCbQ5IOlIkZ0tPotb\n" \
"010Ej3u5rHAiVCvh/cxF16UhkXknf/wgDDWErfGIMaaruAIr0G05p4Q2G/NLgBcc\n" \
"owSgFFfWprg3zfNPEQhH/qNs8O5mByniMZk4n2TsKGlX6eT9RrfJVQhSLoQXxYik\n" \
"MtiZTki4yPUhTQev62KWHQcY6zNV2p9VQ24NUhVCIBnZ0CLkm38QFsS5flWVGat5\n" \
"kraHTXxvffz7yGHJiFkiuQENBFOaNYoBCADBPjB83l1O2m/Nr5KDm6/BwKfrRsoJ\n" \
"DmMZ8nNHNUc/zK4RI4EFKkr35PSmgbA8yOlaSDWVz9zuKyOtb8Nohct2/lrac8zI\n" \
"+b4enZ/Z6qehoAdY1t4QYmA2PebKuerBXjIF1RWsPQDpu3GIZw4oBbdu5oUGB4I9\n" \
"yIepindM2b2I9dlY3ct4uhRbBmXPFcslmJ1K4pCurXvr4Po4DCcWqUmsGUQQbI1G\n" \
"UyAzSad7u9y3CRqhHFwzyFRRfl+/mgB2a6XvbGlG5Dkp1g7T/HIVJu+zv58AQkFw\n" \
"+ABuWNKCXa3TB51bkiBQlkRTSAu2tVZ8hVGZE+wUw0o9rLiy6mldFvbLABEBAAGJ\n" \
"AR8EGAEKAAkFAlOaNYoCGwwACgkQwPIhDg8ZPc1uDwf/SGoiZHjUsTWm4gZgZCzA\n" \
"jOpZs7dKjLL8Wm5G3HTFIGX0O8HCzQJARWq05N6EYmI4nPXxu08ba30SubybSeFU\n" \
"+iAPymqm2YNXrE2RwLWko78M0r9enUep6SvbGKnukPG7lz/33PsxIVyATfMmcmzV\n" \
"4chyC7pICTwgHv/zC3S/k7GoS82Z39LO4R4aDa4aubNq6mx4eHUd0MSnYud1IzRx\n" \
"D8cPxh9fCdoW0OpddqKNczAvO4bl5wwDafrEa7HpIX/sMVMZXo2h6TkitdLCdEfk\n" \
"tgEjS0hTsFtfwsXt9TKi1x3HJIbcm8t78ubpWXepB/iNKVzv4punFHhKiz54ZFyN\n" \
"dQ==\n" \
"=lqIH\n" \
"-----END PGP PUBLIC KEY BLOCK-----\n"
pub = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" \
"\n" \
"mQENBFOaNYoBCAC9FDOrASOxJoo3JhwCCln4wPy+A/UY1H0OYlc+MMolSwev2uDj\n" \
"nnwmt8ziNTjLuLEh3AnKjDWA9xvY5NW2ZkB6Edo6HQkquKLH7AkpLd82kiTWHdOw\n" \
"OH7OWQpz7z2z6e40wwHEduhyGcZ/Ja/A0+6GIb/2YFKlkwfnT92jtB94W//mL6wu\n" \
"LOkZMoU/CS/QatervzAf9VCemvAR9NI0UJc7Y0RC1B/1cBTQAUg70EhjnmJkqyYx\n" \
"yWqaXyfX10dsEX3+MyiP1kvUDfFwhdeL7E2H9sbFE5+MC9Eo99/Qezv3QoXzH2Tj\n" \
"bTun+QMVkbM92dj70KiExAJya9lSLZGCoOrDABEBAAG0M1Rlc3RSU0EtMjA0OCAo\n" \
"VEVTVElORy1VU0UtT05MWSkgPGVtYWlsQGFkZHJlc3MudGxkPokBNwQTAQoAIQUC\n" \
"U5o1igIbIwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRDA8iEODxk9zYQPB/4+\n" \
"kZlIwLE27J7IiZSkk+4T5CPrASxoSsRMadUvoHc0eiZIlQD2Gu05oQcm4kZojJAz\n" \
"Mv12rLtk+ZPwVOZU/TUxPYwuEyJP4keFJEW9P0GiURAvYQRQCbQ5IOlIkZ0tPotb\n" \
"010Ej3u5rHAiVCvh/cxF16UhkXknf/wgDDWErfGIMaaruAIr0G05p4Q2G/NLgBcc\n" \
"owSgFFfWprg3zfNPEQhH/qNs8O5mByniMZk4n2TsKGlX6eT9RrfJVQhSLoQXxYik\n" \
"MtiZTki4yPUhTQev62KWHQcY6zNV2p9VQ24NUhVCIBnZ0CLkm38QFsS5flWVGat5\n" \
"kraHTXxvffz7yGHJiFkiuQENBFOaNYoBCADBPjB83l1O2m/Nr5KDm6/BwKfrRsoJ\n" \
"DmMZ8nNHNUc/zK4RI4EFKkr35PSmgbA8yOlaSDWVz9zuKyOtb8Nohct2/lrac8zI\n" \
"+b4enZ/Z6qehoAdY1t4QYmA2PebKuerBXjIF1RWsPQDpu3GIZw4oBbdu5oUGB4I9\n" \
"yIepindM2b2I9dlY3ct4uhRbBmXPFcslmJ1K4pCurXvr4Po4DCcWqUmsGUQQbI1G\n" \
"UyAzSad7u9y3CRqhHFwzyFRRfl+/mgB2a6XvbGlG5Dkp1g7T/HIVJu+zv58AQkFw\n" \
"+ABuWNKCXa3TB51bkiBQlkRTSAu2tVZ8hVGZE+wUw0o9rLiy6mldFvbLABEBAAGJ\n" \
"AR8EGAEKAAkFAlOaNYoCGwwACgkQwPIhDg8ZPc1uDwf/SGoiZHjUsTWm4gZgZCzA\n" \
"jOpZs7dKjLL8Wm5G3HTFIGX0O8HCzQJARWq05N6EYmI4nPXxu08ba30SubybSeFU\n" \
"+iAPymqm2YNXrE2RwLWko78M0r9enUep6SvbGKnukPG7lz/33PsxIVyATfMmcmzV\n" \
"4chyC7pICTwgHv/zC3S/k7GoS82Z39LO4R4aDa4aubNq6mx4eHUd0MSnYud1IzRx\n" \
"D8cPxh9fCdoW0OpddqKNczAvO4bl5wwDafrEa7HpIX/sMVMZXo2h6TkitdLCdEfk\n" \
"tgEjS0hTsFtfwsXt9TKi1x3HJIbcm8t78ubpWXepB/iNKVzv4punFHhKiz54ZFyN\n" \
"dQ==\n" \
"=lqIH\n" \
"-----END PGP PUBLIC KEY BLOCK-----\n"
# load the keypair above
sk = PGPKey()
sk.parse(sec)
pk = PGPKey()
pk.parse(pub)
# load the keypair above
sk = PGPKey()
sk.parse(sec)
pk = PGPKey()
pk.parse(pub)
sigsubject = bytearray(b"Hello!I'm a test document.I'm going to get signed a bunch of times.KBYE!")
sigsubject = bytearray(b"Hello!I'm a test document.I'm going to get signed a bunch of times.KBYE!")
sig = PGPSignature.new(SignatureType.BinaryDocument, PubKeyAlgorithm.RSAEncryptOrSign, HashAlgorithm.SHA512,
sk.fingerprint.keyid)
sig._signature.subpackets['h_CreationTime'][-1].created = datetime(2014, 8, 6, 23, 28, 51)
sig._signature.subpackets.update_hlen()
hdata = sig.hashdata(sigsubject)
sig._signature.hash2 = hashlib.new('sha512', hdata).digest()[:2]
sig = PGPSignature.new(SignatureType.BinaryDocument, PubKeyAlgorithm.RSAEncryptOrSign, HashAlgorithm.SHA512,
sk.fingerprint.keyid)
sig._signature.subpackets['h_CreationTime'][-1].created = datetime(2014, 8, 6, 23, 28, 51)
sig._signature.subpackets.update_hlen()
hdata = sig.hashdata(sigsubject)
sig._signature.hash2 = hashlib.new('sha512', hdata).digest()[:2]
# create the signature
signer = sk.__key__.__privkey__().signer(padding.PKCS1v15(), hashes.SHA512())
signer.update(hdata)
sig._signature.signature.from_signer(signer.finalize())
sig._signature.update_hlen()
# create the signature
signer = sk.__key__.__privkey__().signer(padding.PKCS1v15(), hashes.SHA512())
signer.update(hdata)
sig._signature.signature.from_signer(signer.finalize())
sig._signature.update_hlen()
# check encoding
assert sig._signature.signature.md_mod_n.to_mpibytes()[2:3] != b'\x00'
# check encoding
assert sig._signature.signature.md_mod_n.to_mpibytes()[2:3] != b'\x00'
# with PGPy
assert pk.verify(sigsubject, sig)
# with PGPy
assert pk.verify(sigsubject, sig)
# with GnuPG
with write_clean('tests/testdata/subj', 'w', sigsubject.decode('latin-1')), \
write_clean('tests/testdata/subj.asc', 'w', str(sig)), \
write_clean('tests/testdata/pub.asc', 'w', str(pk)), \
gpg_import('pub.asc'):
assert gpg_verify('subj', 'subj.asc')
# with GnuPG
with tempfile.NamedTemporaryFile('w+') as subjf, \
tempfile.NamedTemporaryFile('w+') as sigf, \
tempfile.NamedTemporaryFile('w+') as pubf:
subjf.write(sigsubject.decode('latin-1'))
sigf.write(str(sig))
pubf.write(str(pk))
subjf.flush()
sigf.flush()
pubf.flush()
with gpg_import(pubf.name):
assert gpg_verify(subjf.name, sigf.name)
@pytest.mark.regression(issue=157)
def test_reg_bug_157(monkeypatch):
# local imports for this
import pgpy.constants
from pgpy.packet.fields import String2Key
from time import time as rtime
# to more easily replicate this bug, hash only 8 bytes instead of 100 KiB
monkeypatch.setattr('pgpy.constants._hashtunedata', bytearray([10, 11, 12, 13, 14, 15, 16, 17]))
# also monkeypatch time.time to return fewer significant digits
monkeypatch.setattr('time.time', lambda: round(rtime(), 3))
assert len(pgpy.constants._hashtunedata) == 8
pgpy.constants.HashAlgorithm.SHA256.tune_count()
assert pgpy.constants.HashAlgorithm.SHA256.tuned_count > 0
# now let's try it out and ensure that the count actually worked
s2k = String2Key()
s2k.encalg = pgpy.constants.SymmetricKeyAlgorithm.AES256
s2k.specifier = pgpy.constants.String2KeyType.Iterated
s2k.halg = pgpy.constants.HashAlgorithm.SHA256
s2k.count = pgpy.constants.HashAlgorithm.SHA256.tuned_count
start = rtime()
sk = s2k.derive_key('sooper_sekret_passphrase')
elapsed = rtime() - start
# check that we're actually close to our target
assert len(sk) == 32
try:
assert 0.1 <= round(elapsed, 1) <= 0.2
except AssertionError:
warnings.warn("tuned_count: {}; elapsed time: {:.5f}".format(pgpy.constants.HashAlgorithm.SHA256.tuned_count, elapsed))

24
tests/testdata/blocks/dsapubkey.asc vendored Normal file
View File

@@ -0,0 +1,24 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGiBFisk0URBACkc86J1iRa6gV0nQvuZve9WkbGkJWT0kopILrB5H0SckXkLwOx
kR+T+f/+S1GG+7qB39E0tCNDPEQlm06hfof0sKZ594GSNFDfWvcIYNksxyIJ3dHM
obkt7LbrWVqm/QzwhJqnvQEdNPD2tZYmJYlDEFvL3buPfyiczf7LRBVa2wCgmG43
XQ2TbbBl3oVnnpaWMRuo3RMD/27hIJ+ww3zXkH9Nd/4vC3jKsMk/DSxR/MKfcbam
LxRon409/k3c31LDskBz7secNgscvlTzsWwsib2ACw1Cr3cUWke28kypVAJlwVqt
F2OVFjSO9OKjHOwACF/N1tMjulcKHqDRW4EkkNBZpzKwn37UycDbhnxpaeHAjGOe
rPyTA/43OSP/P2WJw1hramsfHV9BAS2jd2+qz3dPdhaB2BdLa7OuZVkik2bnMyp8
IGtblORz2f1Vuc+0Wp3FL4zsrakN3L7VZwla1Gp+FkNpY3Mgfn7Ts69Pl57Y5+Cm
CTRa2lKflL3yM+nf8a/1tCHuOtl/iaQQhLoMkRSpHdrYiIQqzLQeRFNBIHZvbiBU
ZXN0S2V5IDxkc2FAdGVzdC5rZXk+iHgEExECADgWIQQrW7sUO6CykNzuZmi3mK6J
kIdyAQUCWKyTRQIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRC3mK6JkIdy
AdNGAJ9T8hKPVWoG0UJFQo2/6Cr9okRSZACeNxxJ92pOU/LEo9TgtqQqbmk73SK5
AQ0EWKyTRRAEAMXlraLYYWCNoMa0s6aeRvmlQHZiXqcB3ECI+hO4EivzisXAQhg1
BQPDn6IMCE4pX69Q/z2LOk6aeD1htMymWzGbyzG7jFRX8Q9US9ooul+nvSVf3xVW
UjeLGKi8Z3yD2lK8efB/bGxxx7xdNlrk2AyucnHjyc+HOcyKoNA8TAW3AAURA/9D
dpz9M1Rsb9vR4QFHh03fcFh46dhFMjT94ShwMVaJSWy0gHNnTp0MmDzQAdBSFJYE
rdZV7YCex6HoHfGS7btpEX//YoxL/nsoP73y34PgYmmqYLZ95cCLdn+YuYPhNAQT
klCNR4fjtZme83oZLgzd4Hkl5maVixRmA1n8TOB7QYhfBBgRAgAgFiEEK1u7FDug
spDc7mZot5iuiZCHcgEFAlisk0UCGwwACgkQt5iuiZCHcgE5wgCbB5EYVFft5QPK
KiUE3NMzKCik1pEAmOO+qvoi2g6DG2mmEU5Y56IoiSQ=
=wk7s
-----END PGP PUBLIC KEY BLOCK-----

27
tests/testdata/blocks/dsaseckey.asc vendored Normal file
View File

@@ -0,0 +1,27 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
lQHpBFisk0URBACkc86J1iRa6gV0nQvuZve9WkbGkJWT0kopILrB5H0SckXkLwOx
kR+T+f/+S1GG+7qB39E0tCNDPEQlm06hfof0sKZ594GSNFDfWvcIYNksxyIJ3dHM
obkt7LbrWVqm/QzwhJqnvQEdNPD2tZYmJYlDEFvL3buPfyiczf7LRBVa2wCgmG43
XQ2TbbBl3oVnnpaWMRuo3RMD/27hIJ+ww3zXkH9Nd/4vC3jKsMk/DSxR/MKfcbam
LxRon409/k3c31LDskBz7secNgscvlTzsWwsib2ACw1Cr3cUWke28kypVAJlwVqt
F2OVFjSO9OKjHOwACF/N1tMjulcKHqDRW4EkkNBZpzKwn37UycDbhnxpaeHAjGOe
rPyTA/43OSP/P2WJw1hramsfHV9BAS2jd2+qz3dPdhaB2BdLa7OuZVkik2bnMyp8
IGtblORz2f1Vuc+0Wp3FL4zsrakN3L7VZwla1Gp+FkNpY3Mgfn7Ts69Pl57Y5+Cm
CTRa2lKflL3yM+nf8a/1tCHuOtl/iaQQhLoMkRSpHdrYiIQqzP4HAwKXqnH0U/4k
KO7U5CKhvfx/ux6nw+SCR7BF68Slo2Emzu1u1ytu3YJul8WsfNW8DIfe43m3LYhf
Eqkaw+PS8BnQLqLFtB5EU0Egdm9uIFRlc3RLZXkgPGRzYUB0ZXN0LmtleT6IeAQT
EQIAOBYhBCtbuxQ7oLKQ3O5maLeYromQh3IBBQJYrJNFAhsDBQsJCAcCBhUICQoL
AgQWAgMBAh4BAheAAAoJELeYromQh3IB00YAn1PyEo9VagbRQkVCjb/oKv2iRFJk
AJ43HEn3ak5T8sSj1OC2pCpuaTvdIp0BYARYrJNFEAQAxeWtothhYI2gxrSzpp5G
+aVAdmJepwHcQIj6E7gSK/OKxcBCGDUFA8OfogwITilfr1D/PYs6Tpp4PWG0zKZb
MZvLMbuMVFfxD1RL2ii6X6e9JV/fFVZSN4sYqLxnfIPaUrx58H9sbHHHvF02WuTY
DK5ycePJz4c5zIqg0DxMBbcABRED/0N2nP0zVGxv29HhAUeHTd9wWHjp2EUyNP3h
KHAxVolJbLSAc2dOnQyYPNAB0FIUlgSt1lXtgJ7Hoegd8ZLtu2kRf/9ijEv+eyg/
vfLfg+Biaapgtn3lwIt2f5i5g+E0BBOSUI1Hh+O1mZ7zehkuDN3geSXmZpWLFGYD
WfxM4HtB/gcDAnepsPge3rKk7rHlUAbFtKH0vOu4HJQb5iPRzy6UHbDbQ0lN7qMX
pjHwCKQ5/Lmx8T8IIF4Cajzk31Gucb9xUJ0OxvPjCWOQ6D7jEEt9m9SIXwQYEQIA
IBYhBCtbuxQ7oLKQ3O5maLeYromQh3IBBQJYrJNFAhsMAAoJELeYromQh3IBOcIA
mweRGFRX7eUDyiolBNzTMygopNaRAJjjvqr6ItoOgxtpphFOWOeiKIkk
=MJIx
-----END PGP PRIVATE KEY BLOCK-----

View File

@@ -0,0 +1,5 @@
-----BEGIN PGP MESSAGE-----
yyd0BWFzY2lpWKyiRVRoaXMgaXMgc3RvcmVkLCB0ZXh0dWFsbHkhDQo=
=PmSE
-----END PGP MESSAGE-----

View File

@@ -0,0 +1,34 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenPGP.js v2.3.0
Comment: http://openpgpjs.org
xsBNBFdQq7kBB/9T77WfC8wWyfpa2x221JXLBOxan5E9FyTNzCcGxksoykYv
b/wwLPA7M2+wn6Am7OnTkbjMyKVb1D43fy1Bermc1EQqRTscIWvVLhrgzFSu
O8OUXM49DmrNbxvpEuU7/WUjuFyXcJnI0pVBaNKGH5iyLLP6BC4sOdyIPCLz
YQH+FxcWphnx63o1CqzAWIjbGcRNGkQzmLwqNmqS5/f419yTonzUkzm4ptm1
/Kpsj65n/I2vjVtvGQCR86M4aYD53s/dN5xeCirAjmbBDta/B2vlOlYFZWs2
zTBDv4ER5xC3CvMSp0Fy1jvSAJLCSiAhzYUivhSTk9L5yNEyynHbPj2BABEB
AAHNG1QuSy4gPG9wZW5wZ3AuanNAZW1haWwuY29tPsLAdQQQAQgAKQUCV1Cr
vQYLCQcIAwIJEBSrRMdNG9q4BBUIAgoDFgIBAhkBAhsDAh4BAAApRQf+Jxn/
l3Qd3qOYxjTjdf6Fql807abnRIxbDTfZekv6QiIA3TKDPVVvxSLpL/EF2nvF
ErXhxq34ZK4oypp73n9lyoKkmlAS+JLEeFUIISUzdiwgBRAfjQpE19KOImGb
7ZGvMMEfnitkn2p/uR+EnGVCjOsmHsUW5Z4eSI/KXC8+Radi55FN5WA65QHl
Jed6yXDnKQSCR6V9N6UlEQk/Qeiuh1PnBoie0kS8ct9/YA+ltsK7KGrHWKoi
GJC79Ma9ksjhLIkyazACyVK44hYXFAOeSzyCM+/cVAmbLSwfZKWSStdzfj0u
oWFkV/RTfZjwNi6BrTViNcYFb7BcACqg0x0Uzc7ATQRXUKu6AQf/YLVawSGz
HHcRiA9FzlssfosR0CrQgomha0woYnD3PtOqwy/R3FluNVD336i/kC08IsIt
MjjToKoKke/HErDBuyKiCWJmuPTbNvoCPjRJp81V6SUm1QAnjFkhYVzTs3Y6
1O/xIP2BI7yrSVr2v8wSwrQI7f4EktbrLq4w1gOKxiYZcj3zia4BUwmBsa7O
yOKnwTVrpkvMnQE4Ulkkj853V7QiBTUJaveZoSowfUQzXzTcgkWUQO04PjYR
HjUyldGWKny8UwlSxHrNo41rG7JACd8eE5esKVpX/jcwu/820rDqbo1jkhFB
cWSzRh/X2nTUClQud+SpxFE5z8h7A7bDUQARAQABwsBfBBgBCAATBQJXUKu9
CRAUq0THTRvauAIbDAAAMAUH/0j5fQZd3R3+zxR19nUJzkbbgH+PmUoiK5En
z4LzdxT9Tj1D1mDvdKbRqxLehOWlWAJSP0mRpwrs5GOYHYqC+FuCZKeMZi2+
W5RYr8YdMMwn3p5mPO2WvEOWuZLyO2hEDFsF33Jt+w08MkgH/MSDUg/UQZIY
ERiuen4Fe1Xq2FVLxEagwRMF34KXgVfUpCRkeHWjiLpK1ne74Q3+O4cy2H1v
Y1ld+OHKP78dP+Y53h5RodrOJ3bkBddZk2hmx/g7ltVFzyya9/gs+dPdaQpL
11I1n1TWjZ7S+xjcNt8q9gfmSRVYcugt/Vwi+ekg3oNZvog4/kyNW7ypvqA1
gWGghBg=
=/Uz1
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,62 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: OpenPGP.js v2.3.0
Comment: http://openpgpjs.org
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
hBg=
=w1XY
-----END PGP PRIVATE KEY BLOCK-----

View File

@@ -0,0 +1,12 @@
-----BEGIN PGP SIGNATURE-----
Version: PGPy v0.5.0
iQE2BBABCAAgFiEE9ClLyAlKfgWFyF6GN0c7N1jETzYFAlispnYCBAAACgkQN0c7
N1jETzbkhgf/cMR8dJObANItbQD6V96zoC/fSPSntE7XyVGNEl3efGY8CMIx0a9n
y+pOLgnKQcYdUi39U4U0KllnmulV4Z3HJvn6OqA77iQKdpQ3zEhWNA0/5QWIZja5
uI1zRSITfr5KMYJPni+ET09bsYkT8RB+yw5ZOUY6hFyZhuMmk2+FqF2ZS2xUf8yY
aQc6twqRo6tEck6c9zI5fO/MJQE7Zn55ijZp6Qk1yYDFfn3nq0oVp/lBkPXWiM5R
OMDrOYMqAJIliBxcab33dGU/e+afm4vNdOtidAw1CCQtwFF/R2LVei3ba2ceaj8T
ak7da9ht6w9wxU3wjgPd6iz6FIIIv4UqaQ==
=LNt/
-----END PGP SIGNATURE-----

View File

@@ -0,0 +1,31 @@
-----BEGIN PGP MESSAGE-----
Version: BCPG C# v1.6.1.0
jA0EAwMCjUn4xB3iQ+1gycP5SZFnGPJuc+ziJVLfwYrChXjt+tIt65OO32lmoZfg
7xvLkSnKRbEMr4fsbhBKPz02FS1T3qoDx+uFSdAXyYnwIZ/WLIfVbxmcl9AkgLvE
vfrq81rODrwtHJci71EYOKBme0luWkzGdIMHTx8MpvNVLJEBmVqj0f9CeUt/sZDn
rMdoyG8xxkKqFn7IhM7Ef9FtQiUJlE5sRFMn50kFT9gwYE6lGum1nze1aDoX1Pol
s5JRWJ2uQs8eQ4Aba9f9Jffv13qJDfkxovFyqupkCZFwm4smQkvLJUxEMxzpe5Ev
OR071ESiPDfQBfDTT+Eaw4frr0lMhoAAkEEC3Ncju7EhhQeLgQndwxgiiDF+WcVl
OuSl6b1XaTMn/X70SH0Lg3QUdBV0CF5lrLkWuN33u3GffaES4sBm25wC/Bbb13Q8
aJunYMJ8KhkyBkXXiNxqh2G1/L1dxe+nz9WIeL30YBjxxwi3/rifoRkbux7HftGV
VBThkmB2UJZszutGEXGvpNzhrfogQi4TFIOScdgb33P6uwpx+QATxBxHFv3eSJ6n
SYCS5C2b1dShlpXonISqYYXOEwkhmt3vWKEhU8pEWjB76tr7oTbCm3UqMg5QEa/e
QMstP2g9ZG8U4mExfiKFzGsCznoJJSJdG/WfkUSkhUOREZc7OGEBrhgRPHELMMzy
ebEy3DVXU3DLSMmYTh2xZY909ba+8mYzy1AXqEQfiCTQH2JgfSEdviD5E4QxRuOx
q6AhIb1iHXw1JP3ZkO/tbXlTXgBY8AXD5dv6t1mZd+hQ2AzKcAGsK+VOJcXVLhEC
dAz8N8ZYHSLc4dVxz1m3G9St81o0qpB51cfFlWYyAxhKiuKbia6cBIzHAvo2ys81
GZs2raUT6+6ehXwy6zVixi5ouO9UHl9HMA5thaOaK0hP8qZFKmRk2nyUYilKbshr
9lAhu+MxygzuBdISW/EWQjbO4GsJ+r7TG7b/b/nwOVPM02MHZOiP5wyko3hro2xa
nRTLt/8OgQXW9Dn/58bx7KqNx69oTNuvAU4MVDdzTx2pQymhMpz/KrMuGHlcyv5v
/ntTkmTBtfFNu+CNr8Uh3tMqmjlNwMBEY/BI0HagDxikpjO/qETluzG9cLwnBc1C
6ySkkOLisJIdLzrRuMvekHg3795GLhrAI/z8uSPIlvQDi4eLgPzzsckLu9PEsG/d
2p7tk6CCc7HwpBlBj2l5nZcoHUNEesDUJSkG2VpPR+TDdkSIWirEp/1y6QPCoa0d
v8M5x0gnEpWaGvBl8x1wBxIHCxO4KcoiWS+90kNNIdpzKjkSBlnmNPrZ/37E9PQd
xev7+do6CKiRO3YUzs4kT18mazd19AWGCJbxnXll294fc5xTZSJeH9HYPcvuETBG
B/4DV4TKGH10WJgG/ZHqdt+4bqyVts/xvjJI5s+AvwXHTHoFZLDnFzmJdlU8LPic
/sZUEBrFPS49x5f7ccEHt9zBgrvwioB6Db1Y3snGY0hXIl7/jM4hdWOHleKVKC16
GxSWICovLeDvL9j8J6b2NP20sPr32bYwVopdRdhWEeUUkHYIlAR3GuZZtLMQcXeK
pMf+D8ohmWrk1jLcpnYakO1L77fJZuhG5hwF
=NG/d
-----END PGP MESSAGE-----

View File

@@ -0,0 +1,5 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris pretium libero id orci interdum pretium. Cras at arcu in leo facilisis tincidunt ac sed lorem. Quisque quis varius quam. Integer gravida quam non cursus suscipit. Vivamus placerat convallis leo, nec lobortis ipsum. Sed fermentum ipsum sed tellus consequat elementum. Fusce id congue orci, at molestie ex.
Phasellus vel sagittis mauris. Ut in vehicula ipsum. Nullam facilisis molestie diam, in fermentum justo interdum id. Donec vestibulum tristique sapien nec rhoncus. Suspendisse venenatis consectetur mollis. Phasellus fringilla tortor non ligula malesuada, in vehicula mauris efficitur. Duis pulvinar eleifend est nec fringilla. Nunc elit nulla, sodales quis ullamcorper sit amet, elementum vitae justo. In pretium leo sit amet risus pharetra, ac tincidunt sem varius.
Nunc fermentum id risus sed lobortis. Sed id vulputate arcu. In ac quam sed nulla semper ullamcorper. Donec eleifend quam at dolor dictum, ut efficitur tortor dapibus. Nunc maximus quam non erat aliquet, quis blandit nibh sollicitudin. Fusce aliquam est enim, nec mattis orci scelerisque nec. Nullam venenatis eget elit consectetur sagittis.

View File

@@ -0,0 +1,19 @@
-----BEGIN PGP MESSAGE-----
Version: BCPG C# v1.6.1.0
jA0EAwMCCv41ziKqTRVgycGm1XyJLTiWq5AbQLN0xA8/yH/gPf/2D+L+e2yzGq7Q
1sut8FKqX2LBga8mu4gHvvotu4dvewDF5t0jT72zRqc9VsRebWihRkdNr9GU5j5K
kxlA59uOPayFHjlkkqgrwR0m1RZc63G+79w/qoAv8K6OIHnpW5AXZR0WS4ZxfDoK
p5vXPZ7U9SAEHCSNCbJDMKVWKn0haWE4LZ3k5sOmTR4fGCowPdi4qu1sDCcl9Kzk
TTHh/9mshclKvYQKfZPQD4k14PGc/OHWHHj+THhciNUSYDy4D62cXeSbSziwD+Ga
fWMhggFMRkc1kp8c6C9z7aFG7Ii6SVGBJIfY7MWvfFqte9TdDtiyKEF+3CH0jUEG
m1en0tImUFVW+qRzP4T0eUtrw5svxlx/NprgT6ruOfOBGdb9oS2T0WPm3ZgY+TYN
liKI/Co3zDAFF3oE5JjwmuNFLa47/HCsqNLWqzk9bskKBxHYv2g8Mu1lwK8wwBvh
0w85itQFf7N/7ilMSj0ABRaNNQHV+jGWY9NWBnoTfDgz4zYAg70uPL4DVEw1kdVU
HbCRmdSop3D0YHLFuCyUBMLkKg3dq0cXhCszUhkAOYb1Xu6sMGJzJw6xi2kzJliK
ZwnKJVKjE6B8NajPdtWOkganHy5jtPWgg8Et5oysW/QwviRuXh1VGwLUmX2puZ3F
2DwtEB1mJNRKS7F35sfdGfj1fOpVOMsEngiaYoZnUkBplxpb/In+Tz8QW0ocrZbh
VhA26h4uWKAHiAmd4Qlk4xUFls1Hs/p8WHr0njEuB+GjUVmlEMAw6yg160+OiWZt
VjUDkzDEAPA=
=M8wJ
-----END PGP MESSAGE-----

View File

@@ -0,0 +1,2 @@
This is stored, literally\!

13
tox.ini
View File

@@ -1,9 +1,9 @@
[tox]
envlist = pypy, pypy3, py27, py33, py34, py35, pep8, setup35, setup34, setup33, setup27
envlist = pypy, pypy3, py27, py33, py34, py35, py36, pep8, setup36, setup35, setup34, setup33, setup27
skipsdist = True
[pytest]
addopts = -vv -s --color=yes
addopts = -vv -r a -s --color=yes
norecursedirs = testdata
[flake8]
@@ -21,6 +21,7 @@ deps =
singledispatch
pytest
pytest-cov
pytest-ordering
install_command = pip install {opts} --no-cache-dir {packages}
commands =
@@ -32,6 +33,12 @@ commands =
pip install -e .
rm -rf PGPy.egg-info
[testenv:setup36]
recreate = True
basepython = python3.6
whitelist_externals = {[test-setup]whitelist_externals}
commands = {[test-setup]commands}
[testenv:setup35]
recreate = True
basepython = python3.5
@@ -58,7 +65,7 @@ whitelist_externals = {[test-setup]whitelist_externals}
commands = {[test-setup]commands}
[testenv:pep8]
basepython = python3.5
basepython = python3.6
deps =
flake8
pep8-naming