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

View File

@@ -38,12 +38,19 @@ Documentation
`PGPy Documentation <https://pythonhosted.org/PGPy/>`_ `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 Requirements
------------ ------------
- Python 3 >= 3.3; Python 2 >= 2.7 - 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>`_ - `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. 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 .. autoclass:: PGPKey
:members: :members:
:noindex:
.. py:attribute:: ascii_header .. py:attribute:: ascii_header
:noindex:
:annotation: = OrderedDict([('Version', 'PGPy v|version|')]) :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. An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object.
.. py:classmethod:: from_file(filename) .. 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. 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 } # others: { (Fingerprint, bool(key.is_public): PGPKey }
.. py:classmethod:: from_blob(blob) .. 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. 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: :members:
.. py:attribute:: ascii_header .. py:attribute:: ascii_header
:noindex:
An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object. 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: :members:
.. py:attribute:: ascii_header .. py:attribute:: ascii_header
:noindex:
An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object. An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object.
.. py:classmethod:: from_file(filename) .. 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. 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` :returns: :py:obj:`PGPMessage`
.. py:classmethod:: from_blob(blob) .. 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. 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: :members:
.. py:attribute:: ascii_header .. py:attribute:: ascii_header
:noindex:
An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object. An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object.
.. py:classmethod:: from_file(filename) .. 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. 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` :returns: :py:obj:`PGPSignature`
.. py:classmethod:: from_blob(blob) .. 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. 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 .. autoclass:: PubKeyAlgorithm
:no-members: :no-members:
:noindex:
.. autoattribute:: RSAEncryptOrSign .. autoattribute:: RSAEncryptOrSign
:noindex:
:annotation: :annotation:
.. autoattribute:: DSA .. autoattribute:: DSA
:noindex:
:annotation: :annotation:
.. autoattribute:: ElGamal .. autoattribute:: ElGamal
:noindex:
:annotation: :annotation:
.. autoattribute:: ECDH .. autoattribute:: ECDH
:noindex:
:annotation: :annotation:
.. autoattribute:: ECDSA .. autoattribute:: ECDSA
:noindex:
:annotation: :annotation:
:py:class:`EllipticCurveOID` :py:class:`EllipticCurveOID`
---------------------------- ----------------------------
.. autoclass:: EllipticCurveOID .. autoclass:: EllipticCurveOID
:noindex: :no-members:
.. autoattribute:: Curve25519 .. autoattribute:: Curve25519
:noindex:
:annotation: :annotation:
.. autoattribute:: Ed25519 .. autoattribute:: Ed25519
:noindex:
:annotation: :annotation:
.. autoattribute:: NIST_P256 .. autoattribute:: NIST_P256
:noindex:
:annotation: :annotation:
.. autoattribute:: NIST_P384 .. autoattribute:: NIST_P384
:noindex:
:annotation: :annotation:
.. autoattribute:: NIST_P521 .. autoattribute:: NIST_P521
:noindex:
:annotation: :annotation:
.. autoattribute:: Brainpool_P256 .. autoattribute:: Brainpool_P256
:noindex:
:annotation: :annotation:
.. autoattribute:: Brainpool_P384 .. autoattribute:: Brainpool_P384
:noindex:
:annotation: :annotation:
.. autoattribute:: Brainpool_P512 .. autoattribute:: Brainpool_P512
:noindex:
:annotation: :annotation:
.. autoattribute:: SECP256K1 .. autoattribute:: SECP256K1
:noindex:
:annotation: :annotation:
@@ -78,38 +63,35 @@ Constants
.. autoclass:: SymmetricKeyAlgorithm .. autoclass:: SymmetricKeyAlgorithm
:no-members: :no-members:
:noindex:
.. autoattribute:: IDEA .. autoattribute:: IDEA
:noindex:
:annotation: :annotation:
.. autoattribute:: TripleDES .. autoattribute:: TripleDES
:noindex:
:annotation: :annotation:
.. autoattribute:: CAST5 .. autoattribute:: CAST5
:noindex:
:annotation: :annotation:
.. autoattribute:: Blowfish .. autoattribute:: Blowfish
:noindex:
:annotation: :annotation:
.. autoattribute:: AES128 .. autoattribute:: AES128
:noindex: :annotation:
.. autoattribute:: AES192
:annotation:
.. autoattribute:: AES256
:annotation: :annotation:
.. autoattribute:: Camellia128 .. autoattribute:: Camellia128
:noindex:
:annotation: :annotation:
.. autoattribute:: Camellia192 .. autoattribute:: Camellia192
:noindex:
:annotation: :annotation:
.. autoattribute:: Camellia256 .. autoattribute:: Camellia256
:noindex:
:annotation: :annotation:
@@ -118,22 +100,17 @@ Constants
.. autoclass:: CompressionAlgorithm .. autoclass:: CompressionAlgorithm
:no-members: :no-members:
:noindex:
.. autoattribute:: Uncompressed .. autoattribute:: Uncompressed
:noindex:
:annotation: :annotation:
.. autoattribute:: ZIP .. autoattribute:: ZIP
:noindex:
:annotation: :annotation:
.. autoattribute:: ZLIB .. autoattribute:: ZLIB
:noindex:
:annotation: :annotation:
.. autoattribute:: BZ2 .. autoattribute:: BZ2
:noindex:
:annotation: :annotation:
:py:class:`HashAlgorithm` :py:class:`HashAlgorithm`
@@ -141,34 +118,26 @@ Constants
.. autoclass:: HashAlgorithm .. autoclass:: HashAlgorithm
:no-members: :no-members:
:noindex:
.. autoattribute:: MD5 .. autoattribute:: MD5
:noindex:
:annotation: :annotation:
.. autoattribute:: SHA1 .. autoattribute:: SHA1
:noindex:
:annotation: :annotation:
.. autoattribute:: RIPEMD160 .. autoattribute:: RIPEMD160
:noindex:
:annotation: :annotation:
.. autoattribute:: SHA256 .. autoattribute:: SHA256
:noindex:
:annotation: :annotation:
.. autoattribute:: SHA384 .. autoattribute:: SHA384
:noindex:
:annotation: :annotation:
.. autoattribute:: SHA512 .. autoattribute:: SHA512
:noindex:
:annotation: :annotation:
.. autoattribute:: SHA224 .. autoattribute:: SHA224
:noindex:
:annotation: :annotation:
@@ -177,62 +146,47 @@ Constants
.. autoclass:: SignatureType .. autoclass:: SignatureType
:no-members: :no-members:
:noindex:
.. autoattribute:: BinaryDocument .. autoattribute:: BinaryDocument
:noindex:
:annotation: :annotation:
.. autoattribute:: CanonicalDocument .. autoattribute:: CanonicalDocument
:noindex:
:annotation: :annotation:
.. autoattribute:: Standalone .. autoattribute:: Standalone
:noindex:
:annotation: :annotation:
.. autoattribute:: Generic_Cert .. autoattribute:: Generic_Cert
:noindex:
:annotation: :annotation:
.. autoattribute:: Persona_Cert .. autoattribute:: Persona_Cert
:noindex:
:annotation: :annotation:
.. autoattribute:: Positive_Cert .. autoattribute:: Positive_Cert
:noindex:
:annotation: :annotation:
.. autoattribute:: Subkey_Binding .. autoattribute:: Subkey_Binding
:noindex:
:annotation: :annotation:
.. autoattribute:: PrimaryKey_Binding .. autoattribute:: PrimaryKey_Binding
:noindex:
:annotation: :annotation:
.. autoattribute:: DirectlyOnKey .. autoattribute:: DirectlyOnKey
:noindex:
:annotation: :annotation:
.. autoattribute:: KeyRevocation .. autoattribute:: KeyRevocation
:noindex:
:annotation: :annotation:
.. autoattribute:: SubkeyRevocation .. autoattribute:: SubkeyRevocation
:noindex:
:annotation: :annotation:
.. autoattribute:: CertRevocation .. autoattribute:: CertRevocation
:noindex:
:annotation: :annotation:
.. autoattribute:: Timestamp .. autoattribute:: Timestamp
:noindex:
:annotation: :annotation:
.. autoattribute:: ThirdParty_Confirmation .. autoattribute:: ThirdParty_Confirmation
:noindex:
:annotation: :annotation:
@@ -241,34 +195,26 @@ Constants
.. autoclass:: KeyFlags .. autoclass:: KeyFlags
:no-members: :no-members:
:noindex:
.. autoattribute:: Certify .. autoattribute:: Certify
:noindex:
:annotation: :annotation:
.. autoattribute:: Sign .. autoattribute:: Sign
:noindex:
:annotation: :annotation:
.. autoattribute:: EncryptCommunications .. autoattribute:: EncryptCommunications
:noindex:
:annotation: :annotation:
.. autoattribute:: EncryptStorage .. autoattribute:: EncryptStorage
:noindex:
:annotation: :annotation:
.. autoattribute:: Split .. autoattribute:: Split
:noindex:
:annotation: :annotation:
.. autoattribute:: Authentication .. autoattribute:: Authentication
:noindex:
:annotation: :annotation:
.. autoattribute:: MultiPerson .. autoattribute:: MultiPerson
:noindex:
:annotation: :annotation:
@@ -277,25 +223,19 @@ Constants
.. autoclass:: RevocationReason .. autoclass:: RevocationReason
:no-members: :no-members:
:noindex:
.. autoattribute:: NotSpecified .. autoattribute:: NotSpecified
:noindex:
:annotation: :annotation:
.. autoattribute:: Superseded .. autoattribute:: Superseded
:noindex:
:annotation: :annotation:
.. autoattribute:: Compromised .. autoattribute:: Compromised
:noindex:
:annotation: :annotation:
.. autoattribute:: Retired .. autoattribute:: Retired
:noindex:
:annotation: :annotation:
.. autoattribute:: UserID .. autoattribute:: UserID
:noindex:
:annotation: :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 PGPy API
******** ********
.. toctree:: .. include:: exceptions.rst.inc
:hidden:
exceptions .. include:: constants.rst.inc
constants
classes
.. include:: exceptions.rst .. include:: classes.rst.inc
.. include:: constants.rst
.. include:: classes.rst

View File

@@ -4,10 +4,26 @@
Changelog 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 v0.4.0
====== ======
Released: |today| Released: April 21, 2016
Bugs Fixed 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], ciphers=[SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.AES192, SymmetricKeyAlgorithm.AES128],
compression=[CompressionAlgorithm.ZLIB, CompressionAlgorithm.BZ2, CompressionAlgorithm.ZIP, CompressionAlgorithm.Uncompressed]) 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 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: # 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) 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}) key.add_subkey(subkey, usage={KeyFlags.Authentication})
Loading Keys 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_public is False
# key.is_protected 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 # key.is_protected is now True
Unlocking Protected Secret Keys Unlocking Protected Secret Keys
@@ -106,6 +123,11 @@ Key unlocking is quite simple::
# enc_key.is_unlocked is now True # 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 Exporting Keys
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
@@ -126,4 +148,3 @@ in Python 2::
# ASCII armored # ASCII armored
keystr = str(key) keystr = str(key)

View File

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

View File

@@ -2,29 +2,63 @@
Installation Installation
************ ************
.. highlight:: bash .. highlight:: console
Platform Specific Notes 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 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:: 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. 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 Mac OS X
-------- --------
If you are on Mac OS, you may experience more limited functionality without installing a more capable version of OpenSSL. 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 Installation
@@ -33,4 +67,3 @@ Installation
Once you have the prerequisites specified above, PGPy can be installed from PyPI using pip, like so:: Once you have the prerequisites specified above, PGPy can be installed from PyPI using pip, like so::
$ pip install PGPy $ 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. :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: :Curves:
- Curve, False, Brainpool P-256 - Curve, True, Brainpool P-256
- Curve, False, Brainpool P-384 - Curve, True, Brainpool P-384
- Curve, False, Brainpool P-512 - Curve, True, Brainpool P-512
- Curve, False, Curve25519 - Curve, False, Curve25519
- Curve, True, SECP256K1 - Curve, True, SECP256K1
.. note:: Use of Brainpool curves with ECDSA/ECDH .. 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 \* 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" __author__ = "Michael Greene"
__copyright__ = "Copyright (c) 2014 Michael Greene" __copyright__ = "Copyright (c) 2014 Michael Greene"
__license__ = "BSD" __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 .decorators import classproperty
from .types import FlagEnum from .types import FlagEnum
from ._curves import BrainpoolP256R1, BrainpoolP384R1, BrainpoolP512R1
__all__ = ['Backend', __all__ = ['Backend',
'EllipticCurveOID', 'EllipticCurveOID',
@@ -41,8 +42,8 @@ __all__ = ['Backend',
'TrustFlags'] 'TrustFlags']
# this is 100 KiB # this is 50 KiB
_hashtunedata = bytearray([10, 11, 12, 13, 14, 15, 16, 17] * 128 * 100) _hashtunedata = bytearray([10, 11, 12, 13, 14, 15, 16, 17] * 128 * 50)
class Backend(Enum): class Backend(Enum):
@@ -71,29 +72,34 @@ class EllipticCurveOID(Enum):
NIST_P521 = ('1.3.132.0.35', ec.SECP521R1) NIST_P521 = ('1.3.132.0.35', ec.SECP521R1)
#: Brainpool Standard Curve, 256-bit #: Brainpool Standard Curve, 256-bit
#: #:
#: .. warning:: #: .. note::
#: This curve is not currently usable by PGPy #: Requires OpenSSL >= 1.0.2
Brainpool_P256 = ('1.3.36.3.3.2.8.1.1.7', ) Brainpool_P256 = ('1.3.36.3.3.2.8.1.1.7', BrainpoolP256R1)
#: Brainpool Standard Curve, 384-bit #: Brainpool Standard Curve, 384-bit
#: #:
#: .. warning:: #: .. note::
#: This curve is not currently usable by PGPy #: Requires OpenSSL >= 1.0.2
Brainpool_P384 = ('1.3.36.3.3.2.8.1.1.11', ) Brainpool_P384 = ('1.3.36.3.3.2.8.1.1.11', BrainpoolP384R1)
#: Brainpool Standard Curve, 512-bit #: Brainpool Standard Curve, 512-bit
#: #:
#: .. warning:: #: .. note::
#: This curve is not currently usable by PGPy #: Requires OpenSSL >= 1.0.2
Brainpool_P512 = ('1.3.36.3.3.2.8.1.1.13', ) Brainpool_P512 = ('1.3.36.3.3.2.8.1.1.13', BrainpoolP512R1)
#: SECG curve secp256k1 #: SECG curve secp256k1
SECP256K1 = ('1.3.132.0.10', ec.SECP256K1) SECP256K1 = ('1.3.132.0.10', ec.SECP256K1)
def __new__(cls, oid, curve=None): def __new__(cls, oid, curve=None):
# preprocessing stage for enum members: # preprocessing stage for enum members:
# - set enum_member.value to ObjectIdentifier(oid) # - 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 = object.__new__(cls)
obj._value_ = ObjectIdentifier(oid) 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 return obj
@property @property
@@ -110,6 +116,7 @@ class EllipticCurveOID(Enum):
# return the hash algorithm to specify in the KDF fields when generating a key # return the hash algorithm to specify in the KDF fields when generating a key
algs = {256: HashAlgorithm.SHA256, algs = {256: HashAlgorithm.SHA256,
384: HashAlgorithm.SHA384, 384: HashAlgorithm.SHA384,
512: HashAlgorithm.SHA512,
521: HashAlgorithm.SHA512} 521: HashAlgorithm.SHA512}
return algs.get(self.key_size, None) 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 # return the AES algorithm to specify in the KDF fields when generating a key
algs = {256: SymmetricKeyAlgorithm.AES128, algs = {256: SymmetricKeyAlgorithm.AES128,
384: SymmetricKeyAlgorithm.AES192, 384: SymmetricKeyAlgorithm.AES192,
512: SymmetricKeyAlgorithm.AES256,
521: SymmetricKeyAlgorithm.AES256} 521: SymmetricKeyAlgorithm.AES256}
return algs.get(self.key_size, None) return algs.get(self.key_size, None)
@@ -343,15 +351,24 @@ class HashAlgorithm(IntEnum):
return self._tuned_count return self._tuned_count
def tune_count(self): def tune_count(self):
start = time.time() start = end = 0
h = self.hasher htd = _hashtunedata[:]
h.update(_hashtunedata)
end = time.time() 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 # 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 # GnuPG tunes for about 100ms, so we'll do that as well
_TIME = 0.100 _TIME = 0.100
ct = int(len(_hashtunedata) * (_TIME / (end - start))) ct = int(len(htd) * (_TIME / (end - start)))
c1 = ((ct >> (ct.bit_length() - 5)) - 16) c1 = ((ct >> (ct.bit_length() - 5)) - 16)
c2 = (ct.bit_length() - 11) c2 = (ct.bit_length() - 11)
c = ((c2 << 4) + c1) c = ((c2 << 4) + c1)
@@ -432,7 +449,7 @@ class KeyFlags(FlagEnum):
Sign = 0x02 Sign = 0x02
#: Signifies that a key may be used to encrypt messages. #: Signifies that a key may be used to encrypt messages.
EncryptCommunications = 0x04 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 EncryptStorage = 0x08
#: Signifies that the private component of a given key may have been split by a secret-sharing mechanism. Split #: 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. #: keys are not currently supported by PGPy.

View File

@@ -788,6 +788,17 @@ class String2Key(Field):
def __nonzero__(self): def __nonzero__(self):
return self.__bool__() 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): def parse(self, packet, iv=True):
self.usage = packet[0] self.usage = packet[0]
del packet[0] del packet[0]
@@ -985,6 +996,13 @@ class PrivKey(PubKey):
return l 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 @abc.abstractmethod
def __privkey__(self): def __privkey__(self):
"""return the requisite *PrivateKey class from the cryptography library""" """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 there is no ciphertext, then the first session key is the session key being used
if len(self.ct) == 0: if len(self.ct) == 0:
return sk return self.symalg, sk
# otherwise, we now need to decrypt the encrypted session key # otherwise, we now need to decrypt the encrypted session key
m = bytearray(_decrypt(bytes(self.ct), sk, self.symalg)) m = bytearray(_decrypt(bytes(self.ct), sk, self.symalg))
@@ -565,7 +565,7 @@ class SKESessionKeyV4(SKESessionKey):
symalg = SymmetricKeyAlgorithm(m[0]) symalg = SymmetricKeyAlgorithm(m[0])
del m[0] del m[0]
return (symalg, bytes(m)) return symalg, bytes(m)
def encrypt_sk(self, passphrase, sk): def encrypt_sk(self, passphrase, sk):
# generate the salt and derive the key to encrypt sk with from it # generate the salt and derive the key to encrypt sk with from it
@@ -867,7 +867,7 @@ class PrivKeyV4(PrivKey, PubKeyV4):
def pubkey(self): def pubkey(self):
# return a copy of ourselves, but just the public half # 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.created = self.created
pk.pkalg = self.pkalg pk.pkalg = self.pkalg

View File

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

View File

@@ -3,6 +3,7 @@
this is where the armorable PGP block objects live this is where the armorable PGP block objects live
""" """
import binascii import binascii
import calendar
import collections import collections
import contextlib import contextlib
import copy import copy
@@ -360,10 +361,9 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
""" """
_data += re.subn(br'\r?\n', b'\r\n', subject)[0] _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.Positive_Cert, SignatureType.CertRevocation, SignatureType.Subkey_Binding,
SignatureType.PrimaryKey_Binding, SignatureType.DirectlyOnKey, SignatureType.KeyRevocation, SignatureType.PrimaryKey_Binding}:
SignatureType.SubkeyRevocation]:
""" """
When a signature is made over a key, the hash data starts with the 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 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: if len(_s) > 0:
_data += b'\x99' + self.int_to_bytes(len(_s), 2) + _s _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 A subkey binding signature
(type 0x18) or primary key binding signature (type 0x19) then hashes (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 _data += b'\x99' + self.int_to_bytes(len(_s), 2) + _s
if self.type in [SignatureType.Generic_Cert, SignatureType.Persona_Cert, SignatureType.Casual_Cert, if self.type in {SignatureType.KeyRevocation, SignatureType.SubkeyRevocation, SignatureType.DirectlyOnKey}:
SignatureType.Positive_Cert, SignatureType.CertRevocation]: """
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 A certification signature (type 0x10 through 0x13) hashes the User
ID being bound to the key into the hash context after the above 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 _s = subject.hashdata
if subject.is_uid: if subject.is_uid:
_data += b'\xb4' + self.int_to_bytes(len(_s), 4) + _s _data += b'\xb4'
if subject.is_ua: else:
_data += b'\xd1' + self.int_to_bytes(len(_s), 4) + _s _data += b'\xd1'
_data += self.int_to_bytes(len(_s), 4) + _s
# if this is a new signature, do update_hlen # if this is a new signature, do update_hlen
if 0 in list(self._signature.signature): if 0 in list(self._signature.signature):
@@ -789,12 +817,18 @@ class PGPMessage(Armorable, PGPObject):
def __str__(self): def __str__(self):
if self.type == 'cleartext': if self.type == 'cleartext':
return "-----BEGIN PGP SIGNED MESSAGE-----\n" \ tmpl = u"-----BEGIN PGP SIGNED MESSAGE-----\n" \
"Hash: {hashes:s}\n\n" \ u"{hhdr:s}\n" \
"{cleartext:s}\n" \ u"{cleartext:s}\n" \
"{signature:s}".format(hashes=','.join(set(s.hash_algorithm.name for s in self.signatures)), u"{signature:s}"
cleartext=self.dash_escape(self.bytes_to_text(self._message)),
signature=super(PGPMessage, self).__str__()) # 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__() return super(PGPMessage, self).__str__()
@@ -1207,8 +1241,8 @@ class PGPKey(Armorable, ParentRef, PGPObject):
if self.is_public: if self.is_public:
return self._key.__bytearray__()[len(self._key.header):] return self._key.__bytearray__()[len(self._key.header):]
publen = len(self._key) - len(self._key.keymaterial) + self._key.keymaterial.publen() pub = self._key.pubkey()
return self._key.__bytearray__()[len(self._key.header):publen] return pub.__bytearray__()[len(pub.header):]
@property @property
def is_expired(self): def is_expired(self):
@@ -1253,6 +1287,15 @@ class PGPKey(Armorable, ParentRef, PGPObject):
"""The :py:obj:`constants.PubKeyAlgorithm` pertaining to this key""" """The :py:obj:`constants.PubKeyAlgorithm` pertaining to this key"""
return self._key.pkalg 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 @property
def magic(self): def magic(self):
return '{:s} KEY BLOCK'.format('PUBLIC' if (isinstance(self._key, Public) and not isinstance(self._key, Private)) else 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 # copy signatures that weren't copied with uids
for sig in self._signatures: 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 # keep connect the two halves using a weak reference
self._sibling = weakref.ref(pub) self._sibling = weakref.ref(pub)
@@ -1425,7 +1469,7 @@ class PGPKey(Armorable, ParentRef, PGPObject):
raise TypeError raise TypeError
def __or__(self, other): def __or__(self, other, from_sib=False):
if isinstance(other, Key) and self._key is None: if isinstance(other, Key) and self._key is None:
self._key = other self._key = other
@@ -1451,9 +1495,13 @@ class PGPKey(Armorable, ParentRef, PGPObject):
raise TypeError("unsupported operand type(s) for |: '{:s}' and '{:s}'" raise TypeError("unsupported operand type(s) for |: '{:s}' and '{:s}'"
"".format(self.__class__.__name__, other.__class__.__name__)) "".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 = 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 return self
@@ -1461,13 +1509,18 @@ class PGPKey(Armorable, ParentRef, PGPObject):
key = super(PGPKey, self).__copy__() key = super(PGPKey, self).__copy__()
key._key = copy.copy(self._key) key._key = copy.copy(self._key)
for id, subkey in self._children.items():
key |= copy.copy(subkey)
for uid in self._uids: for uid in self._uids:
key |= copy.copy(uid) key |= copy.copy(uid)
for id, subkey in self._children.items():
key |= copy.copy(subkey)
for sig in self._signatures: 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) key |= copy.copy(sig)
return key return key
@@ -1801,10 +1854,10 @@ class PGPKey(Armorable, ParentRef, PGPObject):
:keyword compression: A list of preferred compression algorithms, as :py:obj:`~constants.CompressionAlgorithm`. :keyword compression: A list of preferred compression algorithms, as :py:obj:`~constants.CompressionAlgorithm`.
This keyword is ignored for non-self-certifications. This keyword is ignored for non-self-certifications.
:type compression: ``list`` :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. :py:obj:`~datetime.timedelta` of how long after the key was created it should expire.
This keyword is ignored for non-self-certifications. 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. :keyword keyserver: Specify the URI of the preferred key server of the user.
This keyword is ignored for non-self-certifications. This keyword is ignored for non-self-certifications.
:type keyserver: ``str``, ``unicode``, ``bytes`` :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 ] return [ sig for sig in sigs if sig.signer in _ids ]
# collect signature(s) # collect signature(s)
if isinstance(signature, PGPSignature): if signature is None:
if signature.signer != self.fingerprint.keyid and signature.signer not in self.subkeys: if isinstance(subject, PGPMessage):
raise PGPError("Incorrect key. Expected: {:s}".format(signature.signer)) sspairs += [ (sig, subject.message) for sig in _filter_sigs(subject.signatures) ]
sspairs.append((signature, subject))
if isinstance(subject, PGPMessage): if isinstance(subject, (PGPUID, PGPKey)):
sspairs += [ (sig, subject.message) for sig in _filter_sigs(subject.signatures) ] sspairs += [ (sig, subject) for sig in _filter_sigs(subject.__sig__) ]
if isinstance(subject, (PGPUID, PGPKey)): if isinstance(subject, PGPKey):
sspairs += [ (sig, subject) for sig in _filter_sigs(subject.__sig__) ] # 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): elif signature.signer in {self.fingerprint.keyid} | set(self.subkeys):
# user ids sspairs += [(signature, subject)]
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 len(sspairs) == 0: if len(sspairs) == 0:
raise PGPError("No signatures to verify") raise PGPError("No signatures to verify")
@@ -2074,7 +2126,7 @@ class PGPKey(Armorable, ParentRef, PGPObject):
# finally, start verifying signatures # finally, start verifying signatures
sigv = SignatureVerification() sigv = SignatureVerification()
for sig, subj in sspairs: 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}. " warnings.warn("Signature was signed with this key's subkey: {:s}. "
"Verifying with subkey...".format(sig.signer), "Verifying with subkey...".format(sig.signer),
stacklevel=2) stacklevel=2)

View File

@@ -64,10 +64,10 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
@staticmethod @staticmethod
def is_ascii(text): def is_ascii(text):
if isinstance(text, six.string_types): 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)): 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 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: # the re.VERBOSE flag allows for:
# - whitespace is ignored except when in a character class or escaped # - 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 # - 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) (^-{5}BEGIN\ PGP\ SIGNED\ MESSAGE-{5}(?:\r?\n)
(Hash:\ (?P<hashes>[A-Za-z0-9\-,]+)(?:\r?\n){2})? (Hash:\ (?P<hashes>[A-Za-z0-9\-,]+)(?:\r?\n){2})?
(?P<cleartext>(.*\n)+)(?:\r?\n) (?P<cleartext>(.*\n)+)(?:\r?\n)
)? )?
# armor header line; capture the variable part of the magic text # 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 # try to capture all the headers into one capture group
# if this doesn't match, m['headers'] will be None # 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, # capture all lines of the body, up to 76 characters long,
# including the newline, and the pad character(s) # including the newline, and the pad character(s)
(?P<body>([A-Za-z0-9+/]{1,75}={,2}(?:\r?\n))+) (?P<body>([A-Za-z0-9+/]{1,75}={,2}(?:\r?\n))+)
# capture the armored CRC24 value # 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 # finally, capture the armor tail line, which must match the armor header line
^-{5}END\ PGP\ (?P=magic)-{5}$(?:\r?\n)? ^-{5}END\ PGP\ (?P=magic)-{5}(?:\r?\n)?
""", """, text, flags=re.MULTILINE | re.VERBOSE)
text, flags=re.MULTILINE | re.VERBOSE)
if m is None: # pragma: no cover if m is None: # pragma: no cover
raise ValueError("Expected: ASCII-armored PGP data") raise ValueError("Expected: ASCII-armored PGP data")
@@ -638,15 +637,20 @@ class FlagEnumMeta(EnumMeta):
return self & other return self & other
class FlagEnum(six.with_metaclass(FlagEnumMeta, IntEnum)): if six.PY2:
pass class FlagEnum(IntEnum):
__metaclass__ = FlagEnumMeta
else:
namespace = FlagEnumMeta.__prepare__('FlagEnum', (IntEnum,))
FlagEnum = FlagEnumMeta('FlagEnum', (IntEnum,), namespace)
class Fingerprint(str): class Fingerprint(str):
""" """
A subclass of ``str``. Can be compared using == and != to ``str``, ``unicode``, and other :py:obj:`Fingerprint` instances. 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 @property
def keyid(self): def keyid(self):

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
"""PGPy conftest"""
import pytest import pytest
import contextlib import contextlib
import functools
import glob import glob
import os import os
import re import re
import six import select
import subprocess import subprocess
import sys import sys
import time import time
@@ -56,81 +56,74 @@ def _run(bin, *binargs, **pkw):
return cmdo, cmde 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_bin = _which('gpg2')
_gpg_args = ['--options', './pgpy.gpg.conf', '--expert', '--status-fd', '1'] _gpg_args = ('--options', './pgpy.gpg.conf', '--expert', '--status-fd')
_gpg_env = os.environ.copy() _gpg_env = {}
_gpg_env['GNUPGHOME'] = os.path.abspath(os.path.abspath('tests/testdata')) _gpg_env['GNUPGHOME'] = os.path.abspath(os.path.abspath('tests/testdata'))
_gpg_kwargs = dict() _gpg_kwargs = dict()
_gpg_kwargs['cwd'] = 'tests/testdata' _gpg_kwargs['cwd'] = 'tests/testdata'
_gpg_kwargs['env'] = _gpg_env _gpg_kwargs['env'] = _gpg_env
_gpg_kwargs['stdout'] = subprocess.PIPE _gpg_kwargs['stdout'] = subprocess.PIPE
_gpg_kwargs['stderr'] = subprocess.STDOUT _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 # 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() @pytest.fixture()
def gpg_import(): def gpg_import():
@contextlib.contextmanager @contextlib.contextmanager
def _gpg_import(*keypaths): 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 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 gpg_ver >= '2.1':
if not os.path.exists('tests/testdata/private-keys-v1.d'): if not os.path.exists('tests/testdata/private-keys-v1.d'):
os.mkdir('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: try:
yield gpgo yield gpgo
@@ -139,7 +132,6 @@ def gpg_import():
[os.remove(f) for f in glob.glob('tests/testdata/testkeys.*')] [os.remove(f) for f in glob.glob('tests/testdata/testkeys.*')]
if gpg_ver >= '2.1': if gpg_ver >= '2.1':
[os.remove(f) for f in glob.glob('tests/testdata/private-keys-v1.d/*')] [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) time.sleep(0.5)
@@ -149,9 +141,7 @@ def gpg_import():
@pytest.fixture() @pytest.fixture()
def gpg_check_sigs(): def gpg_check_sigs():
def _gpg_check_sigs(*keyids): def _gpg_check_sigs(*keyids):
gpg_args = _gpg_args + ['--check-sigs'] + list(keyids) gpgc, gpgo, gpge = _gpg('--check-sigs', *keyids)
gpg_kwargs = _gpg_kwargs.copy()
gpgo, _ = _run(_gpg_bin, *gpg_args, **gpg_kwargs)
return 'sig-' not in gpgo return 'sig-' not in gpgo
return _gpg_check_sigs return _gpg_check_sigs
@@ -163,11 +153,11 @@ def gpg_verify():
r'^\[GNUPG:\] VALIDSIG (?:[0-9A-F]{,24})\1', flags=re.MULTILINE | re.DOTALL) r'^\[GNUPG:\] VALIDSIG (?:[0-9A-F]{,24})\1', flags=re.MULTILINE | re.DOTALL)
def _gpg_verify(gpg_subjpath, gpg_sigpath=None, keyid=None): 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 ] rargs = [gpg_sigpath, gpg_subjpath] if gpg_sigpath is not None else [gpg_subjpath,]
gpg_kwargs = _gpg_kwargs.copy()
gpgo, _ = _run(_gpg_bin, *gpg_args, **gpg_kwargs)
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: if keyid is not None:
return keyid in sigs return keyid in sigs
@@ -181,53 +171,56 @@ def gpg_verify():
def gpg_decrypt(): def gpg_decrypt():
sfd_decrypt = re.compile(r'^\[GNUPG:\] BEGIN_DECRYPTION\n' sfd_decrypt = re.compile(r'^\[GNUPG:\] BEGIN_DECRYPTION\n'
r'^\[GNUPG:\] DECRYPTION_INFO \d+ \d+\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'^\[GNUPG:\] PLAINTEXT_LENGTH \d+\n'
r'(?P<text>(?:.|\n)*)'
r'\[GNUPG:\] DECRYPTION_OKAY\n' r'\[GNUPG:\] DECRYPTION_OKAY\n'
r'^\[GNUPG:\] GOODMDC\n' r'(?:^\[GNUPG:\] GOODMDC\n)?'
r'^\[GNUPG:\] END_DECRYPTION', flags=re.MULTILINE) r'^\[GNUPG:\] END_DECRYPTION', flags=re.MULTILINE)
def _gpg_decrypt(encmsgpath, passphrase=None, keyid=None): def _gpg_decrypt(encmsgpath, passphrase=None, keyid=None):
gpg_args = [_gpg_bin] + _gpg_args[:] a = []
gpg_kwargs = _gpg_kwargs.copy()
gpg_kwargs['stderr'] = subprocess.PIPE
_comargs = ()
if passphrase is not None: if passphrase is not None:
gpg_args += ['--batch', '--passphrase-fd', '0'] # create a pipe to send the passphrase to GnuPG through
gpg_kwargs['stdin'] = subprocess.PIPE pfdr, pfdw = os.pipe()
_comargs = (passphrase.encode(),)
if keyid is not None: # write the passphrase to the pipe buffer right away
gpg_args += ['--recipient', keyid] 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) a.extend(['--batch', '--passphrase-fd', str(pfdr)])
gpgo, gpge = gpgdec.communicate(*_comargs)
gpgdec.wait()
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 return _gpg_decrypt
@pytest.fixture @pytest.fixture
def gpg_print(): def gpg_print():
sfd_text = re.compile(r'^\[GNUPG:\] PLAINTEXT (?:62|74|75) .*\n' sfd_text = re.compile(r'^\[GNUPG:\] PLAINTEXT (?:62|74|75) (?P<tstamp>\d+) (?P<fname>.*)\n'
r'^\[GNUPG:\] PLAINTEXT_LENGTH (?P<len>\d+)\n' r'^\[GNUPG:\] PLAINTEXT_LENGTH (?P<len>\d+)\n', re.MULTILINE)
r'^(?P<text>(.|\n)*)', re.MULTILINE)
gpg_text = re.compile(r'(?:- gpg control packet\n)?(?P<text>.*)', re.MULTILINE | re.DOTALL)
def _gpg_print(infile): def _gpg_print(infile):
gpg_args = _gpg_args + ['-o-', infile] gpgc, gpgo, gpge = _gpg('-o-', infile, stderr=subprocess.PIPE)
gpg_kwargs = _gpg_kwargs.copy() status = sfd_text.match(gpgc)
gpg_kwargs['stderr'] = subprocess.PIPE tlen = len(gpgo) if status is None else int(status.group('len'))
gpgo, _ = _run(_gpg_bin, *gpg_args, **gpg_kwargs) return gpg_text.match(gpgo).group('text')[:tlen]
return sfd_text.match(gpgo).group('text')
return _gpg_print return _gpg_print
@@ -235,12 +228,8 @@ def gpg_print():
@pytest.fixture @pytest.fixture
def gpg_keyid_file(): def gpg_keyid_file():
def _gpg_keyid_file(infile): def _gpg_keyid_file(infile):
gpg_args = _gpg_args + ['--list-packets', infile] c, o, e = _gpg('--list-packets', infile)
gpg_kwargs = _gpg_kwargs.copy() return re.findall(r'^\s+keyid: ([0-9A-F]+)', o, flags=re.MULTILINE)
gpgo, _ = _run(_gpg_bin, *gpg_args, **gpg_kwargs)
return re.findall(r'^\s+keyid: ([0-9A-F]+)', gpgo, flags=re.MULTILINE)
return _gpg_keyid_file return _gpg_keyid_file
@@ -257,6 +246,8 @@ def pgpdump():
# pytest_configure # pytest_configure
# called after command line options have been parsed and all plugins and initial conftest files been loaded. # called after command line options have been parsed and all plugins and initial conftest files been loaded.
def pytest_configure(config): def pytest_configure(config):
print("== PGPy Test Suite ==")
# ensure commands we need exist # ensure commands we need exist
for cmd in ['gpg2', 'pgpdump']: for cmd in ['gpg2', 'pgpdump']:
if _which(cmd) is None: if _which(cmd) is None:
@@ -270,25 +261,9 @@ def pytest_configure(config):
v, _ = _run(_which('pgpdump'), '-v', stderr=subprocess.STDOUT) v, _ = _run(_which('pgpdump'), '-v', stderr=subprocess.STDOUT)
pgpdump_ver.parse(v.split(' ')[2].strip(',')) 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("Working Directory: " + os.getcwd())
print("Using OpenSSL " + str(openssl_ver)) print("Using OpenSSL " + str(openssl_ver))
print("Using GnuPG " + str(gpg_ver)) print("Using GnuPG " + str(gpg_ver))
print("Using pgpdump " + str(pgpdump_ver)) print("Using pgpdump " + str(pgpdump_ver))
print("") 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.types',
'pgpy.packet.subpackets.userattribute'] 'pgpy.packet.subpackets.userattribute']
def get_module_objs(module): def get_module_objs(module):
# return a set of strings that represent the names of objects defined in that 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(): def test_pgpy_all():
import pgpy import pgpy
# just check that everything in pgpy.__all__ is actually there # just check that everything in pgpy.__all__ is actually there
assert set(pgpy.__all__) <= { n for n, _ in inspect.getmembers(pgpy) } assert set(pgpy.__all__) <= { n for n, _ in inspect.getmembers(pgpy) }
@pytest.mark.parametrize('modname', modules) @pytest.mark.parametrize('modname', modules)
def test_exports(modname): def test_exports(modname):
module = importlib.import_module(modname) module = importlib.import_module(modname)
modall = getattr(module, '__all__', None) assert get_module_all(module) == get_module_objs(module)
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)

View File

@@ -1,52 +1,69 @@
""" test field parsing """ test field parsing
""" """
from itertools import product import pytest
import itertools
from pgpy.constants import HashAlgorithm from pgpy.constants import HashAlgorithm
from pgpy.constants import String2KeyType from pgpy.constants import String2KeyType
from pgpy.constants import SymmetricKeyAlgorithm from pgpy.constants import SymmetricKeyAlgorithm
from pgpy.packet.types import Header
from pgpy.packet.fields import String2Key from pgpy.packet.fields import String2Key
from pgpy.packet.types import Header
from pgpy.packet.subpackets import Signature from pgpy.packet.subpackets import Signature
from pgpy.packet.subpackets import UserAttribute from pgpy.packet.subpackets import UserAttribute
from pgpy.packet.subpackets.types import Header as HeaderSP from pgpy.packet.subpackets.types import Header as HeaderSP
from pgpy.packet.subpackets.types import Opaque as OpaqueSP 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): class TestHeaders(object):
params = { @pytest.mark.parametrize('pheader', pkt_headers)
'pheader': [# new format def test_packet_header(self, pheader):
# 1 byte length - 191 b = pheader[:]
bytearray(b'\xc2' + b'\xbf' + (b'\x00' * 191) + b'\xca\xfe\xba\xbe'), h = Header()
# 2 byte length - 192 h.parse(pheader)
bytearray(b'\xc2' + b'\xc0\x00' + (b'\x00' * 192) + b'\xca\xfe\xba\xbe'),
# 2 byte length - 8383 assert h.tag == 0x02
bytearray(b'\xc2' + b'\xdf\xff' + (b'\x00' * 8383) + b'\xca\xfe\xba\xbe'), assert h.length == len(pheader) - len(_trailer)
# 5 byte length - 8384 assert pheader[h.length:] == _trailer
bytearray(b'\xc2' + b'\xff\x00\x00 \xc0' + (b'\x00' * 8384) + b'\xca\xfe\xba\xbe'), assert len(h) == len(b) - len(pheader)
# old format assert h.__bytes__() == b[:len(h)]
# 1 byte length - 255
bytearray(b'\x88' + b'\xff' + (b'\x00' * 255) + b'\xca\xfe\xba\xbe'), @pytest.mark.parametrize('spheader', subpkt_headers)
# 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)),
]
}
def test_subpacket_header(self, spheader): def test_subpacket_header(self, spheader):
h = HeaderSP() h = HeaderSP()
h.parse(spheader) h.parse(spheader)
@@ -54,16 +71,6 @@ class TestHeaders(object):
assert 65537 > h.length > 1 assert 65537 > h.length > 1
assert len(h) == len(h.__bytes__()) 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 = { _sspclasses = {
# 0x00: 'Opaque', # 0x00: 'Opaque',
@@ -112,155 +119,158 @@ _sspclasses = {
0x6d: 'Opaque', 0x6d: 'Opaque',
0x6e: 'Opaque', 0x6e: 'Opaque',
} }
_uaspclasses = { _uaspclasses = {
0x01: 'Image' 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): class TestSignatureSubPackets(object):
params = { @pytest.mark.parametrize('sigsubpacket', sig_subpkts)
'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',
]
]
}
def test_load(self, sigsubpacket): def test_load(self, sigsubpacket):
spb = sigsubpacket[:] spb = sigsubpacket[:]
sp = Signature(spb) sp = Signature(spb)
assert spb == b'\xca\xfe\xba\xbe' assert spb == _trailer
assert len(sp) == len(sigsubpacket) - 4 assert len(sp) == len(sigsubpacket) - len(_trailer)
assert len(sp) == len(sp.__bytes__()) assert len(sp) == len(sp.__bytes__())
assert sp.__bytes__() == bytes(sigsubpacket[:-4]) assert sp.__bytes__() == bytes(sigsubpacket[:-len(_trailer)])
if sp.header.typeid in _sspclasses: if sp.header.typeid in _sspclasses:
assert sp.__class__.__name__ == _sspclasses[sp.header.typeid] assert sp.__class__.__name__ == _sspclasses[sp.header.typeid]
else: else:
assert isinstance(sp, OpaqueSP) 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): class TestUserAttributeSubPackets(object):
params = { @pytest.mark.parametrize('uasubpacket', ua_subpkts)
'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'
]
],
}
def test_load(self, uasubpacket): def test_load(self, uasubpacket):
spb = uasubpacket[:] spb = uasubpacket[:]
sp = UserAttribute(spb) sp = UserAttribute(spb)
assert spb == b'\xca\xfe\xba\xbe' assert spb == _trailer
assert len(sp) == len(uasubpacket) - 4 assert len(sp) == len(uasubpacket) - len(_trailer)
assert len(sp) == len(sp.__bytes__()) assert len(sp) == len(sp.__bytes__())
assert sp.__bytes__() == uasubpacket[:-4] assert sp.__bytes__() == uasubpacket[:-len(_trailer)]
if sp.header.typeid in _uaspclasses: if sp.header.typeid in _uaspclasses:
assert sp.__class__.__name__ == _uaspclasses[sp.header.typeid] assert sp.__class__.__name__ == _uaspclasses[sp.header.typeid]
@@ -269,31 +279,32 @@ class TestUserAttributeSubPackets(object):
assert isinstance(sp, OpaqueSP) 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): class TestString2Key(object):
params = {'sis2k': [ (bytearray(i) + @pytest.mark.parametrize('sis2k', sis2ks)
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
]}
def test_simple_string2key(self, sis2k): def test_simple_string2key(self, sis2k):
b = sis2k[:] b = sis2k[:]
s = String2Key() s = String2Key()
@@ -307,9 +318,9 @@ class TestString2Key(object):
assert s.halg in HashAlgorithm assert s.halg in HashAlgorithm
assert s.encalg in SymmetricKeyAlgorithm assert s.encalg in SymmetricKeyAlgorithm
assert s.specifier == String2KeyType.Simple 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): def test_salted_string2key(self, sas2k):
b = sas2k[:] b = sas2k[:]
s = String2Key() s = String2Key()
@@ -323,9 +334,10 @@ class TestString2Key(object):
assert s.halg in HashAlgorithm assert s.halg in HashAlgorithm
assert s.encalg in SymmetricKeyAlgorithm assert s.encalg in SymmetricKeyAlgorithm
assert s.specifier == String2KeyType.Salted assert s.specifier == String2KeyType.Salted
assert s.salt == b'\xCA\xFE\xBA\xBE\xCA\xFE\xBA\xBE' assert s.salt == _salt
assert s.iv == b'\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF' assert s.iv == _iv
@pytest.mark.parametrize('is2k', is2ks)
def test_iterated_string2key(self, is2k): def test_iterated_string2key(self, is2k):
b = is2k[:] b = is2k[:]
s = String2Key() s = String2Key()
@@ -339,20 +351,6 @@ class TestString2Key(object):
assert s.halg in HashAlgorithm assert s.halg in HashAlgorithm
assert s.encalg in SymmetricKeyAlgorithm assert s.encalg in SymmetricKeyAlgorithm
assert s.specifier == String2KeyType.Iterated 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.count == 2048
assert s.iv == b'\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF' assert s.iv == _iv
# TODO: this
# class TestKeyMaterial(object):
# params = {
# 'pkt': [],
# }
#
# ids = {
# 'test_keymaterial': [],
# }
#
# def test_keymaterial(self, pkt):
# pass

View File

@@ -3,22 +3,7 @@
""" """
import pytest import pytest
import glob from pgpy.types import PGPObject
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(': ')
text = { text = {
# some basic utf-8 test strings - these should all pass # some basic utf-8 test strings - these should all pass
@@ -62,22 +47,15 @@ class FakePGPObject(PGPObject):
class TestPGPObject(object): 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.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): def test_text_to_bytes(self, text):
pgpo = FakePGPObject.new(text) pgpo = FakePGPObject.new(text)
assert pgpo.__bytearray__() == bytearray(b'_fake_') + bytearray(text, 'utf-8') assert pgpo.__bytearray__() == bytearray(b'_fake_') + bytearray(text, 'utf-8')
@pytest.mark.regression(issue=154) @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): def test_text_to_bytes_encodings(self, encoded_text):
pgpo = FakePGPObject.new(encoded_text) pgpo = FakePGPObject.new(encoded_text)
# this should fail # this should fail

View File

@@ -5,14 +5,13 @@ import pytest
import glob import glob
import os import os
from pgpy.packet import Packet from pgpy.packet import Packet
from pgpy.packet import PubKeyV4, PubSubKeyV4, PrivKeyV4, PrivSubKeyV4 from pgpy.packet import PubKeyV4, PubSubKeyV4, PrivKeyV4, PrivSubKeyV4
from pgpy.packet import Opaque from pgpy.packet import Opaque
import pgpy.packet.fields # import pgpy.packet.fields
_trailer = b'\xde\xca\xff\xba\xdd'
_pclasses = { _pclasses = {
(0x01, 3): 'PKESessionKeyV3', (0x01, 3): 'PKESessionKeyV3',
(0x02, 4): 'SignatureV4', (0x02, 4): 'SignatureV4',
@@ -36,39 +35,35 @@ _pclasses = {
def binload(f): def binload(f):
with open(f, 'rb') as ff: 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): class TestPacket(object):
params = { @pytest.mark.parametrize('packet', pktfiles, ids=[os.path.basename(f) for f in pktfiles])
# '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]*')])
}
def test_load(self, packet): def test_load(self, packet):
if packet in skip_files: if packet in skip_files:
pytest.skip("not implemented yet") pytest.skip("not implemented yet")
b = binload(packet) + b'\xca\xfe\xba\xbe' b = binload(packet) + _trailer
_b = b[:] _b = b[:]
p = Packet(_b) p = Packet(_b)
# parsed all bytes # parsed all bytes
assert _b == b'\xca\xfe\xba\xbe' assert _b == _trailer
# length is computed correctly # length is computed correctly
assert p.header.length + len(p.header) == len(p) assert p.header.length + len(p.header) == len(p)
assert len(p) == len(b) - 4 assert len(p) == len(b) - len(_trailer)
assert len(p.__bytes__()) == len(b) - 4 assert len(p.__bytes__()) == len(b) - len(_trailer)
# __bytes__ output is correct # __bytes__ output is correct
assert p.__bytes__() == b[:-4] assert p.__bytes__() == b[:-len(_trailer)]
# instantiated class is what we expected # instantiated class is what we expected
if hasattr(p.header, 'version') and (p.header.tag, p.header.version) in _pclasses: 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 glob
import os import os
from datetime import datetime from datetime import datetime
from pgpy.constants import CompressionAlgorithm
from pgpy.constants import HashAlgorithm from pgpy.constants import HashAlgorithm
from pgpy.constants import KeyFlags
from pgpy.constants import PubKeyAlgorithm from pgpy.constants import PubKeyAlgorithm
from pgpy.constants import SignatureType from pgpy.constants import SignatureType
from pgpy.constants import SymmetricKeyAlgorithm
from pgpy.pgp import PGPKey from pgpy.pgp import PGPKey
from pgpy.pgp import PGPMessage from pgpy.pgp import PGPMessage
from pgpy.pgp import PGPSignature 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 # generic block tests
class TestBlocks(object): class TestBlocks(object):
params = { @pytest.mark.parametrize('block', blocks, ids=[os.path.basename(f) for f in blocks])
'block': sorted(glob.glob('tests/testdata/blocks/*.asc')) def test_load_blob(self, block):
}
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):
with open(block) as bf: with open(block) as bf:
bc = bf.read() bc = bf.read()
@@ -238,7 +312,7 @@ class TestBlocks(object):
p = PGPMessage() p = PGPMessage()
else: else:
pytest.skip("not ready for this one") pytest.skip("not ready for file '{}'".format(os.path.basename(block)))
assert False assert False
# load ASCII # load ASCII
@@ -248,8 +322,8 @@ class TestBlocks(object):
# assert str(p) == bc # assert str(p) == bc
# now check attrs # now check attrs
assert block in self.attrs assert block in block_attrs
for attr, val in self.attrs[block]: for attr, val in block_attrs[block]:
attrval = getattr(p, attr) attrval = getattr(p, attr)
# this is probably more helpful than just 'assert attrval == val' # this is probably more helpful than just 'assert attrval == val'
if attrval != val: if attrval != val:

View File

@@ -1,14 +1,10 @@
""" test the functionality of PGPKeyring """ test the functionality of PGPKeyring
""" """
import pytest import pytest
import glob import glob
import os import os
import six import six
from pgpy.packet import Packet
from pgpy import PGPKey from pgpy import PGPKey
from pgpy import PGPKeyring from pgpy import PGPKeyring
from pgpy import PGPMessage from pgpy import PGPMessage
@@ -16,25 +12,8 @@ from pgpy import PGPSignature
from pgpy import PGPUID from pgpy import PGPUID
from pgpy.types import Fingerprint from pgpy.types import Fingerprint
@pytest.fixture from conftest import gpg_ver
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')
@pytest.fixture @pytest.fixture
def abe_image(): def abe_image():
@@ -45,14 +24,11 @@ def abe_image():
return PGPUID.new(abebytes) return PGPUID.new(abebytes)
class TestPGPMessage(object): _msgfiles = sorted(glob.glob('tests/testdata/messages/*.asc'))
params = {
'msgfile': sorted(glob.glob('tests/testdata/messages/*.asc')),
}
ids = {
'test_load_from_file': [ os.path.basename(f) for f in params['msgfile'] ],
}
class TestPGPMessage(object):
@pytest.mark.parametrize('msgfile', _msgfiles, ids=[os.path.basename(f) for f in _msgfiles])
def test_load_from_file(self, msgfile): def test_load_from_file(self, msgfile):
# TODO: figure out a good way to verify that all went well here, because # TODO: figure out a good way to verify that all went well here, because
# PGPy reorders signatures sometimes, and also unwraps compressed messages # PGPy reorders signatures sometimes, and also unwraps compressed messages
@@ -65,6 +41,31 @@ class TestPGPMessage(object):
assert len(str(msg)) == len(mt) 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): class TestPGPUID(object):
def test_userid(self, abe): def test_userid(self, abe):
assert abe.name == 'Abraham Lincoln' 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>') 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): 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): # TODO: maybe store the fingerprint instead of relying on a particular version of GnuPG...?
key, _ = PGPKey.from_file(self.kf) 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): @pytest.mark.parametrize('kf', _keyfiles, ids=[os.path.basename(f) for f in _keyfiles])
with open(self.kf, 'r') as tkf: def test_load_from_str(self, kf, gpg_keyid_file):
with open(kf, 'r') as tkf:
key, _ = PGPKey.from_blob(tkf.read()) 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) @pytest.mark.regression(issue=140)
def test_load_from_bytes(self, gpg_keyid_file): @pytest.mark.parametrize('kf', _keyfiles, ids=[os.path.basename(f) for f in _keyfiles])
with open(self.kf, 'rb') as tkf: def test_load_from_bytes(self, kf, gpg_keyid_file):
with open(kf, 'rb') as tkf:
key, _ = PGPKey.from_blob(tkf.read()) 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) @pytest.mark.regression(issue=140)
def test_load_from_bytearray(self, gpg_keyid_file): @pytest.mark.parametrize('kf', _keyfiles, ids=[os.path.basename(f) for f in _keyfiles])
tkb = bytearray(os.stat(self.kf).st_size) def test_load_from_bytearray(self, kf, gpg_keyid_file):
with open(self.kf, 'rb') as tkf: tkb = bytearray(os.stat(kf).st_size)
with open(kf, 'rb') as tkf:
tkf.readinto(tkb) tkf.readinto(tkb)
key, _ = PGPKey.from_blob(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') @pytest.fixture(scope='module')

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,149 +1,195 @@
""" I've got 99 problems but regression testing ain't one """ I've got 99 problems but regression testing ain't one
""" """
import pytest
import tempfile
import warnings
class TestRegressions(object): @pytest.mark.regression(issue=56)
# regression tests for actions def test_reg_bug_56(gpg_import, gpg_verify):
def test_reg_bug_56(self, write_clean, gpg_import, gpg_verify): # some imports only used by this regression test
# some imports only used by this regression test import hashlib
import hashlib from datetime import datetime
from datetime import datetime
from pgpy.pgp import PGPKey from pgpy.pgp import PGPKey
from pgpy.pgp import PGPSignature from pgpy.pgp import PGPSignature
from pgpy.constants import HashAlgorithm from pgpy.constants import HashAlgorithm
from pgpy.constants import PubKeyAlgorithm from pgpy.constants import PubKeyAlgorithm
from pgpy.constants import SignatureType from pgpy.constants import SignatureType
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric import padding
# do a regression test on issue #56 # do a regression test on issue #56
# re-create a signature that would have been encoded improperly as with 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 # and see if it fails to verify or not
# this was the old seckeys/TestRSA-2048.key # this was the old seckeys/TestRSA-2048.key
sec = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" \ sec = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" \
"\n" \ "\n" \
"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\n" \
"iz54ZFyNdQ==\n" \ "iz54ZFyNdQ==\n" \
"=WLpc\n" \ "=WLpc\n" \
"-----END PGP PRIVATE KEY BLOCK-----\n" "-----END PGP PRIVATE KEY BLOCK-----\n"
pub = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" \ pub = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" \
"\n" \ "\n" \
"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\n" \
"dQ==\n" \ "dQ==\n" \
"=lqIH\n" \ "=lqIH\n" \
"-----END PGP PUBLIC KEY BLOCK-----\n" "-----END PGP PUBLIC KEY BLOCK-----\n"
# load the keypair above # load the keypair above
sk = PGPKey() sk = PGPKey()
sk.parse(sec) sk.parse(sec)
pk = PGPKey() pk = PGPKey()
pk.parse(pub) 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, sig = PGPSignature.new(SignatureType.BinaryDocument, PubKeyAlgorithm.RSAEncryptOrSign, HashAlgorithm.SHA512,
sk.fingerprint.keyid) sk.fingerprint.keyid)
sig._signature.subpackets['h_CreationTime'][-1].created = datetime(2014, 8, 6, 23, 28, 51) sig._signature.subpackets['h_CreationTime'][-1].created = datetime(2014, 8, 6, 23, 28, 51)
sig._signature.subpackets.update_hlen() sig._signature.subpackets.update_hlen()
hdata = sig.hashdata(sigsubject) hdata = sig.hashdata(sigsubject)
sig._signature.hash2 = hashlib.new('sha512', hdata).digest()[:2] sig._signature.hash2 = hashlib.new('sha512', hdata).digest()[:2]
# create the signature # create the signature
signer = sk.__key__.__privkey__().signer(padding.PKCS1v15(), hashes.SHA512()) signer = sk.__key__.__privkey__().signer(padding.PKCS1v15(), hashes.SHA512())
signer.update(hdata) signer.update(hdata)
sig._signature.signature.from_signer(signer.finalize()) sig._signature.signature.from_signer(signer.finalize())
sig._signature.update_hlen() sig._signature.update_hlen()
# check encoding # check encoding
assert sig._signature.signature.md_mod_n.to_mpibytes()[2:3] != b'\x00' assert sig._signature.signature.md_mod_n.to_mpibytes()[2:3] != b'\x00'
# with PGPy # with PGPy
assert pk.verify(sigsubject, sig) assert pk.verify(sigsubject, sig)
# with GnuPG # with GnuPG
with write_clean('tests/testdata/subj', 'w', sigsubject.decode('latin-1')), \ with tempfile.NamedTemporaryFile('w+') as subjf, \
write_clean('tests/testdata/subj.asc', 'w', str(sig)), \ tempfile.NamedTemporaryFile('w+') as sigf, \
write_clean('tests/testdata/pub.asc', 'w', str(pk)), \ tempfile.NamedTemporaryFile('w+') as pubf:
gpg_import('pub.asc'): subjf.write(sigsubject.decode('latin-1'))
assert gpg_verify('subj', 'subj.asc') 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] [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 skipsdist = True
[pytest] [pytest]
addopts = -vv -s --color=yes addopts = -vv -r a -s --color=yes
norecursedirs = testdata norecursedirs = testdata
[flake8] [flake8]
@@ -21,6 +21,7 @@ deps =
singledispatch singledispatch
pytest pytest
pytest-cov pytest-cov
pytest-ordering
install_command = pip install {opts} --no-cache-dir {packages} install_command = pip install {opts} --no-cache-dir {packages}
commands = commands =
@@ -32,6 +33,12 @@ commands =
pip install -e . pip install -e .
rm -rf PGPy.egg-info rm -rf PGPy.egg-info
[testenv:setup36]
recreate = True
basepython = python3.6
whitelist_externals = {[test-setup]whitelist_externals}
commands = {[test-setup]commands}
[testenv:setup35] [testenv:setup35]
recreate = True recreate = True
basepython = python3.5 basepython = python3.5
@@ -58,7 +65,7 @@ whitelist_externals = {[test-setup]whitelist_externals}
commands = {[test-setup]commands} commands = {[test-setup]commands}
[testenv:pep8] [testenv:pep8]
basepython = python3.5 basepython = python3.6
deps = deps =
flake8 flake8
pep8-naming pep8-naming