pax_global_header 0000666 0000000 0000000 00000000064 13145172200 0014505 g ustar 00root root 0000000 0000000 52 comment=bdd04fba1e7e0839fe8f7825472f17cdabd7be8d
python-pgpy-0.4.3/ 0000775 0000000 0000000 00000000000 13145172200 0014007 5 ustar 00root root 0000000 0000000 python-pgpy-0.4.3/.coveragerc 0000664 0000000 0000000 00000000514 13145172200 0016130 0 ustar 00root root 0000000 0000000 [run]
branch = False
[report]
exclude_lines =
# keep the standard pragma
pragma: no cover
# skip abstract methods
@(abc\.)?abstract
# Python 2.x compatibility stuff
if six.PY2:
if six.PY3:
def __nonzero__
# debug-only code
def __repr__
# defensive code
raise NotImplementedError
python-pgpy-0.4.3/.gitignore 0000664 0000000 0000000 00000001165 13145172200 0016002 0 ustar 00root root 0000000 0000000 # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
*.pot
# Sphinx documentation
docs/_build/
.idea
random_seed
/tests/gnupghome/private-keys-v1.d/*
/tests/gnupghome/pubring.kbx*
python-pgpy-0.4.3/.travis.yml 0000664 0000000 0000000 00000002767 13145172200 0016134 0 ustar 00root root 0000000 0000000 dist: trusty
os:
- linux
- osx
sudo: required
language: python
python:
- "3.6"
- "3.5"
- "3.4"
- "2.7"
- "pypy"
- "pypy3"
matrix:
include:
# add a pep8 test
- python: 3.6
env: TOXENV=pep8
# test setup.py using each tested version
- python: 3.6
env: TOXENV=setup36
- python: 3.5
env: TOXENV=setup35
- python: 3.4
env: TOXENV=setup34
- python: 3.3
env: TOXENV=setup33
- python: 2.7
env: TOXENV=setup27
# do some tests with LC_ALL=C to check for locale variance
- python: 3.6
env: LC_ALL=C
- python: 2.7
env: LC_ALL=C
allow_failures:
# pep8 failures shouldn't be considered fatal
- env: TOXENV=pep8
# pypy and pypy3 tests are just for fun
- python: "pypy"
- python: "pypy3"
# osx, until it's working
- os: osx
# install requirements
install:
- sed -i -e 's/^/#/' tests/gnupghome/gpg-agent.conf
- ./install_dependencies.${TRAVIS_OS_NAME}.sh
# ensure tox and coveralls are installed
- pip install tox python-coveralls
# set TOXENV if it isn't yet
before_script:
- if [[ -z "$TOXENV" ]]; then export TOXENV=py${TRAVIS_PYTHON_VERSION//.}; fi
- if [[ "${TRAVIS_PYTHON_VERSION}" == 'pypy' ]]; then export TOXENV=pypy; fi
- if [[ "${TRAVIS_PYTHON_VERSION}" == 'pypy3' ]]; then export TOXENV=pypy3; fi
# run tox
script:
- ./tox.sh
# and report coverage to coveralls, but only if this was a pytest run
after_success:
- if [[ "${TOXENV}" == "py"* ]]; then coveralls; fi
python-pgpy-0.4.3/LICENSE 0000664 0000000 0000000 00000002720 13145172200 0015015 0 ustar 00root root 0000000 0000000 Copyright (c) 2014 Michael Greene
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. python-pgpy-0.4.3/MANIFEST.in 0000664 0000000 0000000 00000000164 13145172200 0015546 0 ustar 00root root 0000000 0000000 include LICENSE
include README.rst
include requirements.txt
include requirements-test.txt
recursive-exclude * *.pyc
python-pgpy-0.4.3/README.rst 0000664 0000000 0000000 00000003573 13145172200 0015506 0 ustar 00root root 0000000 0000000 PGPy: Pretty Good Privacy for Python
====================================
.. image:: https://badge.fury.io/py/PGPy.svg
:target: https://badge.fury.io/py/PGPy
:alt: Latest stable version
.. image:: https://travis-ci.org/SecurityInnovation/PGPy.svg?branch=master
:target: https://travis-ci.org/SecurityInnovation/PGPy?branch=master
:alt: Travis-CI
.. image:: https://coveralls.io/repos/github/SecurityInnovation/PGPy/badge.svg?branch=master
:target: https://coveralls.io/github/SecurityInnovation/PGPy?branch=master
:alt: Coveralls
Homepage: None yet.
`PGPy` is a Python (2 and 3) library for implementing Pretty Good Privacy into Python programs, conforming to the OpenPGP specification per RFC 4880.
Features
--------
Currently, PGPy can load keys and signatures of all kinds in both ASCII armored and binary formats.
It can create and verify RSA, DSA, and ECDSA signatures, at the moment. It can also encrypt and decrypt messages using RSA and ECDH.
Installation
------------
To install PGPy, simply:
.. code-block:: bash
$ pip install PGPy
Documentation
-------------
`PGPy Documentation `_
Discussion
----------
Please report any bugs found on the `issue tracker `_
You can also join ``#pgpy`` on Freenode to ask questions or get involved
Requirements
------------
- Python 3 >= 3.3; Python 2 >= 2.7
Tested with: 3.6, 3.5, 3.4, 3.3, 2.7
- `Cryptography `_
- `enum34 `_
- `singledispatch `_
- `pyasn1 `_
- `six `_
License
-------
BSD 3-Clause licensed. See the bundled `LICENSE `_ file for more details.
python-pgpy-0.4.3/docs/ 0000775 0000000 0000000 00000000000 13145172200 0014737 5 ustar 00root root 0000000 0000000 python-pgpy-0.4.3/docs/Makefile 0000664 0000000 0000000 00000015153 13145172200 0016404 0 ustar 00root root 0000000 0000000 # Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make ' where is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PGPy.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PGPy.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/PGPy"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PGPy"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
python-pgpy-0.4.3/docs/source/ 0000775 0000000 0000000 00000000000 13145172200 0016237 5 ustar 00root root 0000000 0000000 python-pgpy-0.4.3/docs/source/_ext/ 0000775 0000000 0000000 00000000000 13145172200 0017176 5 ustar 00root root 0000000 0000000 python-pgpy-0.4.3/docs/source/_ext/__init__.py 0000664 0000000 0000000 00000000133 13145172200 0021304 0 ustar 00root root 0000000 0000000 """
Just a placeholder so _ext.js_progress can be used as the extension name in conf.py
""" python-pgpy-0.4.3/docs/source/_ext/progress.py 0000664 0000000 0000000 00000011235 13145172200 0021416 0 ustar 00root root 0000000 0000000 __author__ = 'magreene'
import collections
import re
from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.statemachine import StringList
class progress(nodes.General, nodes.Element):
tagname = 'progress'
class ProgressTable(Directive):
has_content = True
required_arguments = 1
final_argument_whitespace = True
option_spec = {'text': str}
def create_headrow(self, label="Progress", classes=('prog-top-label',)):
hrow = nodes.row()
hrow += nodes.entry('', nodes.paragraph(text=label), classes=['head'] + list(classes))
hrow += nodes.entry('', nodes.paragraph(text='PLACEHOLDER'), classes=['PLACEHOLDER'])
return hrow
def create_progtable(self, **attrs):
_attrs = {
'classes': ['progress', 'outer', 'docutils', 'field-list'],
'colwidths': [20, 80]
}
_attrs.update(attrs)
# create container elements
node = nodes.table(classes=_attrs['classes'])
tgroup = nodes.tgroup(cols=2)
thead = thead = nodes.thead()
thead += self.create_headrow()
tbody = nodes.tbody()
# tgroup gets:
# - colspec
# - thead
# - tbody
for w in _attrs['colwidths']:
tgroup += nodes.colspec(colwidth=w)
# assemble the hierarchy
tgroup += thead
tgroup += tbody
node += tgroup
# return the table
return node
def add_progbar(self, row, val, max):
entry = nodes.entry(classes=['progcell', 'field-value'])
pbar = progress(value=val, max=max)
entry += pbar
row.replace(row.children[-1], entry)
def run(self):
secid = [self.arguments[0].lower().replace(' ', '-')]
section = nodes.section(ids=secid)
section.document = self.state.document
section += nodes.title(text=self.arguments[0])
# parse the 'text' option into the section, as a paragraph.
self.state.nested_parse(StringList([self.options['text']], parent=self), 0, section)
node = self.create_progtable()
section.children[-1] += node
head = node.children[0].children[-2].children[0]
body = node.children[0].children[-1]
comps = collections.OrderedDict()
cur = ""
for line in self.content:
# new list
nl = re.match(r'^:(?P.+):$', line)
if nl is not None:
if cur != "":
# finish up shrow
self.add_progbar(shrow, len([c for c in comps[cur] if c is True]), len(comps[cur]))
cur = nl.groupdict()['component']
if cur not in comps:
comps[cur] = []
# shrow is the section header row
shrow = self.create_headrow(cur, classes=['field-name'])
body += shrow
continue
nl = re.match(r'^\s+- (?P[^,]+),\s+(?P(True|False))(, (?P.+)$)?', line)
if nl is not None:
nl = nl.groupdict()
comps[cur].append(True if nl['value'] == "True" else False)
tr = nodes.row()
tr += nodes.description('',
nodes.inline(text="\u2713" if comps[cur][-1] else " "),
classes=['field-name', 'progress-checkbox'])
tr += nodes.description('',
nodes.strong(text='{:s} '.format(nl['item'])),
nodes.inline(text='{:s}'.format(nl['description'] if nl['description'] is not None else ' ')),
classes=['field-value'])
body += tr
if self.content:
# finish up the final hrow
self.add_progbar(shrow, len([c for c in comps[cur] if c == True]), len(comps[cur]))
# and fill in the end of mrow
self.add_progbar(head, len([c for r in comps.values() for c in r if c == True]), len([c for r in comps.values() for c in r]))
return [section]
def visit_progress(self, node):
attrs = {'value': 0,
'max': 0}
for a in attrs.keys():
if a in node.attributes:
attrs[a] = node.attributes[a]
self.body.append(''.format(attrs['value'], attrs['max']))
self.body.append(self.starttag(node, node.tagname, **attrs))
self.body.append('')
def depart_progress(self, node):
pass
def setup(app):
app.add_stylesheet('progress.css')
app.add_node(progress, html=(visit_progress, depart_progress))
app.add_directive('progress', ProgressTable)
python-pgpy-0.4.3/docs/source/_static/ 0000775 0000000 0000000 00000000000 13145172200 0017665 5 ustar 00root root 0000000 0000000 python-pgpy-0.4.3/docs/source/_static/pgpy_better.css 0000664 0000000 0000000 00000000131 13145172200 0022716 0 ustar 00root root 0000000 0000000 header#pageheader, footer#pagefooter, div.related, div.document {
max-width: 75rem;
} python-pgpy-0.4.3/docs/source/_static/progress.css 0000664 0000000 0000000 00000005034 13145172200 0022245 0 ustar 00root root 0000000 0000000 progress {
width: 100%;
height: 22px;
-webkit-appearance: none;
border: none;
}
/* webkit */
progress::-webkit-progress-bar {
background: #999;
border-radius: 6px;
padding: 2px;
box-shadow: 0 1px 1px 0 rgba(255, 255, 255, 0.2);
}
progress::-webkit-progress-value {
border-radius: 5px;
/* top shadow is white, bottom shadow is black */
box-shadow: inset 0 1px 1px 0 rgba(255, 255, 255, 0.3),
inset 0 -1px 1px 0 rgba(0, 0, 0, 0.3);
background:
-webkit-linear-gradient(45deg, transparent, transparent 33%, rgba(0, 0, 0, 0.1) 33%, rgba(0, 0, 0, 0.1) 66%, transparent 66%),
-webkit-linear-gradient(top, rgba(255, 255, 255, 0.25), rgba(0, 0, 0, 0.2)),
-webkit-linear-gradient(left, #4070A0, #6292C2);
background-size: 25px 17px, 100% 100%, 100% 100%;
-webkit-animation: move 5s linear 0 infinite;
animation: move 5s linear 0 infinite;
}
/* firefox */
progress,
progress[role][aria-valuenow]::-moz-progress-bar {
height: 17px;
background: #999 !important;
border-radius: 6px;
padding: 2px;
box-shadow: 0 1px 1px 0 rgba(255, 255, 255, 0.2);
}
progress::-moz-progress-bar {
border-radius: 5px;
/*top shadow is white, bottom shadow is black*/
box-shadow: inset 0 1px 1px 0 rgba(255, 255, 255, 0.3),
inset 0 -1px 1px 0 rgba(0, 0, 0, 0.3);
background:
-moz-linear-gradient(45deg, transparent, transparent 33%, rgba(0, 0, 0, 0.1) 33%, rgba(0, 0, 0, 0.1) 66%, transparent 66%),
-moz-linear-gradient(top, rgba(255, 255, 255, 0.25), rgba(0, 0, 0, 0.2)),
-moz-linear-gradient(left, #4070A0, #6292C2);
background-size: 25px 17px, 100% 100%, 100% 100%;
/* firefox apparently won't animate this :( */
animation: move 5s linear 0 infinite;
}
@-webkit-keyframes move {
from { background-position: 0 0; }
to { background-position: -100px 0; }
}
@keyframes move {
from { background-position: 0 0; }
to { background-position: -100px 0; }
}
th.progcell,
td.progcell {
position: relative;
}
th.progcell > label,
td.progcell > label {
position: absolute;
display: inline-block;
left: 50%;
width: 40px;
line-height: 22px;
margin-left: -40px;
text-align: center;
vertical-align: middle;
font-weight: 600;
text-shadow: 0 0 0.15em #fff, 1px 1px 2px #000;
}
.prog-top-label {
text-align: center !important;
vertical-align: bottom !important;
font-weight: bold;
}
.progress-checkbox {
text-align: right !important;
text-shadow: 2px 2px 1px #fff;
}
python-pgpy-0.4.3/docs/source/api/ 0000775 0000000 0000000 00000000000 13145172200 0017010 5 ustar 00root root 0000000 0000000 python-pgpy-0.4.3/docs/source/api/classes.rst.inc 0000664 0000000 0000000 00000012272 13145172200 0021753 0 ustar 00root root 0000000 0000000 Classes
=======
.. py:currentmodule:: pgpy
:py:class:`PGPKey`
------------------
.. autoclass:: PGPKey
:members:
.. py:attribute:: ascii_header
:annotation: = OrderedDict([('Version', 'PGPy v|version|')])
An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object.
.. py:classmethod:: from_file(filename)
Create a new :py:obj:`PGPKey` object, with contents loaded from a file. May be binary or ASCII armored.
:param filename: The path to the file to load.
:type filename: ``str``
:raises: :py:exc:`ValueError` if a properly formed PGP block was not found in the file at ``filename``
:raises: :py:exc:`~exceptions.PGPError` if de-armoring or parsing failed
:returns: A two element ``tuple`` of :py:obj:`PGPKey`, :py:obj:`~collections.OrderedDict`.
The :py:obj:`~collections.OrderedDict` has the following format::
key, others = PGPKey.from_file('path/to/keyfile')
# others: { (Fingerprint, bool(key.is_public)): PGPKey }
.. py:classmethod:: from_blob(blob)
Create a new :py:obj:`PGPKey` object, with contents loaded from a blob. May be binary or ASCII armored.
:param blob: The data to load.
:type blob: ``str``, ``bytes``, ``unicode``, ``bytearray``
:raises: :py:exc:`TypeError` if blob is not in the expected types above
:raises: :py:exc:`ValueError` if a properly formed PGP block was not found in ``blob``
:raises: :py:exc:`~exceptions.PGPError` if de-armoring or parsing failed
:returns: A two element ``tuple`` of :py:obj:`PGPKey`, :py:obj:`~collections.OrderedDict`.
The :py:obj:`~collections.OrderedDict` has the following format::
key, others = PGPKey.from_file('path/to/keyfile')
# others: { (Fingerprint, bool(key.is_public)): PGPKey }
:py:class:`PGPKeyring`
----------------------
.. autoclass:: PGPKeyring
:members:
.. py:attribute:: ascii_header
An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object.
:py:class:`PGPMessage`
----------------------
.. autoclass:: PGPMessage
:members:
.. py:attribute:: ascii_header
An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object.
.. py:classmethod:: from_file(filename)
Create a new :py:obj:`PGPMessage` object, with contents loaded from a file. May be binary or ASCII armored.
:param filename: The path to the file to load.
:type filename: ``str``
:raises: :py:exc:`ValueError` if a properly formed PGP block was not found in the file at ``filename``
:raises: :py:exc:`~exceptions.PGPError` if de-armoring or parsing failed
:returns: :py:obj:`PGPMessage`
.. py:classmethod:: from_blob(blob)
Create a new :py:obj:`PGPMessage` object, with contents loaded from a blob. May be binary or ASCII armored.
:param blob: The data to load.
:type blob: ``str``, ``bytes``, ``unicode``, ``bytearray``
:raises: :py:exc:`TypeError` if blob is not in the expected types above
:raises: :py:exc:`ValueError` if a properly formed PGP block was not found in ``blob``
:raises: :py:exc:`~exceptions.PGPError` if de-armoring or parsing failed
:returns: :py:obj:`PGPMessage`
:py:class:`PGPSignature`
------------------------
.. autoclass:: PGPSignature
:members:
.. py:attribute:: ascii_header
An :py:obj:`~collections.OrderedDict` of headers that appear, in order, in the ASCII-armored form of this object.
.. py:classmethod:: from_file(filename)
Create a new :py:obj:`PGPSignature` object, with contents loaded from a file. May be binary or ASCII armored.
:param filename: The path to the file to load.
:type filename: ``str``
:raises: :py:exc:`ValueError` if a properly formed PGP block was not found in the file at ``filename``
:raises: :py:exc:`~exceptions.PGPError` if de-armoring or parsing failed
:returns: :py:obj:`PGPSignature`
.. py:classmethod:: from_blob(blob)
Create a new :py:obj:`PGPSignature` object, with contents loaded from a blob. May be binary or ASCII armored.
:param blob: The data to load.
:type blob: ``str``, ``bytes``, ``unicode``, ``bytearray``
:raises: :py:exc:`TypeError` if blob is not in the expected types above
:raises: :py:exc:`ValueError` if a properly formed PGP block was not found in ``blob``
:raises: :py:exc:`~exceptions.PGPError` if de-armoring or parsing failed
:returns: :py:obj:`PGPSignature`
:py:class:`PGPUID`
------------------
.. autoclass:: PGPUID
:members:
Other Objects
=============
.. py:currentmodule:: pgpy.types
These are objects that are returned during certain operations, but are probably not useful to instantiate directly.
:py:class:`~types.SignatureVerification`
----------------------------------------
.. autoclass:: SignatureVerification
:members:
:py:class:`~types.Fingerprint`
------------------------------
.. autoclass:: Fingerprint
:members:
python-pgpy-0.4.3/docs/source/api/constants.rst.inc 0000664 0000000 0000000 00000010331 13145172200 0022324 0 ustar 00root root 0000000 0000000 Constants
=========
.. py:currentmodule:: pgpy.constants
:py:class:`PubKeyAlgorithm`
---------------------------
.. autoclass:: PubKeyAlgorithm
:no-members:
.. autoattribute:: RSAEncryptOrSign
:annotation:
.. autoattribute:: DSA
:annotation:
.. autoattribute:: ElGamal
:annotation:
.. autoattribute:: ECDH
:annotation:
.. autoattribute:: ECDSA
:annotation:
:py:class:`EllipticCurveOID`
----------------------------
.. autoclass:: EllipticCurveOID
:no-members:
.. autoattribute:: Curve25519
:annotation:
.. autoattribute:: Ed25519
:annotation:
.. autoattribute:: NIST_P256
:annotation:
.. autoattribute:: NIST_P384
:annotation:
.. autoattribute:: NIST_P521
:annotation:
.. autoattribute:: Brainpool_P256
:annotation:
.. autoattribute:: Brainpool_P384
:annotation:
.. autoattribute:: Brainpool_P512
:annotation:
.. autoattribute:: SECP256K1
:annotation:
:py:class:`SymmetricKeyAlgorithm`
---------------------------------
.. autoclass:: SymmetricKeyAlgorithm
:no-members:
.. autoattribute:: IDEA
:annotation:
.. autoattribute:: TripleDES
:annotation:
.. autoattribute:: CAST5
:annotation:
.. autoattribute:: Blowfish
:annotation:
.. autoattribute:: AES128
:annotation:
.. autoattribute:: AES192
:annotation:
.. autoattribute:: AES256
:annotation:
.. autoattribute:: Camellia128
:annotation:
.. autoattribute:: Camellia192
:annotation:
.. autoattribute:: Camellia256
:annotation:
:py:class:`CompressionAlgorithm`
--------------------------------
.. autoclass:: CompressionAlgorithm
:no-members:
.. autoattribute:: Uncompressed
:annotation:
.. autoattribute:: ZIP
:annotation:
.. autoattribute:: ZLIB
:annotation:
.. autoattribute:: BZ2
:annotation:
:py:class:`HashAlgorithm`
-------------------------
.. autoclass:: HashAlgorithm
:no-members:
.. autoattribute:: MD5
:annotation:
.. autoattribute:: SHA1
:annotation:
.. autoattribute:: RIPEMD160
:annotation:
.. autoattribute:: SHA256
:annotation:
.. autoattribute:: SHA384
:annotation:
.. autoattribute:: SHA512
:annotation:
.. autoattribute:: SHA224
:annotation:
:py:class:`SignatureType`
-------------------------
.. autoclass:: SignatureType
:no-members:
.. autoattribute:: BinaryDocument
:annotation:
.. autoattribute:: CanonicalDocument
:annotation:
.. autoattribute:: Standalone
:annotation:
.. autoattribute:: Generic_Cert
:annotation:
.. autoattribute:: Persona_Cert
:annotation:
.. autoattribute:: Positive_Cert
:annotation:
.. autoattribute:: Subkey_Binding
:annotation:
.. autoattribute:: PrimaryKey_Binding
:annotation:
.. autoattribute:: DirectlyOnKey
:annotation:
.. autoattribute:: KeyRevocation
:annotation:
.. autoattribute:: SubkeyRevocation
:annotation:
.. autoattribute:: CertRevocation
:annotation:
.. autoattribute:: Timestamp
:annotation:
.. autoattribute:: ThirdParty_Confirmation
:annotation:
:py:class:`KeyFlags`
--------------------
.. autoclass:: KeyFlags
:no-members:
.. autoattribute:: Certify
:annotation:
.. autoattribute:: Sign
:annotation:
.. autoattribute:: EncryptCommunications
:annotation:
.. autoattribute:: EncryptStorage
:annotation:
.. autoattribute:: Split
:annotation:
.. autoattribute:: Authentication
:annotation:
.. autoattribute:: MultiPerson
:annotation:
:py:class:`RevocationReason`
----------------------------
.. autoclass:: RevocationReason
:no-members:
.. autoattribute:: NotSpecified
:annotation:
.. autoattribute:: Superseded
:annotation:
.. autoattribute:: Compromised
:annotation:
.. autoattribute:: Retired
:annotation:
.. autoattribute:: UserID
:annotation:
python-pgpy-0.4.3/docs/source/api/exceptions.rst.inc 0000664 0000000 0000000 00000001234 13145172200 0022473 0 ustar 00root root 0000000 0000000 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
python-pgpy-0.4.3/docs/source/api/index.rst 0000664 0000000 0000000 00000000172 13145172200 0020651 0 ustar 00root root 0000000 0000000 ********
PGPy API
********
.. include:: exceptions.rst.inc
.. include:: constants.rst.inc
.. include:: classes.rst.inc
python-pgpy-0.4.3/docs/source/changelog.rst 0000664 0000000 0000000 00000021464 13145172200 0020727 0 ustar 00root root 0000000 0000000 :tocdepth: 2
*********
Changelog
*********
v0.4.3
======
Released: August 16, 2017
Bugs Fixed
----------
* Private key checksum calculations were not getting stored for ECDSA keys; this has been fixed.
* The test suite gpg wrappers have been replaced with use of the `gpg `_ package. (#171)
v0.4.2
======
Released: August 9, 2017
New Features
------------
* Packets with partial body lengths can now be parsed. For now, these packets are converted to have definite lengths instead. (#95) (#208)
Bugs Fixed
----------
* Private key checksums are now calculated correctly (#172)
* PGPKey.decrypt was mistakenly using message.issuers instead of message.encrypters when determining whether or not the key was eligible
to attempt decrypting the message (#183)
* Fixed an issue with parsing some cleartext messages (#184)
* Fixed signing already-encrypted messages (encrypt-then-sign) (#186) (#191)
* PGP*.from_blob now correctly raises an exception if given zero-length input (#199) (#200)
* Fixed an issue where PGPKey.decrypt would fail with an arcane traceback if the key is passphrase-protected and not unlocked. (#204)
v0.4.1
======
Released: April 13, 2017
Bugs Fixed
----------
* Fixed an issue with dearmoring ASCII-armored PGP blocks with windows-style newlines (#156)
* Improved the robustness of the code that tunes the hash count for deriving symmetric encryption keys (#157)
* Fixed an issue with how public keys are created from private keys that was causing exports to become malformed (#168)
* Added explicit support for Python 3.6 (#166)
New Features
------------
* Added support for Brainpool Standard curves for users who have OpenSSL 1.0.2 available
v0.4.0
======
Released: April 21, 2016
Bugs Fixed
----------
* Armorable.from_blob was incorrectly not accepting bytes objects; this has been fixed (#140)
* Fixed an issue where string-formatting PGPUID objects would sometimes raise an exception (#142)
* Occasionally, the ASN.1 encoding of DSA signatures was being built in a way that although GPG could parse and verify them,
it was incorrect, and PGPy incorrectly failed to verify them. (#143)
* Fixed an issue where keys with expiration dates set would have the wrong value returned from the ``key.is_expired`` property (#151)
* Fixed an issue where PGPy would try to incorrectly coerce non-ASCII-compatible characters to ASCII-compatible bytes, potentially resulting in mojibake. (#154)
New Features
------------
* ECDSA and ECDH keys can now be loaded (#109, #110)
* Keys can be generated with the following algorithms:
- RSA
- DSA
- ECDSA
- ECDH
* Keys can now be passphrase-protected. It is also possible to change the passphrase on a key that is already protected. (#149)
* ECDSA keys can now be used to sign and verify (#111)
* ECDH keys can now be used to encrypt and decrypt
* It is now possible to recover a public key from a private key (#92)
* Marker packets are now understood
Other Changes
-------------
* Removed support for Python 3.2, as multiple dependency libraries have already done so
* Added explicit support for Python 3.5
* Updated library dependencies where required or useful
* Reworked some IO-intensive routines to be less IO-intensive, and therefore faster
v0.3.0
======
Released: November 19, 2014
PGPy v0.3.0 is a major feature release.
.. warning::
The API changed significantly in this version. It is likely that anything using a previous version will need to be
updated to work correctly with PGPy 0.3.0 or later.
Bugs Fixed
----------
* When keys are exported, any certification signatures that are marked as being non-exportable are now skipped (#101)
* When the wrong key is used to validate a signature, the error message in the raised exception
now makes that clear (#106)
New Features
------------
* Standalone sigantures can now be generated
* Can now specify which User ID to use when signing things (#121)
* Can now create new User IDs and User Attributes (#118)
* Can now add new User IDs and User Attributes to keys (#119)
* Timestamp signatures can now be generated
* Can now sign keys, user ids, and user attributes (#104)
* Can now create new PGPMessages (#114)
* Key flags are now respected by PGPKey objects (#99)
* Multiple signatures can now be validated at once in cases where that makes sense, such as when validating
self-signatures on keys/user ids (#120)
* Message signatures can now be verified (#117)
* Messages can now be encrypted/decrypted using a passphrase (#113)
* Cleartext messages can now be created and signed (#26)
* Cleartext messages with inline sigantures can now be verified (#27)
* Messages can now be loaded (#102)
* Messages can now be compressed (#100)
Other Changes
-------------
* CRC24 computation is now much faster than previous versions (#68)
* PGPKey and PGPKeyring APIs have changed significantly (#76)
* String2Key computation is now much faster than previous versions (#94)
* key material parts are now stored as integers (or ``long`` on Python 2.x) (#94)
v0.2.3
======
Released: July 31, 2014
PGPy v0.2.3 is a bugfix release
Bugs Fixed
----------
* Fixed an issue where explicitly selecting a key and then trying to validate with it would erroneously raise an exception as though the wrong key were selected.
v0.2.2
======
Released: July 31, 2014
PGPy v0.2.2 is primarily a bugfix release.
Bugs Fixed
----------
* Fixed a typo that would cause TypeError to be raised as bytecode was being generated (#85)
* Fixed an issue where unicode input on Python 2.7 could result in unexpected UnicodeDecodeError exceptions being raised
New Features
------------
* Switched the main parse loop to use a bytearray instead of slicing a bytes, resulting in a ~160x speedup in parsing large blocks of pasing. (#87)
v0.2.1
======
Released: July 31, 2014
PGPy v0.2.1 is primarily a bugfix release.
Bugs Fixed
----------
* Critical bit on signature subpackets was being ignored, and when set, causing a ValueError to be raised when trying to parse it.
The critical bit is now being parsed and masked out correctly. (#81)
* No longer raises exceptions on unrecognized subpackets; instead, it now treats them as opaque.
* No longer raises exceptions on unrecognized packets; instead, it now treats them as opaque.
This also applies to signature and key packets with versions other than v4.
* Fixed an issue where a User ID packet that lacked both a comment and an email address was failing to be found by the uid regex in KeyCollection.
* Fixed an issue where an old-format packet header with a length_type set longer than needed was resulting in the packet getting truncated.
* Fixed an issue where parsing a subpacket with a 2-byte length was erroneously being parsed as a 5-byte length.
* Fixed an issue where parsing a subpacket with a 5-byte length where the value was < 8434 was causing an error
* Fixed an issue where a packet or subpacket reporting a value marked reserved in RFC 4880 would cause ValueError to be raised during parsing.
* Key material marked as public key algorithm 20 (Reserved - Formerly ElGamal Encrypt or Sign) is now parsed as ElGamal key material.
* Fixed an issue where parsing a new-format packet header length where the first octet was 223 was erroneously reported as being malformed.
New Features
------------
* Added support for parsing the 'Preferred Key Server' signature subpacket
* Added support for loading unsupported or unrecognized signature subpackets.
* Added support for loading unsupported or unrecognized packets.
v0.2.0
======
Released: July 20, 2014
Starting with v0.2.0, PGPy is now using the BSD 3-Clause license. v0.1.0 used the MIT license.
New Features
------------
* Subkeys can now be accessed and used for actions supported by PGPKeyring (#67)
* DSA:
- Signing of binary documents now works (#16)
- Verification of signatures of binary documents now works (#15)
* Can now decrypt secret key material that was encrypted using:
- Camellia128 (#36)
- Camellia192 (#37)
- Camellia256 (#38)
- AES128 (#32)
- AES192 (#33)
- AES256 (#34)
- Blowfish (#31)
- Triple-DES (#30)
- IDEA (#29)
* PGP packets generated by PGPy now exclusively use new-style header lengths (#47)
* GPG Trust Packets are now understood and fully parsed (#14)
* Lots more packet types are now fully parsed
Known Issues
------------
* Signing with 1024-bit DSA keys does not work with OpenSSL 0.9.8 (#48) - this primarily affects Mac OS X.
* Verifying signatures signed with any DSA key length other than 2048-bits does not work with OpenSSL 0.9.8 -
this primarily affects Mac OS X.
Bugs Fixed
----------
* PGP blocks loaded from ASCII armored blocks now retain their ASCII headers (#54)
* PGP new-style packet headers were not being properly parsed in all cases
* Many unit test enhancements
v0.1.0
======
Released: May 02, 2014
* Initial release.
python-pgpy-0.4.3/docs/source/conf.py 0000664 0000000 0000000 00000021536 13145172200 0017545 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# PGPy documentation build configuration file, created by
# sphinx-quickstart on Thu May 1 18:03:51 2014.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
# this needs sphinx-better-theme
from better import better_theme_path
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
_docsrcdir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, _docsrcdir)
sys.path.insert(0, os.path.dirname(os.path.dirname(_docsrcdir)))
from pgpy._author import __version__
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.todo',
'_ext.progress',
# 'sphinx.ext.doctest',
# 'sphinx.ext.coverage',
# 'sphinx.ext.mathjax',
# 'sphinx.ext.viewcode',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'PGPy'
copyright = '2014, Michael Greene'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = __version__
# The full version, including alpha/beta/rc tags.
release = __version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# -- Options for ext.autodoc ----------------------------------------------
autoclass_content = 'init'
autodoc_member_order = 'bysource'
autodoc_default_flags = ['members']
# -- Options for ext.autosummary ------------------------------------------
numpydoc_show_class_members = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# html_theme = 'default'
html_theme = 'better'
html_theme_path = [better_theme_path]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
if html_theme == 'better':
html_theme_options = {
'cssfiles': ['_static/pgpy_better.css'],
'sidebarwidth': '19rem',
}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'PGPydoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
# latex_documents = [
# ('index', 'PGPy.tex', 'PGPy Documentation',
# 'Michael Greene', 'manual'),
# ]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'pgpy', 'PGPy Documentation',
['Michael Greene'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
# texinfo_documents = [
# ('index', 'PGPy', 'PGPy Documentation',
# 'Michael Greene', 'PGPy', 'One line description of project.',
# 'Miscellaneous'),
# ]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
python-pgpy-0.4.3/docs/source/examples.rst 0000664 0000000 0000000 00000000426 13145172200 0020611 0 ustar 00root root 0000000 0000000 ********
Examples
********
.. toctree::
:hidden:
examples/keys
examples/messages
examples/actions
examples/exporting
.. include:: examples/keys.rst
.. include:: examples/messages.rst
.. include:: examples/actions.rst
.. include:: examples/exporting.rst python-pgpy-0.4.3/docs/source/examples/ 0000775 0000000 0000000 00000000000 13145172200 0020055 5 ustar 00root root 0000000 0000000 python-pgpy-0.4.3/docs/source/examples/actions.rst 0000664 0000000 0000000 00000011057 13145172200 0022253 0 ustar 00root root 0000000 0000000 Actions
=======
Signing Things
--------------
One of the things you may want to do with PGPKeys is to sign things. This is split into several categories in order
to keep the method signatures relatively simple. Remember that signing requires a private key.
Text/Messages/Other
^^^^^^^^^^^^^^^^^^^
Text and messages can be signed using the .sign method::
# sign some text
sig = sec.sign("I have just signed this text!")
# sign a message
# the bitwise OR operator '|' is used to add a signature to a PGPMessage.
message |= sec.sign(message)
# timestamp signatures can also be generated, like so.
# Note that GnuPG seems to have no idea what to do with this
timesig = sec.sign(None)
# if optional parameters are supplied, then a standalone signature is created
# instead of a timestamp signature. Effectively, they are equivalent, except
# that the standalone signature has more information in it.
lone_sig = sec.sign(None, notation={"cheese status": "standing alone"})
Keys/User IDs
^^^^^^^^^^^^^
Keys and User IDs can be signed using the .certify method::
# Sign a key - this creates a Signature Directly On A Key.
# GnuPG only partially supports this type of signature.
someones_pubkey |= mykey.certify(someones_pubkey)
# Sign the primary User ID - this creates the usual certification signature
# that is best supported by other popular OpenPGP implementations.
# As above, the bitwise OR operator '|' is used to add a signature to a PGPUID.
cert = mykey.certify(someones_pubkey.userids[0], level=SignatureType.Persona_Cert)
someones_pubkey.userids[0] |= cert
# If you want to sign all of their User IDs, that can be done easily in a loop.
# This is equivalent to GnuPG's default behavior when signing someone's public key.
# As above, the bitwise OR operator '|' is used to add a signature to a PGPKey.
for uid in someones_pubkey.userids:
uid |= mykey.certify(uid)
Verifying Things
----------------
Although signing things uses multiple methods, there is only one method to remember for verifying signatures::
# verify a detached signature
pub.verify("I have just signed this text!", sig)
# verify signatures in a message
pub.verify(message)
# verify signatures on a userid
for uid in someones_pubkey.userids:
pub.verify(uid)
# or, better yet, verify all applicable signatures on a key in one go
pub.verify(someones_pubkey)
Encryption
----------
Another thing you may want to do is encrypt or decrypt messages.
Encrypting/Decrypting Messages With a Public Key
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Encryption using keys requires a public key, while decryption requires a private key. PGPy currently only supports
asymmetric encryption/decryption using RSA::
# this returns a new PGPMessage that contains an encrypted form of the
# unencrypted message
encrypted_message = rsa_pub.encrypt(message)
Encrypting Messages to Multiple Recipients
""""""""""""""""""""""""""""""""""""""""""
.. warning::
Care must be taken when doing this to delete the session key as soon as possible after encrypting the message.
Messages can also be encrypted to multiple recipients by pre-generating the session key::
# The symmetric cipher should be specified, in case the first preferred cipher is not
# the same for all recipients' public keys
cipher = pgpy.constants.SymmetricKeyAlgorithm.AES256
sessionkey = cipher.gen_key()
# encrypt the message to multiple recipients
# A decryption passphrase can be added at any point as well, as long as cipher
# and sessionkey are also provided to enc_msg.encrypt
enc_msg = pubkey1.encrypt(message, cipher=cipher, sessionkey=sessionkey)
enc_msg = pubkey2.encrypt(enc_msg, cipher=cipher, sessionkey=sessionkey)
# do at least this as soon as possible after encrypting to the final recipient
del sessionkey
Encrypting/Decrypting Messages With a Passphrase
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are some situations where encrypting a message with a passphrase may be more desirable than doing so with
someone else's public key. That can be done like so::
# the .encrypt method returns a new PGPMessage object which contains the encrypted
# contents of the old message
enc_message = message.encrypt("S00per_Sekr3t")
# message.is_encrypted is False
# enc_message.is_encrypted is True
# a message that was encrypted using a passphrase can also be decrypted using
# that same passphrase
dec_message = enc_message.decrypt("S00per_Sekr3t")
python-pgpy-0.4.3/docs/source/examples/exporting.rst 0000664 0000000 0000000 00000001122 13145172200 0022622 0 ustar 00root root 0000000 0000000 Exporting PGP* Objects
======================
PGPKey, PGPMessage, and PGPSignature objects can all be exported to OpenPGP-compatible binary and ASCII-armored formats.
To export in ASCII-armored format::
# This works in both Python 2.x and 3.x
# ASCII-armored format
# cleartext PGPMessages will also have properly canonicalized and dash-escaped
# message text
pgpstr = str(pgpobj)
To export to binary format in Python 3::
# binary format
pgpbytes = bytes(pgpobj)
To export to binary format in Python 2::
# binary format
pgpbytes = pgpobj.__bytes__()
python-pgpy-0.4.3/docs/source/examples/keys.rst 0000664 0000000 0000000 00000013655 13145172200 0021574 0 ustar 00root root 0000000 0000000 Keys
====
Generating Keys
---------------
PGPy can generate most types keys as defined in the standard.
Generating Primary Keys
^^^^^^^^^^^^^^^^^^^^^^^
It is possible to generate most types of keys with PGPy now. The process is mostly straightforward::
from pgpy.constants import PubKeyAlgorithm, KeyFlags, HashAlgorithm, SymmetricKeyAlgorithm, CompressionAlgorithm
# we can start by generating a primary key. For this example, we'll use RSA, but it could be DSA or ECDSA as well
key = pgpy.PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 4096)
# we now have some key material, but our new key doesn't have a user ID yet, and therefore is not yet usable!
uid = pgpy.PGPUID.new('Abraham Lincoln', comment='Honest Abe', email='abraham.lincoln@whitehouse.gov')
# now we must add the new user id to the key. We'll need to specify all of our preferences at this point
# because PGPy doesn't have any built-in key preference defaults at this time
# this example is similar to GnuPG 2.1.x defaults, with no expiration or preferred keyserver
key.add_uid(uid, usage={KeyFlags.Sign, KeyFlags.EncryptCommunications, KeyFlags.EncryptStorage},
hashes=[HashAlgorithm.SHA256, HashAlgorithm.SHA384, HashAlgorithm.SHA512, HashAlgorithm.SHA224],
ciphers=[SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.AES192, SymmetricKeyAlgorithm.AES128],
compression=[CompressionAlgorithm.ZLIB, CompressionAlgorithm.BZ2, CompressionAlgorithm.ZIP, CompressionAlgorithm.Uncompressed])
Specifying key expiration can be done using the ``key_expires`` keyword when adding the user id. Expiration can be specified
using a :py:obj:`datetime.datetime` or a :py:obj:`datetime.timedelta` object::
from datetime import timedelta
# in this example, we'll use fewer preferences for the sake of brevity, and set the key to expire in 10 years
key = pgpy.PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 4096)
uid = pgpy.PGPUID.new('Nikola Tesla') # comment and email are optional
# the key_expires keyword accepts a :py:obj:`datetime.datetime`
key.add_uid(uid, usage={KeyFlags.Sign}, hashes=[HashAlgorithm.SHA512, HashAlgorithm.SHA256],
ciphers=[SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.Camellia256],
compression=[CompressionAlgorithm.BZ2, CompressionAlgorithm.Uncompressed],
key_expires=timedelta(days=365))
Generating Sub Keys
^^^^^^^^^^^^^^^^^^^
Generating a subkey is similar to the process above, except that it requires an existing primary key::
# assuming we already have a primary key, we can generate a new key and add it as a subkey thusly:
subkey = pgpy.PGPKey.new(PubKeyAlgorithm.RSA, 4096)
# preferences that are specific to the subkey can be chosen here
# any preference(s) needed for actions by this subkey that not specified here
# will seamlessly "inherit" from those specified on the selected User ID
key.add_subkey(subkey, usage={KeyFlags.Authentication})
Loading Keys
------------
There are two ways to load keys: individually, or in a keyring.
Loading Keys Individually
^^^^^^^^^^^^^^^^^^^^^^^^^
Keys can be loaded individually into PGPKey objects::
# A new, empty PGPkey object can be instantiated, but this is not very useful
# by itself.
# ASCII or binary data can be parsed into an empty PGPKey object with the .parse()
# method
empty_key = pgpy.PGPKey()
empty_key.parse(keyblob)
# A key can be loaded from a file, like so:
key, _ = pgpy.PGPKey.from_file('path/to/key.asc')
# or from a text or binary string/bytes/bytearray that has already been read in:
key, _ = pgpy.PGPKey.from_blob(keyblob)
Loading Keys Into a Keyring
^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you intend to maintain multiple keys in memory for extended periods, using a PGPKeyring may be more appropriate::
# These two methods are mostly equivalent
kr = pgpy.PGPKeyring(glob.glob(os.path.expanduser('~/.gnupg/*ring.gpg')))
# the only advantage to doing it this way, is the .load method returns a set containing
# the fingerprints of all keys and subkeys that were loaded this time
kr = pgpy.PGPKeyring()
loaded = kr.load(glob.glob(os.path.expanduser('~/.gnupg/*ring.gpg')))
Key Operations
--------------
Once you have one or more keys generated or loaded, there are some things you may need or want to do before they can be used.
Passphrase Protecting Secret Keys
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
It is usually recommended to passphrase-protect private keys. Adding a passphrase to a key is simple::
# key.is_public is False
# key.is_protected is False
key.protect("C0rrectPassphr@se", SymmetricKeyAlgorithm.AES256, HashAlgorithm.SHA256)
# key.is_protected is now True
Unlocking Protected Secret Keys
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you have a key that is protected with a passphrase, you will need to unlock it first. PGPy handles this using
a context manager block, which also removes the unprotected key material from the object once execution exits that block.
Key unlocking is quite simple::
# enc_key.is_public is False
# enc_key.is_protected is True
# enc_key.is_unlocked is False
# Note that this context manager yields self, so while you can supply `as cvar`, it isn't strictly required
# If the passphrase given is incorrect, this will raise PGPDecryptionError
with enc_key.unlock("C0rrectPassphr@se"):
# 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
^^^^^^^^^^^^^^
Keys can be exported in OpenPGP compliant binary or ASCII-armored formats.
In Python 3::
# binary
keybytes = bytes(key)
# ASCII armored
keystr = str(key)
in Python 2::
# binary
keybytes = key.__bytes__()
# ASCII armored
keystr = str(key)
python-pgpy-0.4.3/docs/source/examples/messages.rst 0000664 0000000 0000000 00000004030 13145172200 0022413 0 ustar 00root root 0000000 0000000 Messages
========
Other than plaintext, you may want to be able to form PGP Messages. These can be signed and then encrypted to one or
more recipients.
Creating New Messages
---------------------
New messages can be created quite easily::
# this creates a standard message from text
# it will also be compressed, by default with ZIP DEFLATE, unless otherwise specified
text_message = pgpy.PGPMessage.new("This is a brand spankin' new message!")
# if you'd like to pack a file into a message instead, you can do so
# PGPMessage will store the basename of the file and the time it was last modified.
file_message = pgpy.PGPMessage.new("path/to/a/file", file=True)
# or, if you want to create a *cleartext* message, which is what you may know as a
# canonicalized text document with an inline signature block, that is done by setting
# cleartext=True. You can load the contents of a file as above, as well.
ct_message = pgpy.PGPMessage.new("This is a shiny new cleartext document. Hooray!",
cleartext=True)
Loading Existing Messages
-------------------------
Existing messages can also be loaded very simply. This is nearly identical to loading keys, except that
it only returns the new message object, instead of a tuple::
# PGPMessage will automatically determine if this is a cleartext message or not
message_from_file = pgpy.PGPMessage.from_file("path/to/a/message")
message_from_blob = pgpy.PGPMessage.from_blob(msg_blob)
Exporting Messages
------------------
Messages can be exported in OpenPGP compliant binary or ASCII-armored formats.
In Python 3::
# binary
msgbytes = bytes(message)
# ASCII armored
# if message is cleartext, this will also properly canonicalize and dash-escape
# the message text
msgstr = str(message)
in Python 2::
# binary
msgbytes = message.__bytes__()
# ASCII armored
# if message is cleartext, this will also properly canonicalize and dash-escape
# the message text
msgstr = str(message)
python-pgpy-0.4.3/docs/source/index.rst 0000664 0000000 0000000 00000001011 13145172200 0020071 0 ustar 00root root 0000000 0000000 ##################
PGPy Documentation
##################
``PGPy`` is a Python (2 and 3) implementation of the OpenPGP specification, as described in `RFC 4880`_.
It aims to be easy to use above all else, but also to eventually embody a complete,
compliant implementation of the specification.
Contents:
.. toctree::
:maxdepth: 2
installation
examples
API
progress
changelog
.. include:: installation.rst
.. Links and References
.. _RFC 4880: https://tools.ietf.org/html/rfc4880
python-pgpy-0.4.3/docs/source/installation.rst 0000664 0000000 0000000 00000003500 13145172200 0021470 0 ustar 00root root 0000000 0000000 ************
Installation
************
.. highlight:: console
Platform Specific Notes
=======================
Windows
-------
PGPy has not been formally tested on Windows. I see no reason why it wouldn't work, but your mileage may vary.
If you try it out and run into any issues, please submit bug reports on the `issue tracker `_!
Linux
-----
Debian
^^^^^^
PGPy is now in `Debian Sid `_, and can be installed simply::
$ sudo apt install python3-pgpy
Arch Linux
^^^^^^^^^^
PGPy is available on the `AUR `_
Gentoo
^^^^^^
There are gentoo ebuilds available in the `gentoo branch `_
RedHat/CentOS
^^^^^^^^^^^^^
Coming Soon!
Other Linux
^^^^^^^^^^^
Building PGPy on Linux requires a C compiler, headers for Python, headers for OpenSSL, and libffi, to support building Cryptography.
For Debian/Ubuntu, these requirements can be installed like so::
$ sudo apt install build-essential libssl-dev libffi-dev python-dev
You may need to install ``python3-dev`` if you are using PGPy on Python 3.
For Fedora/RHEL derivatives, the build requirements can be installed like so::
$ sudo yum install gcc libffi-devel python-devel openssl-devel
Mac OS X
--------
If you are on Mac OS, you may experience more limited functionality without installing a more capable version of OpenSSL.
You may refer to Cryptography's documentation on `Building cryptography on OS X `_ for information on how to do so.
Installation
============
Once you have the prerequisites specified above, PGPy can be installed from PyPI using pip, like so::
$ pip install PGPy
python-pgpy-0.4.3/docs/source/progress.rst 0000664 0000000 0000000 00000032107 13145172200 0020640 0 ustar 00root root 0000000 0000000 *******************************
OpenPGP Implementation Progress
*******************************
OpenPGP RFCs
============
PGPy is focused on eventually reaching complete OpenPGP implementation, adhering to the base OpenPGP message format specification, and its extension RFCs.
.. progress:: RFC 4880
:text: PGPy is currently focused on achieving :rfc:`4880` compliance for OpenPGP, which is the latest complete OpenPGP Message Format specification. It supersedes RFC 1991 and RFC 2440.
:Versioned Packets, v1:
- Tag 18, True, Symetrically Encrypted and Integrity Protected Data Packet
:Versioned Packets, v3:
- Tag 1, True, Public-Key Encrypted Session Key Packets
- Tag 2, False, Signature Packet
- Tag 4, True, One-Pass Signature Packet
- Tag 5, False, Secret-Key Packet
- Tag 6, False, Public-Key Packet
- Tag 7, False, Secret-Subkey Packet
- Tag 14, False, Public-SubKey Packet
:Versioned Packets, v4:
- Tag 2, True, Signature Packet
- Tag 3, True, Symmetric-Key Encrypted Session Key Packet
- Tag 5, True, Secret-Key Packet
- Tag 6, True, Public-Key Packet
- Tag 7, True, Secret-Subkey Packet
- Tag 14, True, Public-SubKey Packet
:Unversioned Packets:
- Tag 8, True, Compressed Data Packet
- Tag 9, True, Symetrically Encrypted Data Packet
- Tag 10, True, Marker Packet
- Tag 11, True, Literal Data Packet
- Tag 12, True, Trust Packet
- Tag 13, True, User ID Packet
- Tag 17, True, User Attribute Packet
- Tag 19, True, Modification Detection Code Packet
:Signature Subpackets:
- 0x02, True, Signature Creation Time
- 0x03, True, Signature Expiration Time
- 0x04, True, Exportable Certification
- 0x05, True, Trust Signature
- 0x06, True, Regular Expression
- 0x07, True, Revocable
- 0x09, True, Key Expiration Time
- 0x0B, True, Preferred Symmetric Algorithms
- 0x0C, True, Revocation Key
- 0x10, True, Issuer
- 0x14, True, Notation Data
- 0x15, True, Preferred Hash Algorithms
- 0x16, True, Preferred Compression Algorithms
- 0x17, True, Key Server Preferences
- 0x18, True, Preferred Key Server
- 0x19, True, Primary User ID
- 0x1A, True, Policy URI
- 0x1B, True, Key Flags
- 0x1C, True, Signer's User ID
- 0x1D, True, Reason For Revocation
- 0x1E, True, Features
- 0x1F, False, Signature Target
- 0x20, True, Embedded Signature
:User Attribute Subpackets:
- 0x01, True, Image
:Storage Formats:
- ASCII, True, ASCII armored PGP blocks
- binary, True, binary PGP packets
- GPG, True, GPG <= 2.0.x keyrings
- KBX, False, GPG >= 2.1.x keyboxes
:Other Sources:
- Retrieve, False, Retrieve from HKP key servers
- Upload, False, Submit to HKP key servers
:Key Types:
- RSA, True, RSA
- DSA, True, DSA
- ElGamal, True, ElGamal
:Key Actions:
- Protect, True, Protect private keys encryped with CAST5
- Protect, True, Protect private keys encryped with Blowfish
- Protect, True, Protect private keys encryped with AES
- Protect, False, Protect private keys encryped with Twofish
- Unprotect, True, Unprotect private keys encrypted with IDEA*
- Unprotect, True, Unprotect private keys encrypted with Triple-DES
- Unprotect, True, Unprotect private keys encrypted with CAST5
- Unprotect, True, Unprotect private keys encrypted with Blowfish
- Unprotect, True, Unprotect private keys encrypted with AES
- Unprotect, False, Unprotect private keys encrypted with Twofish
:RSA Key Actions:
- Load, True, Load Keys
- Generate, True, Generate Keys
- Generate, True, Generate Subkeys
- Sign, True, Generate detached signatures of binary documents
- Sign, True, Generate inline signatures of canonical documents
- Sign, True, Sign messages
- Sign, True, Sign keys
- Sign, True, Certify User IDs
- Sign, True, Certify User Attributes
- Sign, True, Generate key binding signatures
- Sign, True, Revoke certifications
- Sign, True, Revoke keys
- Sign, True, Revoke subkeys
- Sign, True, Generate timestamp signatures
- Sign, True, Generate standalone signatures
- Sign, False, Generate third party confirmation signatures
- Verify, True, Verify detached signatures
- Verify, True, Verify inline signatures of canonical documents
- Verify, True, Verify messages
- Verify, True, Verify key signatures
- Verify, True, Verify User ID certification signatures
- Verify, True, Verify User Attribute certification signatures
- Verify, True, Verify key binding signatures
- Verify, True, Verify key revocation signatures
- Verify, True, Verify subkey revocation signatures
- Verify, True, Verify certification revocation signatures
- Verify, True, Verify timestamp signatures
- Verify, True, Verify standalone signatures
- Verify, False, Verify third party confirmation signatures
- Revocation, True, Designate Revocation Key
- Revocation, True, Revoke (Sub)Key with Self Signature
- Revocation, False, Revoke (Sub)Key using Designated Revocation Key
- Encryption, True, Encrypt data/messages
- Decryption, True, Decrypt data/messages
:DSA Key Actions:
- Load, True, Load Keys
- Generate, True, Generate Keys
- Generate, True, Generate Subkeys
- Sign, True, Generate detached signatures of binary documents
- Sign, True, Generate inline signatures of canonical documents
- Sign, True, One-Pass Sign messages
- Sign, True, Sign messages
- Sign, True, Sign keys
- Sign, True, Certify User IDs
- Sign, True, Certify User Attributes
- Sign, True, Generate key binding signatures
- Sign, True, Revoke certifications
- Sign, True, Revoke keys
- Sign, True, Revoke subkeys
- Sign, True, Generate timestamp signatures
- Sign, True, Generate standalone signatures
- Sign, False, Generate third party confirmation signatures
- Verify, True, Verify detached signatures
- Verify, True, Verify inline signatures of canonical documents
- Verify, True, Verify messages
- Verify, True, Verify key signatures
- Verify, True, Verify User ID certification signatures
- Verify, True, Verify User Attribute certification signatures
- Verify, True, Verify key binding signatures
- Verify, True, Verify key revocation signatures
- Verify, True, Verify subkey revocation signatures
- Verify, True, Verify certification revocation signatures
- Verify, True, Verify timestamp signatures
- Verify, True, Verify standalone signatures
- Verify, False, Verify third party confirmation signatures
- Revocation, True, Designate Revocation Key
- Revocation, True, Revoke (Sub)Key with Self Signature
- Revocation, False, Revoke (Sub)Key using Designated Revocation Key
:ElGamal Key Actions:
- Load, True, Load Keys
- Generate, False, Generate Keys
- Generate, False, Generate Subkeys
- Encryption, False, Encrypt data/messages
- Decryption, False, Decrypt data/messages
:Other Actions:
- Encryption, True, Encrypt data/messages using symmetric ciphers with passphrases
- Decryption, True, Decrypt data/messages using symmetric ciphers with passphrases
.. progress:: RFC 4398
:text: :rfc:`4398` covers publishing and retrieving PGP public keys via DNS CERT records.
:Key Sources:
- DNS CERT, False, Look up and retrieve keys stored in Content-based DNS CERT records
- DNS CERT, False, Look up and retrieve keys stored in Purpose-based DNS CERT records
.. progress:: RFC 5581
:text: :rfc:`5581` extends RFC 4880 to officially add support for the Camellia cipher
:Actions:
- Encryption, True, Camellia*
- Decryption, True, Camellia*
.. progress:: RFC 6637
:text: :rfc:`6637` extends OpenPGP to officially add support for elliptic curve cryptography
:Key Types:
- ECDH, True, Elliptic Curve Diffie-Hellman
- ECDSA, True, Elliptic Curve Digital Signature Algorithm
:Curves:
- Curve, True, NIST P-256
- Curve, True, NIST P-386
- Curve, True, NIST P-521
:ECDH Key Actions:
- Load, True, Load Keys
- Generate, True, Generate Keys
- Generate, True, Generate Subkeys
- KDF, True, Encode KDF data for encryption
- KDF, True, Decode KDF data for decryption
:ECDSA Key Actions:
- Load, True, Load Keys
- Generate, True, Generate Keys
- Generate, True, Generate Subkeys
- Sign, True, Generate detached signatures of binary documents
- Sign, True, Generate inline signatures of canonical documents
- Sign, True, One-Pass Sign messages
- Sign, True, Sign messages
- Sign, True, Sign keys
- Sign, True, Certify User IDs
- Sign, True, Certify User Attributes
- Sign, True, Generate key binding signatures
- Sign, True, Revoke certifications
- Sign, True, Revoke keys
- Sign, True, Revoke subkeys
- Sign, True, Generate timestamp signatures
- Sign, True, Generate standalone signatures
- Sign, False, Generate third party confirmation signatures
- Verify, True, Verify detached signatures
- Verify, True, Verify inline signatures of canonical documents
- Verify, True, Verify messages
- Verify, True, Verify key signatures
- Verify, True, Verify Use r ID certification signatures
- Verify, True, Verify User Attribute certification signatures
- Verify, True, Verify key binding signatures
- Verify, True, Verify key revocation signatures
- Verify, True, Verify subkey revocation signatures
- Verify, True, Verify certification revocation signatures
- Verify, True, Verify timestamp signatures
- Verify, True, Verify standalone signatures
- Verify, False, Verify third party confirmation signatures
- Revocation, True, Designate Revocation Key
- Revocation, True, Revoke (Sub)Key with Self Signature
- Revocation, False, Revoke (Sub)Key using Designated Revocation Key
Non-RFC Extensions
==================
This section covers things that are considered extensions to PGP, but are not codified in the form of an RFC.
.. progress:: DNS PKA
:text: Publishing OpenPGP keys in DNS
:Other Sources:
- DNS PKA, False, Look up and retrieve keys stored in DNS PKA records.
.. progress:: OpenPGP HTTP Keyserver Protocol (HKP)
:text: The protocol is specified in `Marc Horowitz's thesis paper`_, and an expired RFC draft by David Shaw, `draft-shaw-openpgp-hkp-00`_.
:HKP:
- HKP, False, Look up and retrieve keys from key server
- HKP, False, Send keys to key server
.. progress:: EdDSA for OpenPGP
:text: Use of Ed25519 with ECDSA and ECDH in OpenPGP is currently specified in an in-progress RFC draft by Werner Koch, `draft-koch-eddsa-for-openpgp`_.
:Curves:
- Curve, False, Ed25519
.. progress:: Additional Curves for OpenPGP
:text: Some additional curves that can be used with ECDSA/ECDH that are not explicitly called out in :rfc:`6637`, but have standardized OIDs and are implemented in other software.
:Curves:
- Curve, True, Brainpool P-256
- Curve, True, Brainpool P-384
- Curve, True, Brainpool P-512
- Curve, False, Curve25519
- Curve, True, SECP256K1
.. note:: Use of Brainpool curves with ECDSA/ECDH
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
.. _`Marc Horowitz's thesis paper`: http://www.mit.edu/afs/net.mit.edu/project/pks/thesis/paper/thesis.html
.. _`draft-shaw-openpgp-hkp-00`: https://tools.ietf.org/html/draft-shaw-openpgp-hkp-00
.. _`draft-koch-eddsa-for-openpgp`: https://tools.ietf.org/html/draft-koch-eddsa-for-openpgp-04 python-pgpy-0.4.3/gentoo/ 0000775 0000000 0000000 00000000000 13145172200 0015302 5 ustar 00root root 0000000 0000000 python-pgpy-0.4.3/gentoo/pgpy-0.4.0.ebuild 0000664 0000000 0000000 00000001733 13145172200 0020110 0 ustar 00root root 0000000 0000000 # Copyright 2014 Michael Greene
# Distributed under the terms of the BSD 3-Clause License
# $HEADER: $
EAPI=5
PYTHON_COMPAT=( python{2_7,3_3,3_4,3_5} )
inherit distutils-r1
DESCRIPTION="Pretty Good Privacy for Python - a pure Python OpenPGP implementation."
HOMEPAGE="https://github.com/SecurityInnovation/PGPy"
SRC_URI="https://pypi.python.org/packages/0a/2c/bfe57ac97d31fcd7172df43770d68bab1fbd38d629448ec8013f4714e779/PGPy-0.4.0a.tar.gz"
LICENSE="BSD"
SLOT="0"
KEYWORDS="~amd64"
IUSE=""
DEPEND="dev-python/setuptools[${PYTHON_USEDEP}]"
RDEPEND="dev-python/singledispatch[${PYTHON_USEDEP}]
dev-python/pyasn1[${PYTHON_USEDEP}]
>=dev-python/six-1.9.0[${PYTHON_USEDEP}]
>=dev-python/cryptography-1.1.0[${PYTHON_USEDEP}]
$(python_gen_cond_dep 'dev-python/enum34[${PYTHON_USEDEP}]' python2_7 python3_3)"
DOCS=( README.rst )
src_unpack() {
if [ "${A}" != "" ]; then
unpack ${A}
fi
cd "${WORKDIR}"
mv PGPy-${PV} pgpy-${PV}
}
python-pgpy-0.4.3/install_dependencies.linux.sh 0000775 0000000 0000000 00000001362 13145172200 0021662 0 ustar 00root root 0000000 0000000 #!/bin/bash
sudo apt-get update
sudo apt-get install -y libffi-dev gnupg2
if [ -z "${TOXENV}" ]; then
sudo apt-get install gpgsm libassuan-dev libgpg-error-dev swig
# build/install gpgme 1.8.0 manually
wget https://www.gnupg.org/ftp/gcrypt/gpgme/gpgme-1.7.0.tar.bz2
tar -xvf gpgme-1.7.0.tar.bz2
pushd gpgme-1.7.0
./configure \
--prefix=/usr \
--disable-fd-passing \
--disable-static \
--disable-gpgsm-test \
--infodir=/usr/share/info \
--with-gpg=/usr/bin/gpg \
--with-gpgsm=/usr/bin/gpgsm \
--with-gpgconf=/usr/bin/gpgconf
make
sudo make install
sudo ldconfig
popd
gpgconf --kill gpg-agent
gpg-agent --daemon --homedir tests/gnupghome
fi
python-pgpy-0.4.3/install_dependencies.osx.sh 0000775 0000000 0000000 00000001527 13145172200 0021337 0 ustar 00root root 0000000 0000000 #!/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
python-pgpy-0.4.3/pgpy/ 0000775 0000000 0000000 00000000000 13145172200 0014766 5 ustar 00root root 0000000 0000000 python-pgpy-0.4.3/pgpy/__init__.py 0000664 0000000 0000000 00000000735 13145172200 0017104 0 ustar 00root root 0000000 0000000 """ PGPy :: Pretty Good Privacy for Python
"""
from ._author import *
from .pgp import PGPKey
from .pgp import PGPKeyring
from .pgp import PGPMessage
from .pgp import PGPSignature
from .pgp import PGPUID
__all__ = ['__author__',
'__copyright__',
'__license__',
'__version__',
'constants',
'errors',
'PGPKey',
'PGPKeyring',
'PGPMessage',
'PGPSignature',
'PGPUID', ]
python-pgpy-0.4.3/pgpy/_author.py 0000664 0000000 0000000 00000000707 13145172200 0017005 0 ustar 00root root 0000000 0000000 """_author.py
Canonical location for authorship information
__version__ is a PEP-386 compliant version string,
making use of distutils.version.LooseVersion
"""
from distutils.version import LooseVersion
__all__ = ['__author__',
'__copyright__',
'__license__',
'__version__']
__author__ = "Michael Greene"
__copyright__ = "Copyright (c) 2014 Michael Greene"
__license__ = "BSD"
__version__ = str(LooseVersion("0.4.3"))
python-pgpy-0.4.3/pgpy/_curves.py 0000664 0000000 0000000 00000003207 13145172200 0017010 0 ustar 00root root 0000000 0000000 """ _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
python-pgpy-0.4.3/pgpy/constants.py 0000664 0000000 0000000 00000033601 13145172200 0017357 0 ustar 00root root 0000000 0000000 """ constants.py
"""
import bz2
import hashlib
import imghdr
import os
import time
import zlib
from collections import namedtuple
from enum import Enum
from enum import IntEnum
from pyasn1.type.univ import ObjectIdentifier
import six
from cryptography.hazmat.backends import openssl
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.ciphers import algorithms
from .decorators import classproperty
from .types import FlagEnum
from ._curves import BrainpoolP256R1, BrainpoolP384R1, BrainpoolP512R1
__all__ = ['Backend',
'EllipticCurveOID',
'PacketTag',
'SymmetricKeyAlgorithm',
'PubKeyAlgorithm',
'CompressionAlgorithm',
'HashAlgorithm',
'RevocationReason',
'ImageEncoding',
'SignatureType',
'KeyServerPreferences',
'String2KeyType',
'TrustLevel',
'KeyFlags',
'Features',
'RevocationKeyClass',
'NotationDataFlags',
'TrustFlags']
# this is 50 KiB
_hashtunedata = bytearray([10, 11, 12, 13, 14, 15, 16, 17] * 128 * 50)
class Backend(Enum):
OpenSSL = openssl.backend
class EllipticCurveOID(Enum):
# these are specified as:
# id = (oid, curve)
Invalid = ('', )
#: DJB's fast elliptic curve
#:
#: .. warning::
#: This curve is not currently usable by PGPy
Curve25519 = ('1.3.6.1.4.1.3029.1.5.1', )
#: Twisted Edwards variant of Curve25519
#:
#: .. warning::
#: This curve is not currently usable by PGPy
Ed25519 = ('1.3.6.1.4.1.11591.15.1', )
#: NIST P-256, also known as SECG curve secp256r1
NIST_P256 = ('1.2.840.10045.3.1.7', ec.SECP256R1)
#: NIST P-384, also known as SECG curve secp384r1
NIST_P384 = ('1.3.132.0.34', ec.SECP384R1)
#: NIST P-521, also known as SECG curve secp521r1
NIST_P521 = ('1.3.132.0.35', ec.SECP521R1)
#: Brainpool Standard Curve, 256-bit
#:
#: .. note::
#: Requires OpenSSL >= 1.0.2
Brainpool_P256 = ('1.3.36.3.3.2.8.1.1.7', BrainpoolP256R1)
#: Brainpool Standard Curve, 384-bit
#:
#: .. note::
#: Requires OpenSSL >= 1.0.2
Brainpool_P384 = ('1.3.36.3.3.2.8.1.1.11', BrainpoolP384R1)
#: Brainpool Standard Curve, 512-bit
#:
#: .. note::
#: Requires OpenSSL >= 1.0.2
Brainpool_P512 = ('1.3.36.3.3.2.8.1.1.13', BrainpoolP512R1)
#: SECG curve secp256k1
SECP256K1 = ('1.3.132.0.10', ec.SECP256K1)
def __new__(cls, oid, curve=None):
# preprocessing stage for enum members:
# - set enum_member.value to ObjectIdentifier(oid)
# - if curve is not None and curve.name is in ec._CURVE_TYPES, set enum_member.curve to curve
# - otherwise, set enum_member.curve to None
obj = object.__new__(cls)
obj._value_ = ObjectIdentifier(oid)
obj.curve = None
if curve is not None and curve.name in ec._CURVE_TYPES:
obj.curve = curve
return obj
@property
def can_gen(self):
return self.curve is not None
@property
def key_size(self):
if self.curve is not None:
return self.curve.key_size
@property
def kdf_halg(self):
# return the hash algorithm to specify in the KDF fields when generating a key
algs = {256: HashAlgorithm.SHA256,
384: HashAlgorithm.SHA384,
512: HashAlgorithm.SHA512,
521: HashAlgorithm.SHA512}
return algs.get(self.key_size, None)
@property
def kek_alg(self):
# return the AES algorithm to specify in the KDF fields when generating a key
algs = {256: SymmetricKeyAlgorithm.AES128,
384: SymmetricKeyAlgorithm.AES192,
512: SymmetricKeyAlgorithm.AES256,
521: SymmetricKeyAlgorithm.AES256}
return algs.get(self.key_size, None)
class PacketTag(IntEnum):
Invalid = 0
PublicKeyEncryptedSessionKey = 1
Signature = 2
SymmetricKeyEncryptedSessionKey = 3
OnePassSignature = 4
SecretKey = 5
PublicKey = 6
SecretSubKey = 7
CompressedData = 8
SymmetricallyEncryptedData = 9
Marker = 10
LiteralData = 11
Trust = 12
UserID = 13
PublicSubKey = 14
UserAttribute = 17
SymmetricallyEncryptedIntegrityProtectedData = 18
ModificationDetectionCode = 19
class SymmetricKeyAlgorithm(IntEnum):
"""Supported symmetric key algorithms."""
Plaintext = 0x00
#: .. warning::
#: IDEA is insecure. PGPy only allows it to be used for decryption, not encryption!
IDEA = 0x01
#: Triple-DES with 168-bit key derived from 192
TripleDES = 0x02
#: CAST5 (or CAST-128) with 128-bit key
CAST5 = 0x03
#: Blowfish with 128-bit key and 16 rounds
Blowfish = 0x04
#: AES with 128-bit key
AES128 = 0x07
#: AES with 192-bit key
AES192 = 0x08
#: AES with 256-bit key
AES256 = 0x09
# Twofish with 256-bit key - not currently supported
Twofish256 = 0x0A
#: Camellia with 128-bit key
Camellia128 = 0x0B
#: Camellia with 192-bit key
Camellia192 = 0x0C
#: Camellia with 256-bit key
Camellia256 = 0x0D
@property
def cipher(self):
bs = {SymmetricKeyAlgorithm.IDEA: algorithms.IDEA,
SymmetricKeyAlgorithm.TripleDES: algorithms.TripleDES,
SymmetricKeyAlgorithm.CAST5: algorithms.CAST5,
SymmetricKeyAlgorithm.Blowfish: algorithms.Blowfish,
SymmetricKeyAlgorithm.AES128: algorithms.AES,
SymmetricKeyAlgorithm.AES192: algorithms.AES,
SymmetricKeyAlgorithm.AES256: algorithms.AES,
SymmetricKeyAlgorithm.Twofish256: namedtuple('Twofish256', ['block_size'])(block_size=128),
SymmetricKeyAlgorithm.Camellia128: algorithms.Camellia,
SymmetricKeyAlgorithm.Camellia192: algorithms.Camellia,
SymmetricKeyAlgorithm.Camellia256: algorithms.Camellia}
if self in bs:
return bs[self]
raise NotImplementedError(repr(self))
@property
def is_insecure(self):
insecure_ciphers = {SymmetricKeyAlgorithm.IDEA}
return self in insecure_ciphers
@property
def block_size(self):
return self.cipher.block_size
@property
def key_size(self):
ks = {SymmetricKeyAlgorithm.IDEA: 128,
SymmetricKeyAlgorithm.TripleDES: 192,
SymmetricKeyAlgorithm.CAST5: 128,
SymmetricKeyAlgorithm.Blowfish: 128,
SymmetricKeyAlgorithm.AES128: 128,
SymmetricKeyAlgorithm.AES192: 192,
SymmetricKeyAlgorithm.AES256: 256,
SymmetricKeyAlgorithm.Twofish256: 256,
SymmetricKeyAlgorithm.Camellia128: 128,
SymmetricKeyAlgorithm.Camellia192: 192,
SymmetricKeyAlgorithm.Camellia256: 256}
if self in ks:
return ks[self]
raise NotImplementedError(repr(self))
def gen_iv(self):
return os.urandom(self.block_size // 8)
def gen_key(self):
return os.urandom(self.key_size // 8)
class PubKeyAlgorithm(IntEnum):
Invalid = 0x00
#: Signifies that a key is an RSA key.
RSAEncryptOrSign = 0x01
RSAEncrypt = 0x02 # deprecated
RSASign = 0x03 # deprecated
#: Signifies that a key is an ElGamal key.
ElGamal = 0x10
#: Signifies that a key is a DSA key.
DSA = 0x11
#: Signifies that a key is an ECDH key.
ECDH = 0x12
#: Signifies that a key is an ECDSA key.
ECDSA = 0x13
FormerlyElGamalEncryptOrSign = 0x14 # deprecated - do not generate
DiffieHellman = 0x15 # X9.42
EdDSA = 0x16 # https://tools.ietf.org/html/draft-koch-eddsa-for-openpgp-04
@property
def can_gen(self):
return self in {PubKeyAlgorithm.RSAEncryptOrSign,
PubKeyAlgorithm.DSA,
PubKeyAlgorithm.ECDSA,
PubKeyAlgorithm.ECDH}
@property
def can_encrypt(self): # pragma: no cover
return self in {PubKeyAlgorithm.RSAEncryptOrSign, PubKeyAlgorithm.ElGamal, PubKeyAlgorithm.ECDH}
@property
def can_sign(self):
return self in {PubKeyAlgorithm.RSAEncryptOrSign, PubKeyAlgorithm.DSA, PubKeyAlgorithm.ECDSA}
@property
def deprecated(self):
return self in {PubKeyAlgorithm.RSAEncrypt,
PubKeyAlgorithm.RSASign,
PubKeyAlgorithm.FormerlyElGamalEncryptOrSign}
class CompressionAlgorithm(IntEnum):
#: No compression
Uncompressed = 0x00
#: ZIP DEFLATE
ZIP = 0x01
#: ZIP DEFLATE with zlib headers
ZLIB = 0x02
#: Bzip2
BZ2 = 0x03
def compress(self, data):
if self is CompressionAlgorithm.Uncompressed:
return data
if self is CompressionAlgorithm.ZIP:
return zlib.compress(data)[2:-4]
if self is CompressionAlgorithm.ZLIB:
return zlib.compress(data)
if self is CompressionAlgorithm.BZ2:
return bz2.compress(data)
raise NotImplementedError(self)
def decompress(self, data):
if six.PY2:
data = bytes(data)
if self is CompressionAlgorithm.Uncompressed:
return data
if self is CompressionAlgorithm.ZIP:
return zlib.decompress(data, -15)
if self is CompressionAlgorithm.ZLIB:
return zlib.decompress(data)
if self is CompressionAlgorithm.BZ2:
return bz2.decompress(data)
raise NotImplementedError(self)
class HashAlgorithm(IntEnum):
Invalid = 0x00
MD5 = 0x01
SHA1 = 0x02
RIPEMD160 = 0x03
_reserved_1 = 0x04
_reserved_2 = 0x05
_reserved_3 = 0x06
_reserved_4 = 0x07
SHA256 = 0x08
SHA384 = 0x09
SHA512 = 0x0A
SHA224 = 0x0B
def __init__(self, *args):
super(self.__class__, self).__init__()
self._tuned_count = 0
@property
def hasher(self):
return hashlib.new(self.name)
@property
def digest_size(self):
return self.hasher.digest_size
@property
def tuned_count(self):
if self._tuned_count == 0:
self.tune_count()
return self._tuned_count
def tune_count(self):
start = end = 0
htd = _hashtunedata[:]
while start == end:
# potentially do this multiple times in case the resolution of time.time is low enough that
# hashing 100 KiB isn't enough time to produce a measurable difference
# (e.g. if the timer for time.time doesn't have enough precision)
htd = htd + htd
h = self.hasher
start = time.time()
h.update(htd)
end = time.time()
# now calculate how many bytes need to be hashed to reach our expected time period
# GnuPG tunes for about 100ms, so we'll do that as well
_TIME = 0.100
ct = int(len(htd) * (_TIME / (end - start)))
c1 = ((ct >> (ct.bit_length() - 5)) - 16)
c2 = (ct.bit_length() - 11)
c = ((c2 << 4) + c1)
# constrain self._tuned_count to be between 0 and 255
self._tuned_count = max(min(c, 255), 0)
class RevocationReason(IntEnum):
#: No reason was specified. This is the default reason.
NotSpecified = 0x00
#: The key was superseded by a new key. Only meaningful when revoking a key.
Superseded = 0x01
#: Key material has been compromised. Only meaningful when revoking a key.
Compromised = 0x02
#: Key is retired and no longer used. Only meaningful when revoking a key.
Retired = 0x03
#: User ID information is no longer valid. Only meaningful when revoking a certification of a user id.
UserID = 0x20
class ImageEncoding(IntEnum):
Unknown = 0x00
JPEG = 0x01
@classmethod
def encodingof(cls, imagebytes):
type = imghdr.what(None, h=imagebytes)
if type == 'jpeg':
return ImageEncoding.JPEG
return ImageEncoding.Unknown # pragma: no cover
class SignatureType(IntEnum):
BinaryDocument = 0x00
CanonicalDocument = 0x01
Standalone = 0x02
Generic_Cert = 0x10
Persona_Cert = 0x11
Casual_Cert = 0x12
Positive_Cert = 0x13
Subkey_Binding = 0x18
PrimaryKey_Binding = 0x19
DirectlyOnKey = 0x1F
KeyRevocation = 0x20
SubkeyRevocation = 0x28
CertRevocation = 0x30
Timestamp = 0x40
ThirdParty_Confirmation = 0x50
class KeyServerPreferences(IntEnum):
Unknown = 0x00
NoModify = 0x80
class String2KeyType(IntEnum):
Simple = 0
Salted = 1
Reserved = 2
Iterated = 3
class TrustLevel(IntEnum):
Unknown = 0
Expired = 1
Undefined = 2
Never = 3
Marginal = 4
Fully = 5
Ultimate = 6
class KeyFlags(FlagEnum):
#: Signifies that a key may be used to certify keys and user ids. Primary keys always have this, even if it is not specified.
Certify = 0x01
#: Signifies that a key may be used to sign messages and documents.
Sign = 0x02
#: Signifies that a key may be used to encrypt messages.
EncryptCommunications = 0x04
#: Signifies that a key may be used to encrypt storage. Currently equivalent to :py:obj:`~pgpy.constants.EncryptCommunications`.
EncryptStorage = 0x08
#: Signifies that the private component of a given key may have been split by a secret-sharing mechanism. Split
#: keys are not currently supported by PGPy.
Split = 0x10
#: Signifies that a key may be used for authentication.
Authentication = 0x20
#: Signifies that the private component of a key may be in the possession of more than one person.
MultiPerson = 0x80
class Features(FlagEnum):
ModificationDetection = 0x01
@classproperty
def pgpy_features(cls):
return Features.ModificationDetection
class RevocationKeyClass(FlagEnum):
Sensitive = 0x40
Normal = 0x80
class NotationDataFlags(FlagEnum):
HumanReadable = 0x80
class TrustFlags(FlagEnum):
Revoked = 0x20
SubRevoked = 0x40
Disabled = 0x80
PendingCheck = 0x100
python-pgpy-0.4.3/pgpy/decorators.py 0000664 0000000 0000000 00000007553 13145172200 0017517 0 ustar 00root root 0000000 0000000 """ decorators.py
"""
import contextlib
import functools
import six
import warnings
try:
from singledispatch import singledispatch
except ImportError: # pragma: no cover
from functools import singledispatch
from .errors import PGPError
__all__ = ['classproperty',
'sdmethod',
'sdproperty',
'KeyAction']
def classproperty(fget):
class ClassProperty(object):
def __init__(self, fget):
self.fget = fget
self.__doc__ = fget.__doc__
def __get__(self, cls, owner):
return self.fget(owner)
def __set__(self, obj, value): # pragma: no cover
raise AttributeError("Read-only attribute")
def __delete__(self, obj): # pragma: no cover
raise AttributeError("Read-only attribute")
return ClassProperty(fget)
def sdmethod(meth):
"""
This is a hack to monkey patch sdproperty to work as expected with instance methods.
"""
sd = singledispatch(meth)
def wrapper(obj, *args, **kwargs):
return sd.dispatch(args[0].__class__)(obj, *args, **kwargs)
wrapper.register = sd.register
wrapper.dispatch = sd.dispatch
wrapper.registry = sd.registry
wrapper._clear_cache = sd._clear_cache
functools.update_wrapper(wrapper, meth)
return wrapper
def sdproperty(fget):
def defset(obj, val): # pragma: no cover
raise TypeError(str(val.__class__))
class SDProperty(property):
def register(self, cls=None, fset=None):
return self.fset.register(cls, fset)
def setter(self, fset):
self.register(object, fset)
return type(self)(self.fget, self.fset, self.fdel, self.__doc__)
return SDProperty(fget, sdmethod(defset))
class KeyAction(object):
def __init__(self, *usage, **conditions):
super(KeyAction, self).__init__()
self.flags = set(usage)
self.conditions = conditions
@contextlib.contextmanager
def usage(self, key, user):
def _preiter(first, iterable):
yield first
for item in iterable:
yield item
em = {}
em['keyid'] = key.fingerprint.keyid
em['flags'] = ', '.join(flag.name for flag in self.flags)
if len(self.flags):
for _key in _preiter(key, key.subkeys.values()):
if self.flags & set(_key._get_key_flags(user)):
break
else: # pragma: no cover
raise PGPError("Key {keyid:s} does not have the required usage flag {flags:s}".format(**em))
else:
_key = key
if _key is not key:
em['subkeyid'] = _key.fingerprint.keyid
warnings.warn("Key {keyid:s} does not have the required usage flag {flags:s}; using subkey {subkeyid:s}"
"".format(**em), stacklevel=4)
yield _key
def check_attributes(self, key):
for attr, expected in self.conditions.items():
if getattr(key, attr) != expected:
raise PGPError("Expected: {attr:s} == {eval:s}. Got: {got:s}"
"".format(attr=attr, eval=str(expected), got=str(getattr(key, attr))))
def __call__(self, action):
# @functools.wraps(action)
@six.wraps(action)
def _action(key, *args, **kwargs):
if key._key is None:
raise PGPError("No key!")
# if a key is in the process of being created, it needs to be allowed to certify its own user id
if len(key._uids) == 0 and key.is_primary and action is not key.certify.__wrapped__:
raise PGPError("Key is not complete - please add a User ID!")
with self.usage(key, kwargs.get('user', None)) as _key:
self.check_attributes(key)
# do the thing
return action(_key, *args, **kwargs)
return _action
python-pgpy-0.4.3/pgpy/errors.py 0000664 0000000 0000000 00000001555 13145172200 0016662 0 ustar 00root root 0000000 0000000 """ errors.py
"""
__all__ = ('PGPError',
'PGPEncryptionError',
'PGPDecryptionError',
'PGPOpenSSLCipherNotSupported',
'PGPInsecureCipher',
'WontImplementError',)
class PGPError(Exception):
"""Raised as a general error in PGPy"""
pass
class PGPEncryptionError(Exception):
"""Raised when encryption fails"""
pass
class PGPDecryptionError(Exception):
"""Raised when decryption fails"""
pass
class PGPOpenSSLCipherNotSupported(Exception):
"""Raised when OpenSSL does not support the requested cipher"""
pass
class PGPInsecureCipher(Exception):
"""Raised when a cipher known to be insecure is attempted to be used to encrypt data"""
pass
class WontImplementError(NotImplementedError):
"""Raised when something that is not implemented, will not be implemented"""
pass
python-pgpy-0.4.3/pgpy/memoryview.py 0000664 0000000 0000000 00000010562 13145172200 0017547 0 ustar 00root root 0000000 0000000 """ util.py
"""
import six
__all__ = ('memoryview', )
memoryview = memoryview
if six.PY2:
# because Python2's memoryview can't be released directly, nor can it be used as a context manager
# this wrapper object should hopefully make the behavior more uniform to python 3's
import __builtin__
import functools
# this decorator will raise a ValueError if the wrapped memoryview object has been "released"
def notreleased(meth):
@functools.wraps(meth)
def _inner(self, *args, **kwargs):
if self._mem is None:
raise ValueError("operation forbidden on released memoryview object")
return meth(self, *args, **kwargs)
return _inner
class memoryview(object): # flake8: noqa
@property
@notreleased
def obj(self):
"""The underlying object of the memoryview."""
return self._obj
@property
@notreleased
def nbytes(self):
# nbytes == product(shape) * itemsize == len(m.tobytes())
nb = 1
for dim in self.shape:
nb *= dim
return nb * self.itemsize
# TODO: c_contiguous -> (self.ndim == 0 or ???)
# TODO: f_contiguous -> (self.ndim == 0 or ???)
# TODO: contiguous -> return self.c_contiguous or self.f_contiguous
def __new__(cls, obj, parent=None):
memview = object.__new__(cls)
memview._obj = obj if parent is None else parent.obj
return memview
def __init__(self, obj):
if not hasattr(self, '_mem'):
if not isinstance(obj, __builtin__.memoryview):
obj = __builtin__.memoryview(obj)
self._mem = obj
def __dir__(self):
# so dir(...) looks like a memoryview object, and also
# contains our additional methods and properties, but not our instance members
return sorted(set(self.__class__.__dict__) | set(dir(self._mem)))
@notreleased
def __getitem__(self, item):
# if this is a slice, it'll return another real memoryview object
# we'll need to wrap that subview in another memoryview wrapper
if isinstance(item, slice):
return memoryview(self._mem.__getitem__(item))
return self._mem.__getitem__(item)
@notreleased
def __setitem__(self, key, value):
self._mem.__setitem__(key, value)
@notreleased
def __delitem__(self, key):
raise TypeError("cannot delete memory")
def __getattribute__(self, item):
try:
return object.__getattribute__(self, item)
except AttributeError:
if object.__getattribute__(self, '_mem') is None:
raise ValueError("operation forbidden on released memoryview object")
return object.__getattribute__(self, '_mem').__getattribute__(item)
def __setattr__(self, key, value):
if key not in self.__dict__ and hasattr(__builtin__.memoryview, key):
# there are no writable attributes on memoryview objects
# changing indexed values is handled by __setitem__
raise AttributeError("attribute '{}' of 'memoryview' objects is not writable".format(key))
else:
object.__setattr__(self, key, value)
@notreleased
def __len__(self):
return len(self._mem)
def __eq__(self, other):
if isinstance(other, memoryview):
return self._mem == other._mem
return self._mem == other
@notreleased
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.release()
def __repr__(self):
return '<{}memory at 0x{:02X}>'.format('' if self._mem else 'released ', id(self))
def release(self):
"""Release the underlying buffer exposed by the memoryview object"""
# this should effectively do the same job as memoryview.release() in Python 3
self._mem = None
self._obj = None
@notreleased
def hex(self):
"""Return the data in the buffer as a string of hexadecimal numbers."""
return ''.join(('{:02X}'.format(ord(c)) for c in self._mem))
# TODO: cast
python-pgpy-0.4.3/pgpy/packet/ 0000775 0000000 0000000 00000000000 13145172200 0016235 5 ustar 00root root 0000000 0000000 python-pgpy-0.4.3/pgpy/packet/__init__.py 0000664 0000000 0000000 00000000440 13145172200 0020344 0 ustar 00root root 0000000 0000000 from .types import Key
from .types import Opaque
from .types import Packet
from .types import Primary
from .types import Private
from .types import Public
from .types import Sub
from .packets import * # NOQA
__all__ = ['Key', 'Opaque', 'Packet', 'Primary', 'Private', 'Public', 'Sub']
python-pgpy-0.4.3/pgpy/packet/fields.py 0000664 0000000 0000000 00000133633 13145172200 0020066 0 ustar 00root root 0000000 0000000 """ fields.py
"""
from __future__ import absolute_import, division
import abc
import binascii
import collections
import copy
import hashlib
import itertools
import math
import os
from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder
from pyasn1.type.univ import Integer
from pyasn1.type.univ import Sequence
from pyasn1.type.namedtype import NamedTypes, NamedType
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import dsa
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
from cryptography.hazmat.primitives.keywrap import aes_key_wrap
from cryptography.hazmat.primitives.keywrap import aes_key_unwrap
from cryptography.hazmat.primitives.padding import PKCS7
from .subpackets import Signature as SignatureSP
from .subpackets import UserAttribute
from .subpackets import signature
from .subpackets import userattribute
from .types import MPI
from .types import MPIs
from ..constants import EllipticCurveOID
from ..constants import HashAlgorithm
from ..constants import PubKeyAlgorithm
from ..constants import String2KeyType
from ..constants import SymmetricKeyAlgorithm
from ..decorators import sdproperty
from ..errors import PGPDecryptionError
from ..errors import PGPError
from ..symenc import _decrypt
from ..symenc import _encrypt
from ..types import Field
__all__ = ['SubPackets',
'UserAttributeSubPackets',
'Signature',
'RSASignature',
'DSASignature',
'ECDSASignature',
'PubKey',
'OpaquePubKey',
'RSAPub',
'DSAPub',
'ElGPub',
'ECDSAPub',
'ECDHPub',
'String2Key',
'ECKDF',
'PrivKey',
'OpaquePrivKey',
'RSAPriv',
'DSAPriv',
'ElGPriv',
'ECDSAPriv',
'ECDHPriv',
'CipherText',
'RSACipherText',
'ElGCipherText',
'ECDHCipherText', ]
class SubPackets(collections.MutableMapping, Field):
_spmodule = signature
def __init__(self):
super(SubPackets, self).__init__()
self._hashed_sp = collections.OrderedDict()
self._unhashed_sp = collections.OrderedDict()
def __bytearray__(self):
_bytes = bytearray()
_bytes += self.__hashbytearray__()
_bytes += self.__unhashbytearray__()
return _bytes
def __hashbytearray__(self):
_bytes = bytearray()
_bytes += self.int_to_bytes(sum(len(sp) for sp in self._hashed_sp.values()), 2)
for hsp in self._hashed_sp.values():
_bytes += hsp.__bytearray__()
return _bytes
def __unhashbytearray__(self):
_bytes = bytearray()
_bytes += self.int_to_bytes(sum(len(sp) for sp in self._unhashed_sp.values()), 2)
for uhsp in self._unhashed_sp.values():
_bytes += uhsp.__bytearray__()
return _bytes
def __len__(self): # pragma: no cover
return sum(sp.header.length for sp in itertools.chain(self._hashed_sp.values(), self._unhashed_sp.values())) + 4
def __iter__(self):
for sp in itertools.chain(self._hashed_sp.values(), self._unhashed_sp.values()):
yield sp
def __setitem__(self, key, val):
# the key provided should always be the classname for the subpacket
# but, there can be multiple subpackets of the same type
# so, it should be stored in the format: [h_]_
# where:
# - is the classname of val
# - is a sequence id, starting at 0, for a given classname
i = 0
if isinstance(key, tuple): # pragma: no cover
key, i = key
d = self._unhashed_sp
if key.startswith('h_'):
d, key = self._hashed_sp, key[2:]
while (key, i) in d:
i += 1
d[(key, i)] = val
def __getitem__(self, key):
if isinstance(key, tuple): # pragma: no cover
return self._hashed_sp.get(key, self._unhashed_sp.get(key))
if key.startswith('h_'):
return [v for k, v in self._hashed_sp.items() if key[2:] == k[0]]
else:
return [v for k, v in itertools.chain(self._hashed_sp.items(), self._unhashed_sp.items()) if key == k[0]]
def __delitem__(self, key):
##TODO: this
raise NotImplementedError
def __contains__(self, key):
return key in set(k for k, _ in itertools.chain(self._hashed_sp, self._unhashed_sp))
def __copy__(self):
sp = SubPackets()
sp._hashed_sp = self._hashed_sp.copy()
sp._unhashed_sp = self._unhashed_sp.copy()
return sp
def addnew(self, spname, hashed=False, **kwargs):
nsp = getattr(self._spmodule, spname)()
for p, v in kwargs.items():
if hasattr(nsp, p):
setattr(nsp, p, v)
nsp.update_hlen()
if hashed:
self['h_' + spname] = nsp
else:
self[spname] = nsp
def update_hlen(self):
for sp in self:
sp.update_hlen()
def parse(self, packet):
hl = self.bytes_to_int(packet[:2])
del packet[:2]
# we do it this way because we can't ensure that subpacket headers are sized appropriately
# for their contents, but we can at least output that correctly
# so instead of tracking how many bytes we can now output, we track how many bytes have we parsed so far
plen = len(packet)
while plen - len(packet) < hl:
sp = SignatureSP(packet)
self['h_' + sp.__class__.__name__] = sp
uhl = self.bytes_to_int(packet[:2])
del packet[:2]
plen = len(packet)
while plen - len(packet) < uhl:
sp = SignatureSP(packet)
self[sp.__class__.__name__] = sp
class UserAttributeSubPackets(SubPackets):
"""
This is nearly the same as just the unhashed subpackets from above,
except that there isn't a length specifier. So, parse will only parse one packet,
appending that one packet to self.__unhashed_sp.
"""
_spmodule = userattribute
def __bytearray__(self):
_bytes = bytearray()
for uhsp in self._unhashed_sp.values():
_bytes += uhsp.__bytearray__()
return _bytes
def __len__(self): # pragma: no cover
return sum(len(sp) for sp in self._unhashed_sp.values())
def parse(self, packet):
# parse just one packet and add it to the unhashed subpacket ordereddict
# I actually have yet to come across a User Attribute packet with more than one subpacket
# which makes sense, given that there is only one defined subpacket
sp = UserAttribute(packet)
self[sp.__class__.__name__] = sp
class Signature(MPIs):
def __init__(self):
for i in self.__mpis__:
setattr(self, i, MPI(0))
def __bytearray__(self):
_bytes = bytearray()
for i in self:
_bytes += i.to_mpibytes()
return _bytes
@abc.abstractproperty
def __sig__(self):
"""return the signature bytes in a format that can be understood by the signature verifier"""
@abc.abstractmethod
def from_signer(self, sig):
"""create and parse a concrete Signature class instance"""
class RSASignature(Signature):
__mpis__ = ('md_mod_n', )
def __sig__(self):
return self.md_mod_n.to_mpibytes()[2:]
def parse(self, packet):
self.md_mod_n = MPI(packet)
def from_signer(self, sig):
self.md_mod_n = MPI(self.bytes_to_int(sig))
class DSASignature(Signature):
__mpis__ = ('r', 's')
def __sig__(self):
# return the signature data into an ASN.1 sequence of integers in DER format
seq = Sequence(componentType=NamedTypes(*[NamedType(n, Integer()) for n in self.__mpis__]))
for n in self.__mpis__:
seq.setComponentByName(n, getattr(self, n))
return encoder.encode(seq)
def from_signer(self, sig):
##TODO: just use pyasn1 for this
def _der_intf(_asn):
if _asn[0] != 0x02: # pragma: no cover
raise ValueError("Expected: Integer (0x02). Got: 0x{:02X}".format(_asn[0]))
del _asn[0]
if _asn[0] & 0x80: # pragma: no cover
llen = _asn[0] & 0x7F
del _asn[0]
flen = self.bytes_to_int(_asn[:llen])
del _asn[:llen]
else:
flen = _asn[0] & 0x7F
del _asn[0]
i = self.bytes_to_int(_asn[:flen])
del _asn[:flen]
return i
if isinstance(sig, bytes):
sig = bytearray(sig)
# this is a very limited asn1 decoder - it is only intended to decode a DER encoded sequence of integers
if not sig[0] == 0x30:
raise NotImplementedError("Expected: Sequence (0x30). Got: 0x{:02X}".format(sig[0]))
del sig[0]
# skip the sequence length field
if sig[0] & 0x80: # pragma: no cover
llen = sig[0] & 0x7F
del sig[:llen + 1]
else:
del sig[0]
self.r = MPI(_der_intf(sig))
self.s = MPI(_der_intf(sig))
def parse(self, packet):
self.r = MPI(packet)
self.s = MPI(packet)
class ECDSASignature(DSASignature):
def from_signer(self, sig):
seq, _ = decoder.decode(sig)
self.r = MPI(seq[0])
self.s = MPI(seq[1])
class PubKey(MPIs):
__pubfields__ = ()
@property
def __mpis__(self):
for i in self.__pubfields__:
yield i
def __init__(self):
super(PubKey, self).__init__()
for field in self.__pubfields__:
if isinstance(field, tuple): # pragma: no cover
field, val = field
else:
val = MPI(0)
setattr(self, field, val)
@abc.abstractmethod
def __pubkey__(self):
"""return the requisite *PublicKey class from the cryptography library"""
def __len__(self):
return sum(len(getattr(self, i)) for i in self.__pubfields__)
def __bytearray__(self):
_bytes = bytearray()
for field in self.__pubfields__:
_bytes += getattr(self, field).to_mpibytes()
return _bytes
def publen(self):
return len(self)
def verify(self, subj, sigbytes, hash_alg):
return NotImplemented # pragma: no cover
class OpaquePubKey(PubKey): # pragma: no cover
def __init__(self):
super(OpaquePubKey, self).__init__()
self.data = bytearray()
def __iter__(self):
yield self.data
def __pubkey__(self):
return NotImplemented
def __bytearray__(self):
return self.data
def parse(self, packet):
##TODO: this needs to be length-bounded to the end of the packet
self.data = packet
class RSAPub(PubKey):
__pubfields__ = ('n', 'e')
def __pubkey__(self):
return rsa.RSAPublicNumbers(self.e, self.n).public_key(default_backend())
def verify(self, subj, sigbytes, hash_alg):
# zero-pad sigbytes if necessary
sigbytes = (b'\x00' * (self.n.byte_length() - len(sigbytes))) + sigbytes
verifier = self.__pubkey__().verifier(sigbytes, padding.PKCS1v15(), hash_alg)
verifier.update(subj)
try:
verifier.verify()
except InvalidSignature:
return False
return True
def parse(self, packet):
self.n = MPI(packet)
self.e = MPI(packet)
class DSAPub(PubKey):
__pubfields__ = ('p', 'q', 'g', 'y')
def __pubkey__(self):
params = dsa.DSAParameterNumbers(self.p, self.q, self.g)
return dsa.DSAPublicNumbers(self.y, params).public_key(default_backend())
def verify(self, subj, sigbytes, hash_alg):
verifier = self.__pubkey__().verifier(sigbytes, hash_alg)
verifier.update(subj)
try:
verifier.verify()
except InvalidSignature:
return False
return True
def parse(self, packet):
self.p = MPI(packet)
self.q = MPI(packet)
self.g = MPI(packet)
self.y = MPI(packet)
class ElGPub(PubKey):
__pubfields__ = ('p', 'g', 'y')
def __pubkey__(self):
raise NotImplementedError()
def parse(self, packet):
self.p = MPI(packet)
self.g = MPI(packet)
self.y = MPI(packet)
class ECDSAPub(PubKey):
__pubfields__ = ('x', 'y')
def __init__(self):
super(ECDSAPub, self).__init__()
self.oid = None
def __len__(self):
return sum([len(getattr(self, i)) - 2 for i in self.__pubfields__] +
[3, len(encoder.encode(self.oid.value)) - 1])
def __pubkey__(self):
return ec.EllipticCurvePublicNumbers(self.x, self.y, self.oid.curve()).public_key(default_backend())
def __bytearray__(self):
_b = bytearray()
_b += encoder.encode(self.oid.value)[1:]
# 0x04 || x || y
# where x and y are the same length
_xy = b'\x04' + self.x.to_mpibytes()[2:] + self.y.to_mpibytes()[2:]
_b += MPI(self.bytes_to_int(_xy, 'big')).to_mpibytes()
return _b
def __copy__(self):
pkt = super(ECDSAPub, self).__copy__()
pkt.oid = self.oid
return pkt
def verify(self, subj, sigbytes, hash_alg):
verifier = self.__pubkey__().verifier(sigbytes, ec.ECDSA(hash_alg))
verifier.update(subj)
try:
verifier.verify()
except InvalidSignature:
return False
return True
def parse(self, packet):
oidlen = packet[0]
del packet[0]
_oid = bytearray(b'\x06')
_oid.append(oidlen)
_oid += bytearray(packet[:oidlen])
# try:
oid, _ = decoder.decode(bytes(_oid))
# except:
# raise PGPError("Bad OID octet stream: b'{:s}'".format(''.join(['\\x{:02X}'.format(c) for c in _oid])))
self.oid = EllipticCurveOID(oid)
del packet[:oidlen]
# flen = (self.oid.bit_length // 8)
xy = bytearray(MPI(packet).to_mpibytes()[2:])
# xy = bytearray(MPI(packet).to_bytes(flen, 'big'))
# the first byte is just \x04
del xy[:1]
# now xy needs to be separated into x, y
xylen = len(xy)
x, y = xy[:xylen // 2], xy[xylen // 2:]
self.x = MPI(self.bytes_to_int(x))
self.y = MPI(self.bytes_to_int(y))
class ECDHPub(PubKey):
__pubfields__ = ('x', 'y')
def __init__(self):
super(ECDHPub, self).__init__()
self.oid = None
self.kdf = ECKDF()
def __len__(self):
return sum([len(getattr(self, i)) - 2 for i in self.__pubfields__] +
[3,
len(self.kdf),
len(encoder.encode(self.oid.value)) - 1])
def __pubkey__(self):
return ec.EllipticCurvePublicNumbers(self.x, self.y, self.oid.curve()).public_key(default_backend())
def __bytearray__(self):
_b = bytearray()
_b += encoder.encode(self.oid.value)[1:]
# 0x04 || x || y
# where x and y are the same length
_xy = b'\x04' + self.x.to_mpibytes()[2:] + self.y.to_mpibytes()[2:]
_b += MPI(self.bytes_to_int(_xy, 'big')).to_mpibytes()
_b += self.kdf.__bytearray__()
return _b
def __copy__(self):
pkt = super(ECDHPub, self).__copy__()
pkt.oid = self.oid
pkt.kdf = copy.copy(self.kdf)
return pkt
def parse(self, packet):
"""
Algorithm-Specific Fields for ECDH keys:
o a variable-length field containing a curve OID, formatted
as follows:
- a one-octet size of the following field; values 0 and
0xFF are reserved for future extensions
- the octets representing a curve OID, defined in
Section 11
- MPI of an EC point representing a public key
o a variable-length field containing KDF parameters,
formatted as follows:
- a one-octet size of the following fields; values 0 and
0xff are reserved for future extensions
- a one-octet value 01, reserved for future extensions
- a one-octet hash function ID used with a KDF
- a one-octet algorithm ID for the symmetric algorithm
used to wrap the symmetric key used for the message
encryption; see Section 8 for details
"""
oidlen = packet[0]
del packet[0]
_oid = bytearray(b'\x06')
_oid.append(oidlen)
_oid += bytearray(packet[:oidlen])
# try:
oid, _ = decoder.decode(bytes(_oid))
# except:
# raise PGPError("Bad OID octet stream: b'{:s}'".format(''.join(['\\x{:02X}'.format(c) for c in _oid])))
self.oid = EllipticCurveOID(oid)
del packet[:oidlen]
# flen = (self.oid.bit_length // 8)
xy = bytearray(MPI(packet).to_mpibytes()[2:])
# xy = bytearray(MPI(packet).to_bytes(flen, 'big'))
# the first byte is just \x04
del xy[:1]
# now xy needs to be separated into x, y
xylen = len(xy)
x, y = xy[:xylen // 2], xy[xylen // 2:]
self.x = MPI(self.bytes_to_int(x))
self.y = MPI(self.bytes_to_int(y))
self.kdf.parse(packet)
class String2Key(Field):
"""
3.7. String-to-Key (S2K) Specifiers
String-to-key (S2K) specifiers are used to convert passphrase strings
into symmetric-key encryption/decryption keys. They are used in two
places, currently: to encrypt the secret part of private keys in the
private keyring, and to convert passphrases to encryption keys for
symmetrically encrypted messages.
3.7.1. String-to-Key (S2K) Specifier Types
There are three types of S2K specifiers currently supported, and
some reserved values:
ID S2K Type
-- --------
0 Simple S2K
1 Salted S2K
2 Reserved value
3 Iterated and Salted S2K
100 to 110 Private/Experimental S2K
These are described in Sections 3.7.1.1 - 3.7.1.3.
3.7.1.1. Simple S2K
This directly hashes the string to produce the key data. See below
for how this hashing is done.
Octet 0: 0x00
Octet 1: hash algorithm
Simple S2K hashes the passphrase to produce the session key. The
manner in which this is done depends on the size of the session key
(which will depend on the cipher used) and the size of the hash
algorithm's output. If the hash size is greater than the session key
size, the high-order (leftmost) octets of the hash are used as the
key.
If the hash size is less than the key size, multiple instances of the
hash context are created -- enough to produce the required key data.
These instances are preloaded with 0, 1, 2, ... octets of zeros (that
is to say, the first instance has no preloading, the second gets
preloaded with 1 octet of zero, the third is preloaded with two
octets of zeros, and so forth).
As the data is hashed, it is given independently to each hash
context. Since the contexts have been initialized differently, they
will each produce different hash output. Once the passphrase is
hashed, the output data from the multiple hashes is concatenated,
first hash leftmost, to produce the key data, with any excess octets
on the right discarded.
3.7.1.2. Salted S2K
This includes a "salt" value in the S2K specifier -- some arbitrary
data -- that gets hashed along with the passphrase string, to help
prevent dictionary attacks.
Octet 0: 0x01
Octet 1: hash algorithm
Octets 2-9: 8-octet salt value
Salted S2K is exactly like Simple S2K, except that the input to the
hash function(s) consists of the 8 octets of salt from the S2K
specifier, followed by the passphrase.
3.7.1.3. Iterated and Salted S2K
This includes both a salt and an octet count. The salt is combined
with the passphrase and the resulting value is hashed repeatedly.
This further increases the amount of work an attacker must do to try
dictionary attacks.
Octet 0: 0x03
Octet 1: hash algorithm
Octets 2-9: 8-octet salt value
Octet 10: count, a one-octet, coded value
The count is coded into a one-octet number using the following
formula:
#define EXPBIAS 6
count = ((Int32)16 + (c & 15)) << ((c >> 4) + EXPBIAS);
The above formula is in C, where "Int32" is a type for a 32-bit
integer, and the variable "c" is the coded count, Octet 10.
Iterated-Salted S2K hashes the passphrase and salt data multiple
times. The total number of octets to be hashed is specified in the
encoded count in the S2K specifier. Note that the resulting count
value is an octet count of how many octets will be hashed, not an
iteration count.
Initially, one or more hash contexts are set up as with the other S2K
algorithms, depending on how many octets of key data are needed.
Then the salt, followed by the passphrase data, is repeatedly hashed
until the number of octets specified by the octet count has been
hashed. The one exception is that if the octet count is less than
the size of the salt plus passphrase, the full salt plus passphrase
will be hashed even though that is greater than the octet count.
After the hashing is done, the data is unloaded from the hash
context(s) as with the other S2K algorithms.
"""
@sdproperty
def encalg(self):
return self._encalg
@encalg.register(int)
@encalg.register(SymmetricKeyAlgorithm)
def encalg_int(self, val):
self._encalg = SymmetricKeyAlgorithm(val)
@sdproperty
def specifier(self):
return self._specifier
@specifier.register(int)
@specifier.register(String2KeyType)
def specifier_int(self, val):
self._specifier = String2KeyType(val)
@sdproperty
def halg(self):
return self._halg
@halg.register(int)
@halg.register(HashAlgorithm)
def halg_int(self, val):
self._halg = HashAlgorithm(val)
@sdproperty
def count(self):
return (16 + (self._count & 15)) << ((self._count >> 4) + 6)
@count.register(int)
def count_int(self, val):
if val < 0 or val > 255: # pragma: no cover
raise ValueError("count must be between 0 and 256")
self._count = val
def __init__(self):
super(String2Key, self).__init__()
self.usage = 0
self.encalg = 0
self.specifier = 0
self.iv = None
# specifier-specific fields
# simple, salted, iterated
self.halg = 0
# salted, iterated
self.salt = bytearray()
# iterated
self.count = 0
def __bytearray__(self):
_bytes = bytearray()
_bytes.append(self.usage)
if bool(self):
_bytes.append(self.encalg)
_bytes.append(self.specifier)
if self.specifier >= String2KeyType.Simple:
_bytes.append(self.halg)
if self.specifier >= String2KeyType.Salted:
_bytes += self.salt
if self.specifier == String2KeyType.Iterated:
_bytes.append(self._count)
if self.iv is not None:
_bytes += self.iv
return _bytes
def __len__(self):
return len(self.__bytearray__())
def __bool__(self):
return self.usage in [254, 255]
def __nonzero__(self):
return self.__bool__()
def __copy__(self):
s2k = String2Key()
s2k.usage = self.usage
s2k.encalg = self.encalg
s2k.specifier = self.specifier
s2k.iv = self.iv
s2k.halg = self.halg
s2k.salt = copy.copy(self.salt)
s2k.count = self._count
return s2k
def parse(self, packet, iv=True):
self.usage = packet[0]
del packet[0]
if bool(self):
self.encalg = packet[0]
del packet[0]
self.specifier = packet[0]
del packet[0]
if self.specifier >= String2KeyType.Simple:
# this will always be true
self.halg = packet[0]
del packet[0]
if self.specifier >= String2KeyType.Salted:
self.salt = packet[:8]
del packet[:8]
if self.specifier == String2KeyType.Iterated:
self.count = packet[0]
del packet[0]
if iv:
self.iv = packet[:(self.encalg.block_size // 8)]
del packet[:(self.encalg.block_size // 8)]
def derive_key(self, passphrase):
##TODO: raise an exception if self.usage is not 254 or 255
keylen = self.encalg.key_size
hashlen = self.halg.digest_size * 8
ctx = int(math.ceil((keylen / hashlen)))
# Simple S2K - always done
hsalt = b''
hpass = passphrase.encode('latin-1')
# salted, iterated S2K
if self.specifier >= String2KeyType.Salted:
hsalt = bytes(self.salt)
count = len(hsalt + hpass)
if self.specifier == String2KeyType.Iterated and self.count > len(hsalt + hpass):
count = self.count
hcount = (count // len(hsalt + hpass))
hleft = count - (hcount * len(hsalt + hpass))
hashdata = ((hsalt + hpass) * hcount) + (hsalt + hpass)[:hleft]
h = []
for i in range(0, ctx):
_h = self.halg.hasher
_h.update(b'\x00' * i)
_h.update(hashdata)
h.append(_h)
# GC some stuff
del hsalt
del hpass
del hashdata
# and return the key!
return b''.join(hc.digest() for hc in h)[:(keylen // 8)]
class ECKDF(Field):
"""
o a variable-length field containing KDF parameters,
formatted as follows:
- a one-octet size of the following fields; values 0 and
0xff are reserved for future extensions
- a one-octet value 01, reserved for future extensions
- a one-octet hash function ID used with a KDF
- a one-octet algorithm ID for the symmetric algorithm
used to wrap the symmetric key used for the message
encryption; see Section 8 for details
"""
@sdproperty
def halg(self):
return self._halg
@halg.register(int)
@halg.register(HashAlgorithm)
def halg_int(self, val):
self._halg = HashAlgorithm(val)
@sdproperty
def encalg(self):
return self._encalg
@encalg.register(int)
@encalg.register(SymmetricKeyAlgorithm)
def encalg_int(self, val):
self._encalg = SymmetricKeyAlgorithm(val)
def __init__(self):
super(ECKDF, self).__init__()
self.halg = 0
self.encalg = 0
def __bytearray__(self):
_bytes = bytearray()
_bytes.append(len(self) - 1)
_bytes.append(0x01)
_bytes.append(self.halg)
_bytes.append(self.encalg)
return _bytes
def __len__(self):
return 4
def parse(self, packet):
# packet[0] should always be 3
# packet[1] should always be 1
# TODO: this assert is likely not necessary, but we should raise some kind of exception
# if parsing fails due to these fields being incorrect
assert packet[:2] == b'\x03\x01'
del packet[:2]
self.halg = packet[0]
del packet[0]
self.encalg = packet[0]
del packet[0]
def derive_key(self, s, curve, pkalg, fingerprint):
# wrapper around the Concatenation KDF method provided by cryptography
# assemble the additional data as defined in RFC 6637:
# Param = curve_OID_len || curve_OID || public_key_alg_ID || 03 || 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap || "Anonymous
data = bytearray()
data += encoder.encode(curve.value)[1:]
data.append(pkalg)
data += b'\x03\x01'
data.append(self.halg)
data.append(self.encalg)
data += b'Anonymous Sender '
data += binascii.unhexlify(fingerprint.replace(' ', ''))
ckdf = ConcatKDFHash(algorithm=getattr(hashes, self.halg.name)(), length=self.encalg.key_size // 8, otherinfo=bytes(data), backend=default_backend())
return ckdf.derive(s)
class PrivKey(PubKey):
__privfields__ = ()
@property
def __mpis__(self):
for i in super(PrivKey, self).__mpis__:
yield i
for i in self.__privfields__:
yield i
def __init__(self):
super(PrivKey, self).__init__()
self.s2k = String2Key()
self.encbytes = bytearray()
self.chksum = bytearray()
for field in self.__privfields__:
setattr(self, field, MPI(0))
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(PrivKey, self).__bytearray__()
_bytes += self.s2k.__bytearray__()
if self.s2k:
_bytes += self.encbytes
else:
for field in self.__privfields__:
_bytes += getattr(self, field).to_mpibytes()
if self.s2k.usage == 0:
_bytes += self.chksum
return _bytes
def __len__(self):
l = super(PrivKey, self).__len__() + len(self.s2k) + len(self.chksum)
if self.s2k:
l += len(self.encbytes)
else:
l += sum(len(getattr(self, i)) for i in self.__privfields__)
return l
def __copy__(self):
pk = super(PrivKey, self).__copy__()
pk.s2k = copy.copy(self.s2k)
pk.encbytes = copy.copy(self.encbytes)
pk.chksum = copy.copy(self.chksum)
return pk
@abc.abstractmethod
def __privkey__(self):
"""return the requisite *PrivateKey class from the cryptography library"""
@abc.abstractmethod
def _generate(self, key_size):
"""Generate a new PrivKey"""
def _compute_chksum(self):
"Calculate the key checksum"
def publen(self):
return super(PrivKey, self).__len__()
def encrypt_keyblob(self, passphrase, enc_alg, hash_alg):
# PGPy will only ever use iterated and salted S2k mode
self.s2k.usage = 254
self.s2k.encalg = enc_alg
self.s2k.specifier = String2KeyType.Iterated
self.s2k.iv = enc_alg.gen_iv()
self.s2k.halg = hash_alg
self.s2k.salt = bytearray(os.urandom(8))
self.s2k.count = hash_alg.tuned_count
# now that String-to-Key is ready to go, derive sessionkey from passphrase
# and then unreference passphrase
sessionkey = self.s2k.derive_key(passphrase)
del passphrase
pt = bytearray()
for pf in self.__privfields__:
pt += getattr(self, pf).to_mpibytes()
# append a SHA-1 hash of the plaintext so far to the plaintext
pt += hashlib.new('sha1', pt).digest()
# encrypt
self.encbytes = bytearray(_encrypt(bytes(pt), bytes(sessionkey), enc_alg, bytes(self.s2k.iv)))
# delete pt and clear self
del pt
self.clear()
@abc.abstractmethod
def decrypt_keyblob(self, passphrase):
if not self.s2k: # pragma: no cover
# not encrypted
return
# Encryption/decryption of the secret data is done in CFB mode using
# the key created from the passphrase and the Initial Vector from the
# packet. A different mode is used with V3 keys (which are only RSA)
# than with other key formats. (...)
#
# With V4 keys, a simpler method is used. All secret MPI values are
# encrypted in CFB mode, including the MPI bitcount prefix.
# derive the session key from our passphrase, and then unreference passphrase
sessionkey = self.s2k.derive_key(passphrase)
del passphrase
# attempt to decrypt this key
pt = _decrypt(bytes(self.encbytes), bytes(sessionkey), self.s2k.encalg, bytes(self.s2k.iv))
# check the hash to see if we decrypted successfully or not
if self.s2k.usage == 254 and not pt[-20:] == hashlib.new('sha1', pt[:-20]).digest():
# if the usage byte is 254, key material is followed by a 20-octet sha-1 hash of the rest
# of the key material block
raise PGPDecryptionError("Passphrase was incorrect!")
if self.s2k.usage == 255 and not self.bytes_to_int(pt[-2:]) == (sum(bytearray(pt[:-2])) % 65536): # pragma: no cover
# if the usage byte is 255, key material is followed by a 2-octet checksum of the rest
# of the key material block
raise PGPDecryptionError("Passphrase was incorrect!")
return bytearray(pt)
def sign(self, sigdata, hash_alg):
return NotImplemented # pragma: no cover
def clear(self):
"""delete and re-initialize all private components to zero"""
for field in self.__privfields__:
delattr(self, field)
setattr(self, field, MPI(0))
class OpaquePrivKey(PrivKey, OpaquePubKey): # pragma: no cover
def __privkey__(self):
return NotImplemented
def _generate(self, key_size):
# return NotImplemented
raise NotImplementedError()
def decrypt_keyblob(self, passphrase):
return NotImplemented
class RSAPriv(PrivKey, RSAPub):
__privfields__ = ('d', 'p', 'q', 'u')
def __privkey__(self):
return rsa.RSAPrivateNumbers(self.p, self.q, self.d,
rsa.rsa_crt_dmp1(self.d, self.p),
rsa.rsa_crt_dmq1(self.d, self.q),
rsa.rsa_crt_iqmp(self.p, self.q),
rsa.RSAPublicNumbers(self.e, self.n)).private_key(default_backend())
def _compute_chksum(self):
chs = sum(sum(bytearray(c.to_mpibytes())) for c in (self.d, self.p, self.q, self.u)) % 65536
self.chksum = bytearray(self.int_to_bytes(chs, 2))
def _generate(self, key_size):
if any(c != 0 for c in self): # pragma: no cover
raise PGPError("key is already populated")
# generate some big numbers!
pk = rsa.generate_private_key(65537, key_size, default_backend())
pkn = pk.private_numbers()
self.n = MPI(pkn.public_numbers.n)
self.e = MPI(pkn.public_numbers.e)
self.d = MPI(pkn.d)
self.p = MPI(pkn.p)
self.q = MPI(pkn.q)
# from the RFC:
# "- MPI of u, the multiplicative inverse of p, mod q."
# or, simply, p^-1 mod p
# rsa.rsa_crt_iqmp(p, q) normally computes q^-1 mod p,
# so if we swap the values around we get the answer we want
self.u = MPI(rsa.rsa_crt_iqmp(pkn.q, pkn.p))
del pkn
del pk
self._compute_chksum()
def parse(self, packet):
super(RSAPriv, self).parse(packet)
self.s2k.parse(packet)
if not self.s2k:
self.d = MPI(packet)
self.p = MPI(packet)
self.q = MPI(packet)
self.u = MPI(packet)
if self.s2k.usage == 0:
self.chksum = packet[:2]
del packet[:2]
else:
##TODO: this needs to be bounded to the length of the encrypted key material
self.encbytes = packet
def decrypt_keyblob(self, passphrase):
kb = super(RSAPriv, self).decrypt_keyblob(passphrase)
del passphrase
self.d = MPI(kb)
self.p = MPI(kb)
self.q = MPI(kb)
self.u = MPI(kb)
if self.s2k.usage in [254, 255]:
self.chksum = kb
del kb
def sign(self, sigdata, hash_alg):
signer = self.__privkey__().signer(padding.PKCS1v15(), hash_alg)
signer.update(sigdata)
return signer.finalize()
class DSAPriv(PrivKey, DSAPub):
__privfields__ = ('x',)
def __privkey__(self):
params = dsa.DSAParameterNumbers(self.p, self.q, self.g)
pn = dsa.DSAPublicNumbers(self.y, params)
return dsa.DSAPrivateNumbers(self.x, pn).private_key(default_backend())
def _compute_chksum(self):
chs = sum(bytearray(self.x.to_mpibytes())) % 65536
self.chksum = bytearray(self.int_to_bytes(chs, 2))
def _generate(self, key_size):
if any(c != 0 for c in self): # pragma: no cover
raise PGPError("key is already populated")
# generate some big numbers!
pk = dsa.generate_private_key(key_size, default_backend())
pkn = pk.private_numbers()
self.p = MPI(pkn.public_numbers.parameter_numbers.p)
self.q = MPI(pkn.public_numbers.parameter_numbers.q)
self.g = MPI(pkn.public_numbers.parameter_numbers.g)
self.y = MPI(pkn.public_numbers.y)
self.x = MPI(pkn.x)
del pkn
del pk
self._compute_chksum()
def parse(self, packet):
super(DSAPriv, self).parse(packet)
self.s2k.parse(packet)
if not self.s2k:
self.x = MPI(packet)
else:
self.encbytes = packet
if self.s2k.usage in [0, 255]:
self.chksum = packet[:2]
del packet[:2]
def decrypt_keyblob(self, passphrase):
kb = super(DSAPriv, self).decrypt_keyblob(passphrase)
del passphrase
self.x = MPI(kb)
if self.s2k.usage in [254, 255]:
self.chksum = kb
del kb
def sign(self, sigdata, hash_alg):
signer = self.__privkey__().signer(hash_alg)
signer.update(sigdata)
return signer.finalize()
class ElGPriv(PrivKey, ElGPub):
__privfields__ = ('x', )
def __privkey__(self):
raise NotImplementedError()
def _compute_chksum(self):
chs = sum(bytearray(self.x.to_mpibytes())) % 65536
self.chksum = bytearray(self.int_to_bytes(chs, 2))
def _generate(self, key_size):
raise NotImplementedError(PubKeyAlgorithm.ElGamal)
def parse(self, packet):
super(ElGPriv, self).parse(packet)
self.s2k.parse(packet)
if not self.s2k:
self.x = MPI(packet)
else:
self.encbytes = packet
if self.s2k.usage in [0, 255]:
self.chksum = packet[:2]
del packet[:2]
def decrypt_keyblob(self, passphrase):
kb = super(ElGPriv, self).decrypt_keyblob(passphrase)
del passphrase
self.x = MPI(kb)
if self.s2k.usage in [254, 255]:
self.chksum = kb
del kb
class ECDSAPriv(PrivKey, ECDSAPub):
__privfields__ = ('s', )
def __privkey__(self):
ecp = ec.EllipticCurvePublicNumbers(self.x, self.y, self.oid.curve())
return ec.EllipticCurvePrivateNumbers(self.s, ecp).private_key(default_backend())
def _compute_chksum(self):
chs = sum(bytearray(self.s.to_mpibytes())) % 65536
self.chksum = bytearray(self.int_to_bytes(chs, 2))
def _generate(self, oid):
if any(c != 0 for c in self): # pragma: no cover
raise PGPError("Key is already populated!")
self.oid = EllipticCurveOID(oid)
if not self.oid.can_gen:
raise ValueError("Curve not currently supported: {}".format(oid.name))
pk = ec.generate_private_key(self.oid.curve(), default_backend())
pubn = pk.public_key().public_numbers()
self.x = MPI(pubn.x)
self.y = MPI(pubn.y)
self.s = MPI(pk.private_numbers().private_value)
self._compute_chksum()
def parse(self, packet):
super(ECDSAPriv, self).parse(packet)
self.s2k.parse(packet)
if not self.s2k:
self.s = MPI(packet)
if self.s2k.usage == 0:
self.chksum = packet[:2]
del packet[:2]
else:
##TODO: this needs to be bounded to the length of the encrypted key material
self.encbytes = packet
def decrypt_keyblob(self, passphrase):
kb = super(ECDSAPriv, self).decrypt_keyblob(passphrase)
del passphrase
self.s = MPI(kb)
def sign(self, sigdata, hash_alg):
signer = self.__privkey__().signer(ec.ECDSA(hash_alg))
signer.update(sigdata)
return signer.finalize()
class ECDHPriv(ECDSAPriv, ECDHPub):
def __bytearray__(self):
_b = ECDHPub.__bytearray__(self)
_b += self.s2k.__bytearray__()
if not self.s2k:
_b += self.s.to_mpibytes()
if self.s2k.usage == 0:
_b += self.chksum
else:
_b += self.encbytes
return _b
def __len__(self):
# because of the inheritance used for this, ECDSAPub.__len__ is called instead of ECDHPub.__len__
# the only real difference is self.kdf, so we can just add that
return super(ECDHPriv, self).__len__() + len(self.kdf)
def _generate(self, oid):
ECDSAPriv._generate(self, oid)
self.kdf.halg = self.oid.kdf_halg
self.kdf.encalg = self.oid.kek_alg
def publen(self):
return ECDHPub.__len__(self)
def parse(self, packet):
ECDHPub.parse(self, packet)
self.s2k.parse(packet)
if not self.s2k:
self.s = MPI(packet)
if self.s2k.usage == 0:
self.chksum = packet[:2]
del packet[:2]
else:
##TODO: this needs to be bounded to the length of the encrypted key material
self.encbytes = packet
class CipherText(MPIs):
def __init__(self):
super(CipherText, self).__init__()
for i in self.__mpis__:
setattr(self, i, MPI(0))
@classmethod
@abc.abstractmethod
def encrypt(cls, encfn, *args):
"""create and populate a concrete CipherText class instance"""
@abc.abstractmethod
def decrypt(self, decfn, *args):
"""decrypt the ciphertext contained in this CipherText instance"""
def __bytearray__(self):
_bytes = bytearray()
for i in self:
_bytes += i.to_mpibytes()
return _bytes
class RSACipherText(CipherText):
__mpis__ = ('me_mod_n', )
@classmethod
def encrypt(cls, encfn, *args):
ct = cls()
ct.me_mod_n = MPI(cls.bytes_to_int(encfn(*args)))
return ct
def decrypt(self, decfn, *args):
return decfn(*args)
def parse(self, packet):
self.me_mod_n = MPI(packet)
class ElGCipherText(CipherText):
__mpis__ = ('gk_mod_p', 'myk_mod_p')
@classmethod
def encrypt(cls, encfn, *args):
raise NotImplementedError()
def decrypt(self, decfn, *args):
raise NotImplementedError()
def parse(self, packet):
self.gk_mod_p = MPI(packet)
self.myk_mod_p = MPI(packet)
class ECDHCipherText(CipherText):
__mpis__ = ('vX', 'vY')
@classmethod
def encrypt(cls, pk, *args):
"""
For convenience, the synopsis of the encoding method is given below;
however, this section, [NIST-SP800-56A], and [RFC3394] are the
normative sources of the definition.
Obtain the authenticated recipient public key R
Generate an ephemeral key pair {v, V=vG}
Compute the shared point S = vR;
m = symm_alg_ID || session key || checksum || pkcs5_padding;
curve_OID_len = (byte)len(curve_OID);
Param = curve_OID_len || curve_OID || public_key_alg_ID || 03
|| 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap || "Anonymous
Sender " || recipient_fingerprint;
Z_len = the key size for the KEK_alg_ID used with AESKeyWrap
Compute Z = KDF( S, Z_len, Param );
Compute C = AESKeyWrap( Z, m ) as per [RFC3394]
VB = convert point V to the octet string
Output (MPI(VB) || len(C) || C).
The decryption is the inverse of the method given. Note that the
recipient obtains the shared secret by calculating
"""
# *args should be:
# - m
#
_m, = args
# m may need to be PKCS5-padded
padder = PKCS7(64).padder()
m = padder.update(_m) + padder.finalize()
km = pk.keymaterial
ct = cls()
# generate ephemeral key pair, then store it in ct
v = ec.generate_private_key(km.oid.curve(), default_backend())
ct.vX = MPI(v.public_key().public_numbers().x)
ct.vY = MPI(v.public_key().public_numbers().y)
# compute the shared point S
s = v.exchange(ec.ECDH(), km.__pubkey__())
# derive the wrapping key
z = km.kdf.derive_key(s, km.oid, PubKeyAlgorithm.ECDH, pk.fingerprint)
# compute C
ct.c = aes_key_wrap(z, m, default_backend())
return ct
def decrypt(self, pk, *args):
km = pk.keymaterial
# assemble the public component of ephemeral key v
v = ec.EllipticCurvePublicNumbers(self.vX, self.vY, km.oid.curve()).public_key(default_backend())
# compute s using the inverse of how it was derived during encryption
s = km.__privkey__().exchange(ec.ECDH(), v)
# derive the wrapping key
z = km.kdf.derive_key(s, km.oid, PubKeyAlgorithm.ECDH, pk.fingerprint)
# unwrap and unpad m
_m = aes_key_unwrap(z, self.c, default_backend())
padder = PKCS7(64).unpadder()
return padder.update(_m) + padder.finalize()
def __init__(self):
super(ECDHCipherText, self).__init__()
self.c = bytearray(0)
def __bytearray__(self):
_bytes = bytearray()
_xy = b'\x04' + self.vX.to_mpibytes()[2:] + self.vY.to_mpibytes()[2:]
_bytes += MPI(self.bytes_to_int(_xy, 'big')).to_mpibytes()
_bytes.append(len(self.c))
_bytes += self.c
return _bytes
def parse(self, packet):
# self.v = MPI(packet)
xy = bytearray(MPI(packet).to_mpibytes()[2:])
del xy[:1]
xylen = len(xy)
x, y = xy[:xylen // 2], xy[xylen // 2:]
self.vX = MPI(self.bytes_to_int(x))
self.vY = MPI(self.bytes_to_int(y))
clen = packet[0]
del packet[0]
self.c += packet[:clen]
del packet[:clen]
python-pgpy-0.4.3/pgpy/packet/packets.py 0000664 0000000 0000000 00000152667 13145172200 0020262 0 ustar 00root root 0000000 0000000 """ packet.py
"""
import abc
import binascii
import calendar
import copy
import hashlib
import os
import re
from datetime import datetime
import six
from cryptography.hazmat.primitives import constant_time
from cryptography.hazmat.primitives.asymmetric import padding
from .fields import DSAPriv, DSAPub, DSASignature
from .fields import ECDSAPub, ECDSAPriv, ECDSASignature
from .fields import ECDHPub, ECDHPriv, ECDHCipherText
from .fields import ElGCipherText, ElGPriv, ElGPub
from .fields import OpaquePubKey
from .fields import OpaquePrivKey
from .fields import RSACipherText, RSAPriv, RSAPub, RSASignature
from .fields import String2Key
from .fields import SubPackets
from .fields import UserAttributeSubPackets
from .types import Packet
from .types import Primary
from .types import Private
from .types import Public
from .types import Sub
from .types import VersionedPacket
from ..constants import CompressionAlgorithm
from ..constants import HashAlgorithm
from ..constants import PubKeyAlgorithm
from ..constants import SignatureType
from ..constants import SymmetricKeyAlgorithm
from ..constants import TrustFlags
from ..constants import TrustLevel
from ..decorators import sdproperty
from ..errors import PGPDecryptionError
from ..symenc import _decrypt
from ..symenc import _encrypt
from ..types import Fingerprint
__all__ = ['PKESessionKey',
'PKESessionKeyV3',
'Signature',
'SignatureV4',
'SKESessionKey',
'SKESessionKeyV4',
'OnePassSignature',
'OnePassSignatureV3',
'PrivKey',
'PubKey',
'PubKeyV4',
'PrivKeyV4',
'PrivSubKey',
'PrivSubKeyV4',
'CompressedData',
'SKEData',
'Marker',
'LiteralData',
'Trust',
'UserID',
'PubSubKey',
'PubSubKeyV4',
'UserAttribute',
'IntegrityProtectedSKEData',
'IntegrityProtectedSKEDataV1',
'MDC']
class PKESessionKey(VersionedPacket):
__typeid__ = 0x01
__ver__ = 0
@abc.abstractmethod
def decrypt_sk(self, pk):
raise NotImplementedError()
@abc.abstractmethod
def encrypt_sk(self, pk, symalg, symkey):
raise NotImplementedError()
class PKESessionKeyV3(PKESessionKey):
"""
5.1. Public-Key Encrypted Session Key Packets (Tag 1)
A Public-Key Encrypted Session Key packet holds the session key used
to encrypt a message. Zero or more Public-Key Encrypted Session Key
packets and/or Symmetric-Key Encrypted Session Key packets may
precede a Symmetrically Encrypted Data Packet, which holds an
encrypted message. The message is encrypted with the session key,
and the session key is itself encrypted and stored in the Encrypted
Session Key packet(s). The Symmetrically Encrypted Data Packet is
preceded by one Public-Key Encrypted Session Key packet for each
OpenPGP key to which the message is encrypted. The recipient of the
message finds a session key that is encrypted to their public key,
decrypts the session key, and then uses the session key to decrypt
the message.
The body of this packet consists of:
- A one-octet number giving the version number of the packet type.
The currently defined value for packet version is 3.
- An eight-octet number that gives the Key ID of the public key to
which the session key is encrypted. If the session key is
encrypted to a subkey, then the Key ID of this subkey is used
here instead of the Key ID of the primary key.
- A one-octet number giving the public-key algorithm used.
- A string of octets that is the encrypted session key. This
string takes up the remainder of the packet, and its contents are
dependent on the public-key algorithm used.
Algorithm Specific Fields for RSA encryption
- multiprecision integer (MPI) of RSA encrypted value m**e mod n.
Algorithm Specific Fields for Elgamal encryption:
- MPI of Elgamal (Diffie-Hellman) value g**k mod p.
- MPI of Elgamal (Diffie-Hellman) value m * y**k mod p.
The value "m" in the above formulas is derived from the session key
as follows. First, the session key is prefixed with a one-octet
algorithm identifier that specifies the symmetric encryption
algorithm used to encrypt the following Symmetrically Encrypted Data
Packet. Then a two-octet checksum is appended, which is equal to the
sum of the preceding session key octets, not including the algorithm
identifier, modulo 65536. This value is then encoded as described in
PKCS#1 block encoding EME-PKCS1-v1_5 in Section 7.2.1 of [RFC3447] to
form the "m" value used in the formulas above. See Section 13.1 of
this document for notes on OpenPGP's use of PKCS#1.
Note that when an implementation forms several PKESKs with one
session key, forming a message that can be decrypted by several keys,
the implementation MUST make a new PKCS#1 encoding for each key.
An implementation MAY accept or use a Key ID of zero as a "wild card"
or "speculative" Key ID. In this case, the receiving implementation
would try all available private keys, checking for a valid decrypted
session key. This format helps reduce traffic analysis of messages.
"""
__ver__ = 3
@sdproperty
def encrypter(self):
return self._encrypter
@encrypter.register(bytearray)
def encrypter_bin(self, val):
self._encrypter = binascii.hexlify(val).upper().decode('latin-1')
@sdproperty
def pkalg(self):
return self._pkalg
@pkalg.register(int)
@pkalg.register(PubKeyAlgorithm)
def pkalg_int(self, val):
self._pkalg = PubKeyAlgorithm(val)
_c = {PubKeyAlgorithm.RSAEncryptOrSign: RSACipherText,
PubKeyAlgorithm.RSAEncrypt: RSACipherText,
PubKeyAlgorithm.ElGamal: ElGCipherText,
PubKeyAlgorithm.FormerlyElGamalEncryptOrSign: ElGCipherText,
PubKeyAlgorithm.ECDH: ECDHCipherText}
ct = _c.get(self._pkalg, None)
self.ct = ct() if ct is not None else ct
def __init__(self):
super(PKESessionKeyV3, self).__init__()
self.encrypter = bytearray(8)
self.pkalg = 0
self.ct = None
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(PKESessionKeyV3, self).__bytearray__()
_bytes += binascii.unhexlify(self.encrypter.encode())
_bytes += bytearray([self.pkalg])
_bytes += self.ct.__bytearray__() if self.ct is not None else b'\x00' * (self.header.length - 10)
return _bytes
def __copy__(self):
sk = self.__class__()
sk.header = copy.copy(self.header)
sk._encrypter = self._encrypter
sk.pkalg = self.pkalg
if self.ct is not None:
sk.ct = copy.copy(self.ct)
return sk
def decrypt_sk(self, pk):
if self.pkalg == PubKeyAlgorithm.RSAEncryptOrSign:
# pad up ct with null bytes if necessary
ct = self.ct.me_mod_n.to_mpibytes()[2:]
ct = b'\x00' * ((pk.keymaterial.__privkey__().key_size // 8) - len(ct)) + ct
decrypter = pk.keymaterial.__privkey__().decrypt
decargs = (ct, padding.PKCS1v15(),)
elif self.pkalg == PubKeyAlgorithm.ECDH:
decrypter = pk
decargs = ()
else:
raise NotImplementedError(self.pkalg)
m = bytearray(self.ct.decrypt(decrypter, *decargs))
"""
The value "m" in the above formulas is derived from the session key
as follows. First, the session key is prefixed with a one-octet
algorithm identifier that specifies the symmetric encryption
algorithm used to encrypt the following Symmetrically Encrypted Data
Packet. Then a two-octet checksum is appended, which is equal to the
sum of the preceding session key octets, not including the algorithm
identifier, modulo 65536. This value is then encoded as described in
PKCS#1 block encoding EME-PKCS1-v1_5 in Section 7.2.1 of [RFC3447] to
form the "m" value used in the formulas above. See Section 13.1 of
this document for notes on OpenPGP's use of PKCS#1.
"""
symalg = SymmetricKeyAlgorithm(m[0])
del m[0]
symkey = m[:symalg.key_size // 8]
del m[:symalg.key_size // 8]
checksum = self.bytes_to_int(m[:2])
del m[:2]
if not sum(symkey) % 65536 == checksum: # pragma: no cover
raise PGPDecryptionError("{:s} decryption failed".format(self.pkalg.name))
return (symalg, symkey)
def encrypt_sk(self, pk, symalg, symkey):
m = bytearray(self.int_to_bytes(symalg) + symkey)
m += self.int_to_bytes(sum(bytearray(symkey)) % 65536, 2)
if self.pkalg == PubKeyAlgorithm.RSAEncryptOrSign:
encrypter = pk.keymaterial.__pubkey__().encrypt
encargs = (bytes(m), padding.PKCS1v15(),)
elif self.pkalg == PubKeyAlgorithm.ECDH:
encrypter = pk
encargs = (bytes(m),)
else:
raise NotImplementedError(self.pkalg)
self.ct = self.ct.encrypt(encrypter, *encargs)
self.update_hlen()
def parse(self, packet):
super(PKESessionKeyV3, self).parse(packet)
self.encrypter = packet[:8]
del packet[:8]
self.pkalg = packet[0]
del packet[0]
if self.ct is not None:
self.ct.parse(packet)
else: # pragma: no cover
del packet[:(self.header.length - 18)]
class Signature(VersionedPacket):
__typeid__ = 0x02
__ver__ = 0
class SignatureV4(Signature):
"""
5.2.3. Version 4 Signature Packet Format
The body of a version 4 Signature packet contains:
- One-octet version number (4).
- One-octet signature type.
- One-octet public-key algorithm.
- One-octet hash algorithm.
- Two-octet scalar octet count for following hashed subpacket data.
Note that this is the length in octets of all of the hashed
subpackets; a pointer incremented by this number will skip over
the hashed subpackets.
- Hashed subpacket data set (zero or more subpackets).
- Two-octet scalar octet count for the following unhashed subpacket
data. Note that this is the length in octets of all of the
unhashed subpackets; a pointer incremented by this number will
skip over the unhashed subpackets.
- Unhashed subpacket data set (zero or more subpackets).
- Two-octet field holding the left 16 bits of the signed hash
value.
- One or more multiprecision integers comprising the signature.
This portion is algorithm specific, as described above.
The concatenation of the data being signed and the signature data
from the version number through the hashed subpacket data (inclusive)
is hashed. The resulting hash value is what is signed. The left 16
bits of the hash are included in the Signature packet to provide a
quick test to reject some invalid signatures.
There are two fields consisting of Signature subpackets. The first
field is hashed with the rest of the signature data, while the second
is unhashed. The second set of subpackets is not cryptographically
protected by the signature and should include only advisory
information.
The algorithms for converting the hash function result to a signature
are described in a section below.
"""
__ver__ = 4
@sdproperty
def sigtype(self):
return self._sigtype
@sigtype.register(int)
@sigtype.register(SignatureType)
def sigtype_int(self, val):
self._sigtype = SignatureType(val)
@sdproperty
def pubalg(self):
return self._pubalg
@pubalg.register(int)
@pubalg.register(PubKeyAlgorithm)
def pubalg_int(self, val):
self._pubalg = PubKeyAlgorithm(val)
sigs = {PubKeyAlgorithm.RSAEncryptOrSign: RSASignature,
PubKeyAlgorithm.RSAEncrypt: RSASignature,
PubKeyAlgorithm.RSASign: RSASignature,
PubKeyAlgorithm.DSA: DSASignature,
PubKeyAlgorithm.ECDSA: ECDSASignature, }
if self.pubalg in sigs:
self.signature = sigs[self.pubalg]()
@sdproperty
def halg(self):
return self._halg
@halg.register(int)
@halg.register(HashAlgorithm)
def halg_int(self, val):
try:
self._halg = HashAlgorithm(val)
except ValueError: # pragma: no cover
self._halg = val
@property
def signature(self):
return self._signature
@signature.setter
def signature(self, val):
self._signature = val
@property
def signer(self):
return self.subpackets['Issuer'][-1].issuer
def __init__(self):
super(Signature, self).__init__()
self._sigtype = None
self._pubalg = None
self._halg = None
self.subpackets = SubPackets()
self.hash2 = bytearray(2)
self.signature = None
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(Signature, self).__bytearray__()
_bytes += self.int_to_bytes(self.sigtype)
_bytes += self.int_to_bytes(self.pubalg)
_bytes += self.int_to_bytes(self.halg)
_bytes += self.subpackets.__bytearray__()
_bytes += self.hash2
_bytes += self.signature.__bytearray__()
return _bytes
def __copy__(self):
spkt = SignatureV4()
spkt.header = copy.copy(self.header)
spkt._sigtype = self._sigtype
spkt._pubalg = self._pubalg
spkt._halg = self._halg
spkt.subpackets = copy.copy(self.subpackets)
spkt.hash2 = copy.copy(self.hash2)
spkt.signature = copy.copy(self.signature)
return spkt
def update_hlen(self):
self.subpackets.update_hlen()
super(SignatureV4, self).update_hlen()
def parse(self, packet):
super(Signature, self).parse(packet)
self.sigtype = packet[0]
del packet[0]
self.pubalg = packet[0]
del packet[0]
self.halg = packet[0]
del packet[0]
self.subpackets.parse(packet)
self.hash2 = packet[:2]
del packet[:2]
self.signature.parse(packet)
class SKESessionKey(VersionedPacket):
__typeid__ = 0x03
__ver__ = 0
@abc.abstractmethod
def decrypt_sk(self, passphrase):
raise NotImplementedError()
@abc.abstractmethod
def encrypt_sk(self, passphrase, sk):
raise NotImplementedError()
class SKESessionKeyV4(SKESessionKey):
"""
5.3. Symmetric-Key Encrypted Session Key Packets (Tag 3)
The Symmetric-Key Encrypted Session Key packet holds the
symmetric-key encryption of a session key used to encrypt a message.
Zero or more Public-Key Encrypted Session Key packets and/or
Symmetric-Key Encrypted Session Key packets may precede a
Symmetrically Encrypted Data packet that holds an encrypted message.
The message is encrypted with a session key, and the session key is
itself encrypted and stored in the Encrypted Session Key packet or
the Symmetric-Key Encrypted Session Key packet.
If the Symmetrically Encrypted Data packet is preceded by one or
more Symmetric-Key Encrypted Session Key packets, each specifies a
passphrase that may be used to decrypt the message. This allows a
message to be encrypted to a number of public keys, and also to one
or more passphrases. This packet type is new and is not generated
by PGP 2.x or PGP 5.0.
The body of this packet consists of:
- A one-octet version number. The only currently defined version
is 4.
- A one-octet number describing the symmetric algorithm used.
- A string-to-key (S2K) specifier, length as defined above.
- Optionally, the encrypted session key itself, which is decrypted
with the string-to-key object.
If the encrypted session key is not present (which can be detected
on the basis of packet length and S2K specifier size), then the S2K
algorithm applied to the passphrase produces the session key for
decrypting the file, using the symmetric cipher algorithm from the
Symmetric-Key Encrypted Session Key packet.
If the encrypted session key is present, the result of applying the
S2K algorithm to the passphrase is used to decrypt just that
encrypted session key field, using CFB mode with an IV of all zeros.
The decryption result consists of a one-octet algorithm identifier
that specifies the symmetric-key encryption algorithm used to
encrypt the following Symmetrically Encrypted Data packet, followed
by the session key octets themselves.
Note: because an all-zero IV is used for this decryption, the S2K
specifier MUST use a salt value, either a Salted S2K or an
Iterated-Salted S2K. The salt value will ensure that the decryption
key is not repeated even if the passphrase is reused.
"""
__ver__ = 4
@property
def symalg(self):
return self.s2k.encalg
def __init__(self):
super(SKESessionKeyV4, self).__init__()
self.s2k = String2Key()
self.ct = bytearray()
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(SKESessionKeyV4, self).__bytearray__()
_bytes += self.s2k.__bytearray__()[1:]
_bytes += self.ct
return _bytes
def __copy__(self):
sk = self.__class__()
sk.header = copy.copy(self.header)
sk.s2k = copy.copy(self.s2k)
sk.ct = self.ct[:]
return sk
def parse(self, packet):
super(SKESessionKeyV4, self).parse(packet)
# prepend a valid usage identifier so this parses correctly
packet.insert(0, 255)
self.s2k.parse(packet, iv=False)
ctend = self.header.length - len(self.s2k)
self.ct = packet[:ctend]
del packet[:ctend]
def decrypt_sk(self, passphrase):
# derive the first session key from our passphrase
sk = self.s2k.derive_key(passphrase)
del passphrase
# if there is no ciphertext, then the first session key is the session key being used
if len(self.ct) == 0:
return self.symalg, sk
# otherwise, we now need to decrypt the encrypted session key
m = bytearray(_decrypt(bytes(self.ct), sk, self.symalg))
del sk
symalg = SymmetricKeyAlgorithm(m[0])
del m[0]
return symalg, bytes(m)
def encrypt_sk(self, passphrase, sk):
# generate the salt and derive the key to encrypt sk with from it
self.s2k.salt = bytearray(os.urandom(8))
esk = self.s2k.derive_key(passphrase)
del passphrase
self.ct = _encrypt(self.int_to_bytes(self.symalg) + sk, esk, self.symalg)
# update header length and return sk
self.update_hlen()
class OnePassSignature(VersionedPacket):
__typeid__ = 0x04
__ver__ = 0
class OnePassSignatureV3(OnePassSignature):
"""
5.4. One-Pass Signature Packets (Tag 4)
The One-Pass Signature packet precedes the signed data and contains
enough information to allow the receiver to begin calculating any
hashes needed to verify the signature. It allows the Signature
packet to be placed at the end of the message, so that the signer
can compute the entire signed message in one pass.
A One-Pass Signature does not interoperate with PGP 2.6.x or
earlier.
The body of this packet consists of:
- A one-octet version number. The current version is 3.
- A one-octet signature type. Signature types are described in
Section 5.2.1.
- A one-octet number describing the hash algorithm used.
- A one-octet number describing the public-key algorithm used.
- An eight-octet number holding the Key ID of the signing key.
- A one-octet number holding a flag showing whether the signature
is nested. A zero value indicates that the next packet is
another One-Pass Signature packet that describes another
signature to be applied to the same message data.
Note that if a message contains more than one one-pass signature,
then the Signature packets bracket the message; that is, the first
Signature packet after the message corresponds to the last one-pass
packet and the final Signature packet corresponds to the first
one-pass packet.
"""
__ver__ = 3
@sdproperty
def sigtype(self):
return self._sigtype
@sigtype.register(int)
@sigtype.register(SignatureType)
def sigtype_int(self, val):
self._sigtype = SignatureType(val)
@sdproperty
def pubalg(self):
return self._pubalg
@pubalg.register(int)
@pubalg.register(PubKeyAlgorithm)
def pubalg_int(self, val):
self._pubalg = PubKeyAlgorithm(val)
if self._pubalg in [PubKeyAlgorithm.RSAEncryptOrSign, PubKeyAlgorithm.RSAEncrypt, PubKeyAlgorithm.RSASign]:
self.signature = RSASignature()
elif self._pubalg == PubKeyAlgorithm.DSA:
self.signature = DSASignature()
@sdproperty
def halg(self):
return self._halg
@halg.register(int)
@halg.register(HashAlgorithm)
def halg_int(self, val):
try:
self._halg = HashAlgorithm(val)
except ValueError: # pragma: no cover
self._halg = val
@sdproperty
def signer(self):
return self._signer
@signer.register(str)
@signer.register(six.text_type)
def signer_str(self, val):
self._signer = val
@signer.register(bytearray)
def signer_bin(self, val):
self._signer = binascii.hexlify(val).upper().decode('latin-1')
def __init__(self):
super(OnePassSignatureV3, self).__init__()
self._sigtype = None
self._halg = None
self._pubalg = None
self._signer = b'\x00' * 8
self.nested = False
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(OnePassSignatureV3, self).__bytearray__()
_bytes += bytearray([self.sigtype])
_bytes += bytearray([self.halg])
_bytes += bytearray([self.pubalg])
_bytes += binascii.unhexlify(six.b(self.signer))
_bytes += bytearray([int(self.nested)])
return _bytes
def parse(self, packet):
super(OnePassSignatureV3, self).parse(packet)
self.sigtype = packet[0]
del packet[0]
self.halg = packet[0]
del packet[0]
self.pubalg = packet[0]
del packet[0]
self.signer = packet[:8]
del packet[:8]
self.nested = (packet[0] == 1)
del packet[0]
class PrivKey(VersionedPacket, Primary, Private):
__typeid__ = 0x05
__ver__ = 0
class PubKey(VersionedPacket, Primary, Public):
__typeid__ = 0x06
__ver__ = 0
@abc.abstractproperty
def fingerprint(self):
"""compute and return the fingerprint of the key"""
class PubKeyV4(PubKey):
__ver__ = 4
@sdproperty
def created(self):
return self._created
@created.register(datetime)
def created_datetime(self, val):
self._created = val
@created.register(int)
def created_int(self, val):
self.created = datetime.utcfromtimestamp(val)
@created.register(bytes)
@created.register(bytearray)
def created_bin(self, val):
self.created = self.bytes_to_int(val)
@sdproperty
def pkalg(self):
return self._pkalg
@pkalg.register(int)
@pkalg.register(PubKeyAlgorithm)
def pkalg_int(self, val):
self._pkalg = PubKeyAlgorithm(val)
_c = {
# True means public
(True, PubKeyAlgorithm.RSAEncryptOrSign): RSAPub,
(True, PubKeyAlgorithm.RSAEncrypt): RSAPub,
(True, PubKeyAlgorithm.RSASign): RSAPub,
(True, PubKeyAlgorithm.DSA): DSAPub,
(True, PubKeyAlgorithm.ElGamal): ElGPub,
(True, PubKeyAlgorithm.FormerlyElGamalEncryptOrSign): ElGPub,
(True, PubKeyAlgorithm.ECDSA): ECDSAPub,
(True, PubKeyAlgorithm.ECDH): ECDHPub,
# False means private
(False, PubKeyAlgorithm.RSAEncryptOrSign): RSAPriv,
(False, PubKeyAlgorithm.RSAEncrypt): RSAPriv,
(False, PubKeyAlgorithm.RSASign): RSAPriv,
(False, PubKeyAlgorithm.DSA): DSAPriv,
(False, PubKeyAlgorithm.ElGamal): ElGPriv,
(False, PubKeyAlgorithm.FormerlyElGamalEncryptOrSign): ElGPriv,
(False, PubKeyAlgorithm.ECDSA): ECDSAPriv,
(False, PubKeyAlgorithm.ECDH): ECDHPriv,
}
k = (self.public, self.pkalg)
km = _c.get(k, None)
self.keymaterial = (km or (OpaquePubKey if self.public else OpaquePrivKey))()
# km = _c.get(k, None)
# self.keymaterial = km() if km is not None else km
@property
def public(self):
return isinstance(self, PubKey) and not isinstance(self, PrivKey)
@property
def fingerprint(self):
# A V4 fingerprint is the 160-bit SHA-1 hash of the octet 0x99, followed by the two-octet packet length,
# followed by the entire Public-Key packet starting with the version field. The Key ID is the
# low-order 64 bits of the fingerprint.
fp = hashlib.new('sha1')
plen = self.keymaterial.publen()
bcde_len = self.int_to_bytes(6 + plen, 2)
# a.1) 0x99 (1 octet)
# a.2) high-order length octet
# a.3) low-order length octet
fp.update(b'\x99' + bcde_len[:1] + bcde_len[-1:])
# b) version number = 4 (1 octet);
fp.update(b'\x04')
# c) timestamp of key creation (4 octets);
fp.update(self.int_to_bytes(calendar.timegm(self.created.timetuple()), 4))
# d) algorithm (1 octet): 17 = DSA (example);
fp.update(self.int_to_bytes(self.pkalg))
# e) Algorithm-specific fields.
fp.update(self.keymaterial.__bytearray__()[:plen])
# and return the digest
return Fingerprint(fp.hexdigest().upper())
def __init__(self):
super(PubKeyV4, self).__init__()
self.created = datetime.utcnow()
self.pkalg = 0
self.keymaterial = None
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(PubKeyV4, self).__bytearray__()
_bytes += self.int_to_bytes(calendar.timegm(self.created.timetuple()), 4)
_bytes += self.int_to_bytes(self.pkalg)
_bytes += self.keymaterial.__bytearray__()
return _bytes
def __copy__(self):
pk = self.__class__()
pk.header = copy.copy(self.header)
pk.created = self.created
pk.pkalg = self.pkalg
pk.keymaterial = copy.copy(self.keymaterial)
return pk
def verify(self, subj, sigbytes, hash_alg):
return self.keymaterial.verify(subj, sigbytes, hash_alg)
def parse(self, packet):
super(PubKeyV4, self).parse(packet)
self.created = packet[:4]
del packet[:4]
self.pkalg = packet[0]
del packet[0]
# bound keymaterial to the remaining length of the packet
pend = self.header.length - 6
self.keymaterial.parse(packet[:pend])
del packet[:pend]
class PrivKeyV4(PrivKey, PubKeyV4):
__ver__ = 4
@classmethod
def new(cls, key_algorithm, key_size):
# build a key packet
pk = PrivKeyV4()
pk.pkalg = key_algorithm
if pk.keymaterial is None:
raise NotImplementedError(key_algorithm)
pk.keymaterial._generate(key_size)
pk.update_hlen()
return pk
def pubkey(self):
# return a copy of ourselves, but just the public half
pk = PubKeyV4() if not isinstance(self, PrivSubKeyV4) else PubSubKeyV4()
pk.created = self.created
pk.pkalg = self.pkalg
# copy over MPIs
for pm in self.keymaterial.__pubfields__:
setattr(pk.keymaterial, pm, copy.copy(getattr(self.keymaterial, pm)))
if self.pkalg == PubKeyAlgorithm.ECDSA:
pk.keymaterial.oid = self.keymaterial.oid
if self.pkalg == PubKeyAlgorithm.ECDH:
pk.keymaterial.oid = self.keymaterial.oid
pk.keymaterial.kdf = copy.copy(self.keymaterial.kdf)
pk.update_hlen()
return pk
@property
def protected(self):
return bool(self.keymaterial.s2k)
@property
def unlocked(self):
if self.protected:
return 0 not in list(self.keymaterial)
return True # pragma: no cover
def protect(self, passphrase, enc_alg, hash_alg):
self.keymaterial.encrypt_keyblob(passphrase, enc_alg, hash_alg)
del passphrase
self.update_hlen()
def unprotect(self, passphrase):
self.keymaterial.decrypt_keyblob(passphrase)
del passphrase
def sign(self, sigdata, hash_alg):
return self.keymaterial.sign(sigdata, hash_alg)
class PrivSubKey(VersionedPacket, Sub, Private):
__typeid__ = 0x07
__ver__ = 0
class PrivSubKeyV4(PrivSubKey, PrivKeyV4):
__ver__ = 4
class CompressedData(Packet):
"""
5.6. Compressed Data Packet (Tag 8)
The Compressed Data packet contains compressed data. Typically, this
packet is found as the contents of an encrypted packet, or following
a Signature or One-Pass Signature packet, and contains a literal data
packet.
The body of this packet consists of:
- One octet that gives the algorithm used to compress the packet.
- Compressed data, which makes up the remainder of the packet.
A Compressed Data Packet's body contains an block that compresses
some set of packets. See section "Packet Composition" for details on
how messages are formed.
ZIP-compressed packets are compressed with raw RFC 1951 [RFC1951]
DEFLATE blocks. Note that PGP V2.6 uses 13 bits of compression. If
an implementation uses more bits of compression, PGP V2.6 cannot
decompress it.
ZLIB-compressed packets are compressed with RFC 1950 [RFC1950] ZLIB-
style blocks.
BZip2-compressed packets are compressed using the BZip2 [BZ2]
algorithm.
"""
__typeid__ = 0x08
@sdproperty
def calg(self):
return self._calg
@calg.register(int)
@calg.register(CompressionAlgorithm)
def calg_int(self, val):
self._calg = CompressionAlgorithm(val)
def __init__(self):
super(CompressedData, self).__init__()
self._calg = None
self.packets = []
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(CompressedData, self).__bytearray__()
_bytes += bytearray([self.calg])
_pb = bytearray()
for pkt in self.packets:
_pb += pkt.__bytearray__()
_bytes += self.calg.compress(bytes(_pb))
return _bytes
def parse(self, packet):
super(CompressedData, self).parse(packet)
self.calg = packet[0]
del packet[0]
cdata = bytearray(self.calg.decompress(packet[:self.header.length - 1]))
del packet[:self.header.length - 1]
while len(cdata) > 0:
self.packets.append(Packet(cdata))
class SKEData(Packet):
"""
5.7. Symmetrically Encrypted Data Packet (Tag 9)
The Symmetrically Encrypted Data packet contains data encrypted with
a symmetric-key algorithm. When it has been decrypted, it contains
other packets (usually a literal data packet or compressed data
packet, but in theory other Symmetrically Encrypted Data packets or
sequences of packets that form whole OpenPGP messages).
The body of this packet consists of:
- Encrypted data, the output of the selected symmetric-key cipher
operating in OpenPGP's variant of Cipher Feedback (CFB) mode.
The symmetric cipher used may be specified in a Public-Key or
Symmetric-Key Encrypted Session Key packet that precedes the
Symmetrically Encrypted Data packet. In that case, the cipher
algorithm octet is prefixed to the session key before it is
encrypted. If no packets of these types precede the encrypted data,
the IDEA algorithm is used with the session key calculated as the MD5
hash of the passphrase, though this use is deprecated.
The data is encrypted in CFB mode, with a CFB shift size equal to the
cipher's block size. The Initial Vector (IV) is specified as all
zeros. Instead of using an IV, OpenPGP prefixes a string of length
equal to the block size of the cipher plus two to the data before it
is encrypted. The first block-size octets (for example, 8 octets for
a 64-bit block length) are random, and the following two octets are
copies of the last two octets of the IV. For example, in an 8-octet
block, octet 9 is a repeat of octet 7, and octet 10 is a repeat of
octet 8. In a cipher of length 16, octet 17 is a repeat of octet 15
and octet 18 is a repeat of octet 16. As a pedantic clarification,
in both these examples, we consider the first octet to be numbered 1.
After encrypting the first block-size-plus-two octets, the CFB state
is resynchronized. The last block-size octets of ciphertext are
passed through the cipher and the block boundary is reset.
The repetition of 16 bits in the random data prefixed to the message
allows the receiver to immediately check whether the session key is
incorrect. See the "Security Considerations" section for hints on
the proper use of this "quick check".
"""
__typeid__ = 0x09
def __init__(self):
super(SKEData, self).__init__()
self.ct = bytearray()
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(SKEData, self).__bytearray__()
_bytes += self.ct
return _bytes
def __copy__(self):
skd = self.__class__()
skd.ct = self.ct[:]
return skd
def parse(self, packet):
super(SKEData, self).parse(packet)
self.ct = packet[:self.header.length]
del packet[:self.header.length]
def decrypt(self, key, alg): # pragma: no cover
pt = _decrypt(bytes(self.ct), bytes(key), alg)
iv = bytes(pt[:alg.block_size // 8])
del pt[:alg.block_size // 8]
ivl2 = bytes(pt[:2])
del pt[:2]
if not constant_time.bytes_eq(iv[-2:], ivl2):
raise PGPDecryptionError("Decryption failed")
return pt
class Marker(Packet):
__typeid__ = 0x0a
def __init__(self):
super(Marker, self).__init__()
self.data = b'PGP'
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(Marker, self).__bytearray__()
_bytes += self.data
return _bytes
def parse(self, packet):
super(Marker, self).parse(packet)
self.data = packet[:self.header.length]
del packet[:self.header.length]
class LiteralData(Packet):
"""
5.9. Literal Data Packet (Tag 11)
A Literal Data packet contains the body of a message; data that is
not to be further interpreted.
The body of this packet consists of:
- A one-octet field that describes how the data is formatted.
If it is a 'b' (0x62), then the Literal packet contains binary data.
If it is a 't' (0x74), then it contains text data, and thus may need
line ends converted to local form, or other text-mode changes. The
tag 'u' (0x75) means the same as 't', but also indicates that
implementation believes that the literal data contains UTF-8 text.
Early versions of PGP also defined a value of 'l' as a 'local' mode
for machine-local conversions. RFC 1991 [RFC1991] incorrectly stated
this local mode flag as '1' (ASCII numeral one). Both of these local
modes are deprecated.
- File name as a string (one-octet length, followed by a file
name). This may be a zero-length string. Commonly, if the
source of the encrypted data is a file, this will be the name of
the encrypted file. An implementation MAY consider the file name
in the Literal packet to be a more authoritative name than the
actual file name.
If the special name "_CONSOLE" is used, the message is considered to
be "for your eyes only". This advises that the message data is
unusually sensitive, and the receiving program should process it more
carefully, perhaps avoiding storing the received data to disk, for
example.
- A four-octet number that indicates a date associated with the
literal data. Commonly, the date might be the modification date
of a file, or the time the packet was created, or a zero that
indicates no specific time.
- The remainder of the packet is literal data.
Text data is stored with text endings (i.e., network-
normal line endings). These should be converted to native line
endings by the receiving software.
"""
__typeid__ = 0x0B
@sdproperty
def mtime(self):
return self._mtime
@mtime.register(datetime)
def mtime_datetime(self, val):
self._mtime = val
@mtime.register(int)
def mtime_int(self, val):
self.mtime = datetime.utcfromtimestamp(val)
@mtime.register(bytes)
@mtime.register(bytearray)
def mtime_bin(self, val):
self.mtime = self.bytes_to_int(val)
@property
def contents(self):
if self.format == 't':
return self._contents.decode('latin-1')
if self.format == 'u':
return self._contents.decode('utf-8')
return self._contents
def __init__(self):
super(LiteralData, self).__init__()
self.format = 'b'
self.filename = ''
self.mtime = datetime.utcnow()
self._contents = bytearray()
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(LiteralData, self).__bytearray__()
_bytes += self.format.encode('latin-1')
_bytes += bytearray([len(self.filename)])
_bytes += self.filename.encode('latin-1')
_bytes += self.int_to_bytes(calendar.timegm(self.mtime.timetuple()), 4)
_bytes += self._contents
return _bytes
def __copy__(self):
pkt = LiteralData()
pkt.header = copy.copy(self.header)
pkt.format = self.format
pkt.filename = self.filename
pkt.mtime = self.mtime
pkt._contents = self._contents[:]
return pkt
def parse(self, packet):
super(LiteralData, self).parse(packet)
self.format = chr(packet[0])
del packet[0]
fnl = packet[0]
del packet[0]
self.filename = packet[:fnl].decode()
del packet[:fnl]
self.mtime = packet[:4]
del packet[:4]
self._contents = packet[:self.header.length - (6 + fnl)]
del packet[:self.header.length - (6 + fnl)]
class Trust(Packet):
"""
5.10. Trust Packet (Tag 12)
The Trust packet is used only within keyrings and is not normally
exported. Trust packets contain data that record the user's
specifications of which key holders are trustworthy introducers,
along with other information that implementing software uses for
trust information. The format of Trust packets is defined by a given
implementation.
Trust packets SHOULD NOT be emitted to output streams that are
transferred to other users, and they SHOULD be ignored on any input
other than local keyring files.
"""
__typeid__ = 0x0C
@sdproperty
def trustlevel(self):
return self._trustlevel
@trustlevel.register(int)
@trustlevel.register(TrustLevel)
def trustlevel_int(self, val):
self._trustlevel = TrustLevel(val & 0x0F)
@sdproperty
def trustflags(self):
return self._trustflags
@trustflags.register(list)
def trustflags_list(self, val):
self._trustflags = val
@trustflags.register(int)
def trustflags_int(self, val):
self._trustflags = TrustFlags & val
def __init__(self):
super(Trust, self).__init__()
self.trustlevel = TrustLevel.Unknown
self.trustflags = []
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(Trust, self).__bytearray__()
_bytes += self.int_to_bytes(self.trustlevel + sum(self.trustflags), 2)
return _bytes
def parse(self, packet):
super(Trust, self).parse(packet)
# self.trustlevel = packet[0] & 0x1f
t = self.bytes_to_int(packet[:2])
del packet[:2]
self.trustlevel = t
self.trustflags = t
class UserID(Packet):
"""
5.11. User ID Packet (Tag 13)
A User ID packet consists of UTF-8 text that is intended to represent
the name and email address of the key holder. By convention, it
includes an RFC 2822 [RFC2822] mail name-addr, but there are no
restrictions on its content. The packet length in the header
specifies the length of the User ID.
"""
__typeid__ = 0x0D
def __init__(self):
super(UserID, self).__init__()
self.name = ""
self.comment = ""
self.email = ""
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(UserID, self).__bytearray__()
_bytes += self.text_to_bytes(self.name)
if self.comment:
_bytes += b' (' + self.text_to_bytes(self.comment) + b')'
if self.email:
_bytes += b' <' + self.text_to_bytes(self.email) + b'>'
return _bytes
def __copy__(self):
uid = UserID()
uid.header = copy.copy(self.header)
uid.name = self.name
uid.comment = self.comment
uid.email = self.email
return uid
def parse(self, packet):
super(UserID, self).parse(packet)
uid_text = packet[:self.header.length].decode('latin-1')
del packet[:self.header.length]
# came across a UID packet with no payload. If that happens, don't bother trying to parse anything!
if self.header.length > 0:
uid = re.match(r"""^
# name should always match something
(?P.+?)
# comment *optionally* matches text in parens following name
# this should never come after email and must be followed immediately by
# either the email field, or the end of the packet.
(\ \((?P.+?)\)(?=(\ <|$)))?
# email *optionally* matches text in angle brackets following name or comment
# this should never come before a comment, if comment exists,
# but can immediately follow name if comment does not exist
(\ <(?P.+)>)?
$
""", uid_text, flags=re.VERBOSE).groupdict()
self.name = uid['name']
self.comment = uid['comment'] or ""
self.email = uid['email'] or ""
class PubSubKey(VersionedPacket, Sub, Public):
__typeid__ = 0x0E
__ver__ = 0
class PubSubKeyV4(PubSubKey, PubKeyV4):
__ver__ = 4
class UserAttribute(Packet):
"""
5.12. User Attribute Packet (Tag 17)
The User Attribute packet is a variation of the User ID packet. It
is capable of storing more types of data than the User ID packet,
which is limited to text. Like the User ID packet, a User Attribute
packet may be certified by the key owner ("self-signed") or any other
key owner who cares to certify it. Except as noted, a User Attribute
packet may be used anywhere that a User ID packet may be used.
While User Attribute packets are not a required part of the OpenPGP
standard, implementations SHOULD provide at least enough
compatibility to properly handle a certification signature on the
User Attribute packet. A simple way to do this is by treating the
User Attribute packet as a User ID packet with opaque contents, but
an implementation may use any method desired.
The User Attribute packet is made up of one or more attribute
subpackets. Each subpacket consists of a subpacket header and a
body. The header consists of:
- the subpacket length (1, 2, or 5 octets)
- the subpacket type (1 octet)
and is followed by the subpacket specific data.
The only currently defined subpacket type is 1, signifying an image.
An implementation SHOULD ignore any subpacket of a type that it does
not recognize. Subpacket types 100 through 110 are reserved for
private or experimental use.
"""
__typeid__ = 0x11
@property
def image(self):
if 'Image' not in self.subpackets:
self.subpackets.addnew('Image')
return next(iter(self.subpackets['Image']))
def __init__(self):
super(UserAttribute, self).__init__()
self.subpackets = UserAttributeSubPackets()
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(UserAttribute, self).__bytearray__()
_bytes += self.subpackets.__bytearray__()
return _bytes
def parse(self, packet):
super(UserAttribute, self).parse(packet)
plen = len(packet)
while self.header.length > (plen - len(packet)):
self.subpackets.parse(packet)
def update_hlen(self):
self.subpackets.update_hlen()
super(UserAttribute, self).update_hlen()
class IntegrityProtectedSKEData(VersionedPacket):
__typeid__ = 0x12
__ver__ = 0
class IntegrityProtectedSKEDataV1(IntegrityProtectedSKEData):
"""
5.13. Sym. Encrypted Integrity Protected Data Packet (Tag 18)
The Symmetrically Encrypted Integrity Protected Data packet is a
variant of the Symmetrically Encrypted Data packet. It is a new
feature created for OpenPGP that addresses the problem of detecting a
modification to encrypted data. It is used in combination with a
Modification Detection Code packet.
There is a corresponding feature in the features Signature subpacket
that denotes that an implementation can properly use this packet
type. An implementation MUST support decrypting these packets and
SHOULD prefer generating them to the older Symmetrically Encrypted
Data packet when possible. Since this data packet protects against
modification attacks, this standard encourages its proliferation.
While blanket adoption of this data packet would create
interoperability problems, rapid adoption is nevertheless important.
An implementation SHOULD specifically denote support for this packet,
but it MAY infer it from other mechanisms.
For example, an implementation might infer from the use of a cipher
such as Advanced Encryption Standard (AES) or Twofish that a user
supports this feature. It might place in the unhashed portion of
another user's key signature a Features subpacket. It might also
present a user with an opportunity to regenerate their own self-
signature with a Features subpacket.
This packet contains data encrypted with a symmetric-key algorithm
and protected against modification by the SHA-1 hash algorithm. When
it has been decrypted, it will typically contain other packets (often
a Literal Data packet or Compressed Data packet). The last decrypted
packet in this packet's payload MUST be a Modification Detection Code
packet.
The body of this packet consists of:
- A one-octet version number. The only currently defined value is
1.
- Encrypted data, the output of the selected symmetric-key cipher
operating in Cipher Feedback mode with shift amount equal to the
block size of the cipher (CFB-n where n is the block size).
The symmetric cipher used MUST be specified in a Public-Key or
Symmetric-Key Encrypted Session Key packet that precedes the
Symmetrically Encrypted Data packet. In either case, the cipher
algorithm octet is prefixed to the session key before it is
encrypted.
The data is encrypted in CFB mode, with a CFB shift size equal to the
cipher's block size. The Initial Vector (IV) is specified as all
zeros. Instead of using an IV, OpenPGP prefixes an octet string to
the data before it is encrypted. The length of the octet string
equals the block size of the cipher in octets, plus two. The first
octets in the group, of length equal to the block size of the cipher,
are random; the last two octets are each copies of their 2nd
preceding octet. For example, with a cipher whose block size is 128
bits or 16 octets, the prefix data will contain 16 random octets,
then two more octets, which are copies of the 15th and 16th octets,
respectively. Unlike the Symmetrically Encrypted Data Packet, no
special CFB resynchronization is done after encrypting this prefix
data. See "OpenPGP CFB Mode" below for more details.
The repetition of 16 bits in the random data prefixed to the message
allows the receiver to immediately check whether the session key is
incorrect.
The plaintext of the data to be encrypted is passed through the SHA-1
hash function, and the result of the hash is appended to the
plaintext in a Modification Detection Code packet. The input to the
hash function includes the prefix data described above; it includes
all of the plaintext, and then also includes two octets of values
0xD3, 0x14. These represent the encoding of a Modification Detection
Code packet tag and length field of 20 octets.
The resulting hash value is stored in a Modification Detection Code
(MDC) packet, which MUST use the two octet encoding just given to
represent its tag and length field. The body of the MDC packet is
the 20-octet output of the SHA-1 hash.
The Modification Detection Code packet is appended to the plaintext
and encrypted along with the plaintext using the same CFB context.
During decryption, the plaintext data should be hashed with SHA-1,
including the prefix data as well as the packet tag and length field
of the Modification Detection Code packet. The body of the MDC
packet, upon decryption, is compared with the result of the SHA-1
hash.
Any failure of the MDC indicates that the message has been modified
and MUST be treated as a security problem. Failures include a
difference in the hash values, but also the absence of an MDC packet,
or an MDC packet in any position other than the end of the plaintext.
Any failure SHOULD be reported to the user.
Note: future designs of new versions of this packet should consider
rollback attacks since it will be possible for an attacker to change
the version back to 1.
"""
__ver__ = 1
def __init__(self):
super(IntegrityProtectedSKEDataV1, self).__init__()
self.ct = bytearray()
def __bytearray__(self):
_bytes = bytearray()
_bytes += super(IntegrityProtectedSKEDataV1, self).__bytearray__()
_bytes += self.ct
return _bytes
def __copy__(self):
skd = self.__class__()
skd.ct = self.ct[:]
return skd
def parse(self, packet):
super(IntegrityProtectedSKEDataV1, self).parse(packet)
self.ct = packet[:self.header.length - 1]
del packet[:self.header.length - 1]
def encrypt(self, key, alg, data):
iv = alg.gen_iv()
data = iv + iv[-2:] + data
mdc = MDC()
mdc.mdc = binascii.hexlify(hashlib.new('SHA1', data + b'\xd3\x14').digest())
mdc.update_hlen()
data += mdc.__bytes__()
self.ct = _encrypt(data, key, alg)
self.update_hlen()
def decrypt(self, key, alg):
# iv, ivl2, pt = super(IntegrityProtectedSKEDataV1, self).decrypt(key, alg)
pt = _decrypt(bytes(self.ct), bytes(key), alg)
# do the MDC checks
_expected_mdcbytes = b'\xd3\x14' + hashlib.new('SHA1', pt[:-20]).digest()
if not constant_time.bytes_eq(bytes(pt[-22:]), _expected_mdcbytes):
raise PGPDecryptionError("Decryption failed") # pragma: no cover
iv = bytes(pt[:alg.block_size // 8])
del pt[:alg.block_size // 8]
ivl2 = bytes(pt[:2])
del pt[:2]
if not constant_time.bytes_eq(iv[-2:], ivl2):
raise PGPDecryptionError("Decryption failed") # pragma: no cover
return pt
class MDC(Packet):
"""
5.14. Modification Detection Code Packet (Tag 19)
The Modification Detection Code packet contains a SHA-1 hash of
plaintext data, which is used to detect message modification. It is
only used with a Symmetrically Encrypted Integrity Protected Data
packet. The Modification Detection Code packet MUST be the last
packet in the plaintext data that is encrypted in the Symmetrically
Encrypted Integrity Protected Data packet, and MUST appear in no
other place.
A Modification Detection Code packet MUST have a length of 20 octets.
The body of this packet consists of:
- A 20-octet SHA-1 hash of the preceding plaintext data of the
Symmetrically Encrypted Integrity Protected Data packet,
including prefix data, the tag octet, and length octet of the
Modification Detection Code packet.
Note that the Modification Detection Code packet MUST always use a
new format encoding of the packet tag, and a one-octet encoding of
the packet length. The reason for this is that the hashing rules for
modification detection include a one-octet tag and one-octet length
in the data hash. While this is a bit restrictive, it reduces
complexity.
"""
__typeid__ = 0x13
def __init__(self):
super(MDC, self).__init__()
self.mdc = ''
def __bytearray__(self):
return super(MDC, self).__bytearray__() + binascii.unhexlify(self.mdc)
def parse(self, packet):
super(MDC, self).parse(packet)
self.mdc = binascii.hexlify(packet[:20])
del packet[:20]
python-pgpy-0.4.3/pgpy/packet/subpackets/ 0000775 0000000 0000000 00000000000 13145172200 0020401 5 ustar 00root root 0000000 0000000 python-pgpy-0.4.3/pgpy/packet/subpackets/__init__.py 0000664 0000000 0000000 00000000315 13145172200 0022511 0 ustar 00root root 0000000 0000000 from .types import Signature as Signature
from .types import UserAttribute as UserAttribute
from .signature import * # NOQA
from .userattribute import * # NOQA
__all__ = ['Signature', 'UserAttribute']
python-pgpy-0.4.3/pgpy/packet/subpackets/signature.py 0000664 0000000 0000000 00000060237 13145172200 0022764 0 ustar 00root root 0000000 0000000 """ signature.py
Signature SubPackets
"""
import binascii
import calendar
from datetime import datetime
from datetime import timedelta
import six
from .types import EmbeddedSignatureHeader
from .types import Signature
from ...constants import CompressionAlgorithm
from ...constants import Features as _Features
from ...constants import HashAlgorithm
from ...constants import KeyFlags as _KeyFlags
from ...constants import KeyServerPreferences as _KeyServerPreferences
from ...constants import NotationDataFlags
from ...constants import PubKeyAlgorithm
from ...constants import RevocationKeyClass
from ...constants import RevocationReason
from ...constants import SymmetricKeyAlgorithm
from ...decorators import sdproperty
from ...types import Fingerprint
__all__ = ['URI',
'FlagList',
'ByteFlag',
'Boolean',
'CreationTime',
'SignatureExpirationTime',
'ExportableCertification',
'TrustSignature',
'RegularExpression',
'Revocable',
'KeyExpirationTime',
'PreferredSymmetricAlgorithms',
'RevocationKey',
'Issuer',
'NotationData',
'PreferredHashAlgorithms',
'PreferredCompressionAlgorithms',
'KeyServerPreferences',
'PreferredKeyServer',
'PrimaryUserID',
'Policy',
'KeyFlags',
'SignersUserID',
'ReasonForRevocation',
'Features',
'EmbeddedSignature']
class URI(Signature):
@sdproperty
def uri(self):
return self._uri
@uri.register(str)
@uri.register(six.text_type)
def uri_str(self, val):
self._uri = val
@uri.register(bytearray)
def uri_bytearray(self, val):
self.uri = val.decode('latin-1')
def __init__(self):
super(URI, self).__init__()
self.uri = ""
def __bytearray__(self):
_bytes = super(URI, self).__bytearray__()
_bytes += self.uri.encode()
return _bytes
def parse(self, packet):
super(URI, self).parse(packet)
self.uri = packet[:(self.header.length - 1)]
del packet[:(self.header.length - 1)]
class FlagList(Signature):
__flags__ = None
@sdproperty
def flags(self):
return self._flags
@flags.register(list)
@flags.register(tuple)
def flags_list(self, val):
self._flags = list(val)
@flags.register(int)
@flags.register(CompressionAlgorithm)
@flags.register(HashAlgorithm)
@flags.register(PubKeyAlgorithm)
@flags.register(SymmetricKeyAlgorithm)
def flags_int(self, val):
if self.__flags__ is None: # pragma: no cover
raise AttributeError("Error: __flags__ not set!")
self._flags.append(self.__flags__(val))
@flags.register(bytearray)
def flags_bytearray(self, val):
self.flags = self.bytes_to_int(val)
def __init__(self):
super(FlagList, self).__init__()
self.flags = []
def __bytearray__(self):
_bytes = super(FlagList, self).__bytearray__()
_bytes += b''.join(self.int_to_bytes(b) for b in self.flags)
return _bytes
def parse(self, packet):
super(FlagList, self).parse(packet)
for i in range(0, self.header.length - 1):
self.flags = packet[:1]
del packet[:1]
class ByteFlag(Signature):
__flags__ = None
@sdproperty
def flags(self):
return self._flags
@flags.register(set)
@flags.register(list)
def flags_seq(self, val):
self._flags = set(val)
@flags.register(int)
@flags.register(_KeyFlags)
@flags.register(_Features)
def flags_int(self, val):
if self.__flags__ is None: # pragma: no cover
raise AttributeError("Error: __flags__ not set!")
self._flags |= (self.__flags__ & val)
@flags.register(bytearray)
def flags_bytearray(self, val):
self.flags = self.bytes_to_int(val)
def __init__(self):
super(ByteFlag, self).__init__()
self.flags = []
def __bytearray__(self):
_bytes = super(ByteFlag, self).__bytearray__()
_bytes += self.int_to_bytes(sum(self.flags))
# null-pad _bytes if they are not up to the end now
if len(_bytes) < len(self):
_bytes += b'\x00' * (len(self) - len(_bytes))
return _bytes
def parse(self, packet):
super(ByteFlag, self).parse(packet)
for i in range(0, self.header.length - 1):
self.flags = packet[:1]
del packet[:1]
class Boolean(Signature):
@sdproperty
def bflag(self):
return self._bool
@bflag.register(bool)
def bflag_bool(self, val):
self._bool = val
@bflag.register(bytearray)
def bflag_bytearray(self, val):
self.bool = bool(self.bytes_to_int(val))
def __init__(self):
super(Boolean, self).__init__()
self.bflag = False
def __bytearray__(self):
_bytes = super(Boolean, self).__bytearray__()
_bytes += self.int_to_bytes(int(self.bflag))
return _bytes
def __bool__(self):
return self.bflag
def __nonzero__(self):
return self.__bool__()
def parse(self, packet):
super(Boolean, self).parse(packet)
self.bflag = packet[:1]
del packet[:1]
class CreationTime(Signature):
"""
5.2.3.4. Signature Creation Time
(4-octet time field)
The time the signature was made.
MUST be present in the hashed area.
"""
__typeid__ = 0x02
@sdproperty
def created(self):
return self._created
@created.register(datetime)
def created_datetime(self, val):
self._created = val
@created.register(int)
def created_int(self, val):
self.created = datetime.utcfromtimestamp(val)
@created.register(bytearray)
def created_bytearray(self, val):
self.created = self.bytes_to_int(val)
def __init__(self):
super(CreationTime, self).__init__()
self.created = datetime.utcnow()
def __bytearray__(self):
_bytes = super(CreationTime, self).__bytearray__()
_bytes += self.int_to_bytes(calendar.timegm(self.created.timetuple()), 4)
return _bytes
def parse(self, packet):
super(CreationTime, self).parse(packet)
self.created = packet[:4]
del packet[:4]
class SignatureExpirationTime(Signature):
"""
5.2.3.10. Signature Expiration Time
(4-octet time field)
The validity period of the signature. This is the number of seconds
after the signature creation time that the signature expires. If
this is not present or has a value of zero, it never expires.
"""
__typeid__ = 0x03
@sdproperty
def expires(self):
return self._expires
@expires.register(timedelta)
def expires_timedelta(self, val):
self._expires = val
@expires.register(int)
def expires_int(self, val):
self.expires = timedelta(seconds=val)
@expires.register(bytearray)
def expires_bytearray(self, val):
self.expires = self.bytes_to_int(val)
def __init__(self):
super(SignatureExpirationTime, self).__init__()
self.expires = 0
def __bytearray__(self):
_bytes = super(SignatureExpirationTime, self).__bytearray__()
_bytes += self.int_to_bytes(int(self.expires.total_seconds()), 4)
return _bytes
def parse(self, packet):
super(SignatureExpirationTime, self).parse(packet)
self.expires = packet[:4]
del packet[:4]
class ExportableCertification(Boolean):
"""
5.2.3.11. Exportable Certification
(1 octet of exportability, 0 for not, 1 for exportable)
This subpacket denotes whether a certification signature is
"exportable", to be used by other users than the signature's issuer.
The packet body contains a Boolean flag indicating whether the
signature is exportable. If this packet is not present, the
certification is exportable; it is equivalent to a flag containing a
1.
Non-exportable, or "local", certifications are signatures made by a
user to mark a key as valid within that user's implementation only.
Thus, when an implementation prepares a user's copy of a key for
transport to another user (this is the process of "exporting" the
key), any local certification signatures are deleted from the key.
The receiver of a transported key "imports" it, and likewise trims
any local certifications. In normal operation, there won't be any,
assuming the import is performed on an exported key. However, there
are instances where this can reasonably happen. For example, if an
implementation allows keys to be imported from a key database in
addition to an exported key, then this situation can arise.
Some implementations do not represent the interest of a single user
(for example, a key server). Such implementations always trim local
certifications from any key they handle.
"""
__typeid__ = 0x04
class TrustSignature(Signature):
"""
5.2.3.13. Trust Signature
(1 octet "level" (depth), 1 octet of trust amount)
Signer asserts that the key is not only valid but also trustworthy at
the specified level. Level 0 has the same meaning as an ordinary
validity signature. Level 1 means that the signed key is asserted to
be a valid trusted introducer, with the 2nd octet of the body
specifying the degree of trust. Level 2 means that the signed key is
asserted to be trusted to issue level 1 trust signatures, i.e., that
it is a "meta introducer". Generally, a level n trust signature
asserts that a key is trusted to issue level n-1 trust signatures.
The trust amount is in a range from 0-255, interpreted such that
values less than 120 indicate partial trust and values of 120 or
greater indicate complete trust. Implementations SHOULD emit values
of 60 for partial trust and 120 for complete trust.
"""
__typeid__ = 0x05
@sdproperty
def level(self):
return self._level
@level.register(int)
def level_int(self, val):
self._level = val
@level.register(bytearray)
def level_bytearray(self, val):
self.level = self.bytes_to_int(val)
@sdproperty
def amount(self):
return self._amount
@amount.register(int)
def amount_int(self, val):
# clamp 'val' to the range 0-255
self._amount = max(0, min(val, 255))
@amount.register(bytearray)
def amount_bytearray(self, val):
self.amount = self.bytes_to_int(val)
def __init__(self):
super(TrustSignature, self).__init__()
self.level = 0
self.amount = 0
def __bytearray__(self):
_bytes = super(TrustSignature, self).__bytearray__()
_bytes += self.int_to_bytes(self.level)
_bytes += self.int_to_bytes(self.amount)
return _bytes
def parse(self, packet):
super(TrustSignature, self).parse(packet)
self.level = packet[:1]
del packet[:1]
self.amount = packet[:1]
del packet[:1]
class RegularExpression(Signature):
"""
5.2.3.14. Regular Expression
(null-terminated regular expression)
Used in conjunction with trust Signature packets (of level > 0) to
limit the scope of trust that is extended. Only signatures by the
target key on User IDs that match the regular expression in the body
of this packet have trust extended by the trust Signature subpacket.
The regular expression uses the same syntax as the Henry Spencer's
"almost public domain" regular expression [REGEX] package. A
description of the syntax is found in Section 8 below.
"""
__typeid__ = 0x06
@sdproperty
def regex(self):
return self._regex
@regex.register(str)
@regex.register(six.text_type)
def regex_str(self, val):
self._regex = val
@regex.register(bytearray)
def regex_bytearray(self, val):
self.regex = val.decode('latin-1')
def __init__(self):
super(RegularExpression, self).__init__()
self.regex = r''
def __bytearray__(self):
_bytes = super(RegularExpression, self).__bytearray__()
_bytes += self.regex.encode()
return _bytes
def parse(self, packet):
super(RegularExpression, self).parse(packet)
self.regex = packet[:(self.header.length - 1)]
del packet[:(self.header.length - 1)]
class Revocable(Boolean):
"""
5.2.3.12. Revocable
(1 octet of revocability, 0 for not, 1 for revocable)
Signature's revocability status. The packet body contains a Boolean
flag indicating whether the signature is revocable. Signatures that
are not revocable have any later revocation signatures ignored. They
represent a commitment by the signer that he cannot revoke his
signature for the life of his key. If this packet is not present,
the signature is revocable.
"""
__typeid__ = 0x07
class KeyExpirationTime(SignatureExpirationTime):
"""
5.2.3.6. Key Expiration Time
(4-octet time field)
The validity period of the key. This is the number of seconds after
the key creation time that the key expires. If this is not present
or has a value of zero, the key never expires. This is found only on
a self-signature.
"""
__typeid__ = 0x09
class PreferredSymmetricAlgorithms(FlagList):
"""
5.2.3.7. Preferred Symmetric Algorithms
(array of one-octet values)
Symmetric algorithm numbers that indicate which algorithms the key
holder prefers to use. The subpacket body is an ordered list of
octets with the most preferred listed first. It is assumed that only
algorithms listed are supported by the recipient's software.
Algorithm numbers are in Section 9. This is only found on a self-
signature.
"""
__typeid__ = 0x0B
__flags__ = SymmetricKeyAlgorithm
class RevocationKey(Signature):
"""
5.2.3.15. Revocation Key
(1 octet of class, 1 octet of public-key algorithm ID, 20 octets of
fingerprint)
Authorizes the specified key to issue revocation signatures for this
key. Class octet must have bit 0x80 set. If the bit 0x40 is set,
then this means that the revocation information is sensitive. Other
bits are for future expansion to other kinds of authorizations. This
is found on a self-signature.
If the "sensitive" flag is set, the keyholder feels this subpacket
contains private trust information that describes a real-world
sensitive relationship. If this flag is set, implementations SHOULD
NOT export this signature to other users except in cases where the
data needs to be available: when the signature is being sent to the
designated revoker, or when it is accompanied by a revocation
signature from that revoker. Note that it may be appropriate to
isolate this subpacket within a separate signature so that it is not
combined with other subpackets that need to be exported.
"""
__typeid__ = 0x0C
@sdproperty
def keyclass(self):
return self._keyclass
@keyclass.register(list)
def keyclass_list(self, val):
self._keyclass = val
@keyclass.register(int)
@keyclass.register(RevocationKeyClass)
def keyclass_int(self, val):
self._keyclass += RevocationKeyClass & val
@keyclass.register(bytearray)
def keyclass_bytearray(self, val):
self.keyclass = self.bytes_to_int(val)
@sdproperty
def algorithm(self):
return self._algorithm
@algorithm.register(int)
@algorithm.register(PubKeyAlgorithm)
def algorithm_int(self, val):
self._algorithm = PubKeyAlgorithm(val)
@algorithm.register(bytearray)
def algorithm_bytearray(self, val):
self.algorithm = self.bytes_to_int(val)
@sdproperty
def fingerprint(self):
return self._fingerprint
@fingerprint.register(str)
@fingerprint.register(six.text_type)
@fingerprint.register(Fingerprint)
def fingerprint_str(self, val):
self._fingerprint = Fingerprint(val)
@fingerprint.register(bytearray)
def fingerprint_bytearray(self, val):
self.fingerprint = ''.join('{:02x}'.format(c) for c in val).upper()
def __init__(self):
super(RevocationKey, self).__init__()
self.keyclass = []
self.algorithm = PubKeyAlgorithm.Invalid
self._fingerprint = ""
def __bytearray__(self):
_bytes = super(RevocationKey, self).__bytearray__()
_bytes += self.int_to_bytes(sum(self.keyclass))
_bytes += self.int_to_bytes(self.algorithm.value)
_bytes += self.fingerprint.__bytes__()
return _bytes
def parse(self, packet):
super(RevocationKey, self).parse(packet)
self.keyclass = packet[:1]
del packet[:1]
self.algorithm = packet[:1]
del packet[:1]
self.fingerprint = packet[:20]
del packet[:20]
class Issuer(Signature):
__typeid__ = 0x10
@sdproperty
def issuer(self):
return self._issuer
@issuer.register(bytearray)
def issuer_bytearray(self, val):
self._issuer = binascii.hexlify(val).upper().decode('latin-1')
def __init__(self):
super(Issuer, self).__init__()
self.issuer = bytearray()
def __bytearray__(self):
_bytes = super(Issuer, self).__bytearray__()
_bytes += binascii.unhexlify(self._issuer.encode())
return _bytes
def parse(self, packet):
super(Issuer, self).parse(packet)
self.issuer = packet[:8]
del packet[:8]
class NotationData(Signature):
__typeid__ = 0x14
@sdproperty
def flags(self):
return self._flags
@flags.register(list)
def flags_list(self, val):
self._flags = val
@flags.register(int)
@flags.register(NotationDataFlags)
def flags_int(self, val):
self.flags += NotationDataFlags & val
@flags.register(bytearray)
def flags_bytearray(self, val):
self.flags = self.bytes_to_int(val)
@sdproperty
def name(self):
return self._name
@name.register(str)
@name.register(six.text_type)
def name_str(self, val):
self._name = val
@name.register(bytearray)
def name_bytearray(self, val):
self.name = val.decode('latin-1')
@sdproperty
def value(self):
return self._value
@value.register(str)
@value.register(six.text_type)
def value_str(self, val):
self._value = val
@value.register(bytearray)
def value_bytearray(self, val):
if NotationDataFlags.HumanReadable in self.flags:
self.value = val.decode('latin-1')
else: # pragma: no cover
self._value = val
def __init__(self):
super(NotationData, self).__init__()
self.flags = [0, 0, 0, 0]
self.name = ""
self.value = ""
def __bytearray__(self):
_bytes = super(NotationData, self).__bytearray__()
_bytes += self.int_to_bytes(sum(self.flags)) + b'\x00\x00\x00'
_bytes += self.int_to_bytes(len(self.name), 2)
_bytes += self.int_to_bytes(len(self.value), 2)
_bytes += self.name.encode()
_bytes += self.value if isinstance(self.value, bytearray) else self.value.encode()
return bytes(_bytes)
def parse(self, packet):
super(NotationData, self).parse(packet)
self.flags = packet[:1]
del packet[:4]
nlen = self.bytes_to_int(packet[:2])
del packet[:2]
vlen = self.bytes_to_int(packet[:2])
del packet[:2]
self.name = packet[:nlen]
del packet[:nlen]
self.value = packet[:vlen]
del packet[:vlen]
class PreferredHashAlgorithms(FlagList):
__typeid__ = 0x15
__flags__ = HashAlgorithm
class PreferredCompressionAlgorithms(FlagList):
__typeid__ = 0x16
__flags__ = CompressionAlgorithm
class KeyServerPreferences(FlagList):
__typeid__ = 0x17
__flags__ = _KeyServerPreferences
class PreferredKeyServer(URI):
__typeid__ = 0x18
class PrimaryUserID(Signature):
__typeid__ = 0x19
@sdproperty
def primary(self):
return self._primary
@primary.register(bool)
def primary_bool(self, val):
self._primary = val
@primary.register(bytearray)
def primary_byrearray(self, val):
self.primary = bool(self.bytes_to_int(val))
def __init__(self):
super(PrimaryUserID, self).__init__()
self.primary = True
def __bytearray__(self):
_bytes = super(PrimaryUserID, self).__bytearray__()
_bytes += self.int_to_bytes(int(self.primary))
return _bytes
def __bool__(self):
return self.primary
def __nonzero__(self):
return self.__bool__()
def parse(self, packet):
super(PrimaryUserID, self).parse(packet)
self.primary = packet[:1]
del packet[:1]
class Policy(URI):
__typeid__ = 0x1a
class KeyFlags(ByteFlag):
__typeid__ = 0x1B
__flags__ = _KeyFlags
class SignersUserID(Signature):
__typeid__ = 0x1C
@sdproperty
def userid(self):
return self._userid
@userid.register(str)
@userid.register(six.text_type)
def userid_str(self, val):
self._userid = val
@userid.register(bytearray)
def userid_bytearray(self, val):
self.userid = val.decode('latin-1')
def __init__(self):
super(SignersUserID, self).__init__()
self.userid = ""
def __bytearray__(self):
_bytes = super(SignersUserID, self).__bytearray__()
_bytes += self.userid.encode()
return _bytes
def parse(self, packet):
super(SignersUserID, self).parse(packet)
self.userid = packet[:(self.header.length - 1)]
del packet[:(self.header.length - 1)]
class ReasonForRevocation(Signature):
__typeid__ = 0x1D
@sdproperty
def code(self):
return self._code
@code.register(int)
@code.register(RevocationReason)
def code_int(self, val):
self._code = RevocationReason(val)
@code.register(bytearray)
def code_bytearray(self, val):
self.code = self.bytes_to_int(val)
@sdproperty
def string(self):
return self._string
@string.register(str)
@string.register(six.text_type)
def string_str(self, val):
self._string = val
@string.register(bytearray)
def string_bytearray(self, val):
self.string = val.decode('latin-1')
def __init__(self):
super(ReasonForRevocation, self).__init__()
self.code = 0x00
self.string = ""
def __bytearray__(self):
_bytes = super(ReasonForRevocation, self).__bytearray__()
_bytes += self.int_to_bytes(self.code)
_bytes += self.string.encode()
return _bytes
def parse(self, packet):
super(ReasonForRevocation, self).parse(packet)
self.code = packet[:1]
del packet[:1]
self.string = packet[:(self.header.length - 2)]
del packet[:(self.header.length - 2)]
class Features(ByteFlag):
__typeid__ = 0x1E
__flags__ = _Features
##TODO: obtain subpacket type 0x1F - Signature Target
class EmbeddedSignature(Signature):
__typeid__ = 0x20
@sdproperty
def _sig(self):
return self._sigpkt
@_sig.setter
def _sig(self, val):
esh = EmbeddedSignatureHeader()
esh.version = val.header.version
val.header = esh
val.update_hlen()
self._sigpkt = val
@property
def sigtype(self):
return self._sig.sigtype
@property
def pubalg(self):
return self._sig.pubalg
@property
def halg(self):
return self._sig.halg
@property
def subpackets(self):
return self._sig.subpackets
@property
def hash2(self): # pragma: no cover
return self._sig.hash2
@property
def signature(self):
return self._sig.signature
@property
def signer(self):
return self._sig.signer
def __init__(self):
super(EmbeddedSignature, self).__init__()
from ..packets import SignatureV4
self._sigpkt = SignatureV4()
self._sigpkt.header = EmbeddedSignatureHeader()
def __bytearray__(self):
return super(EmbeddedSignature, self).__bytearray__() + self._sigpkt.__bytearray__()
def parse(self, packet):
super(EmbeddedSignature, self).parse(packet)
self._sig.parse(packet)
python-pgpy-0.4.3/pgpy/packet/subpackets/types.py 0000664 0000000 0000000 00000006375 13145172200 0022132 0 ustar 00root root 0000000 0000000 """ subpacket.py
"""
import abc
from ..types import VersionedHeader
from ...decorators import sdproperty
from ...types import Dispatchable
from ...types import Header as _Header
__all__ = ['Header',
'EmbeddedSignatureHeader',
'SubPacket',
'Signature',
'UserAttribute',
'Opaque']
class Header(_Header):
@sdproperty
def critical(self):
return self._critical
@critical.register(bool)
def critical_bool(self, val):
self._critical = val
@sdproperty
def typeid(self):
return self._typeid
@typeid.register(int)
def typeid_int(self, val):
self._typeid = val & 0x7f
@typeid.register(bytes)
@typeid.register(bytearray)
def typeid_bin(self, val):
v = self.bytes_to_int(val)
self.typeid = v
self.critical = bool(v & 0x80)
def __init__(self):
super(Header, self).__init__()
self.typeid = b'\x00'
self.critical = False
def parse(self, packet):
self.length = packet
self.typeid = packet[:1]
del packet[:1]
def __len__(self):
return self.llen + 1
def __bytearray__(self):
_bytes = bytearray(self.encode_length(self.length))
_bytes += self.int_to_bytes((int(self.critical) << 7) + self.typeid)
return _bytes
class EmbeddedSignatureHeader(VersionedHeader):
def __bytearray__(self):
return bytearray([self.version])
def parse(self, packet):
self.tag = 2
super(EmbeddedSignatureHeader, self).parse(packet)
class SubPacket(Dispatchable):
__headercls__ = Header
def __init__(self):
super(SubPacket, self).__init__()
self.header = Header()
# if self.__typeid__ not in [-1, None]:
if (self.header.typeid == 0x00 and
(not hasattr(self.__typeid__, '__abstractmethod__')) and
(self.__typeid__ not in [-1, None])):
self.header.typeid = self.__typeid__
def __bytearray__(self):
return self.header.__bytearray__()
def __len__(self):
return (self.header.llen + self.header.length)
def __repr__(self):
return "<{} [0x{:02x}] at 0x{:x}>".format(self.__class__.__name__, self.header.typeid, id(self))
def update_hlen(self):
self.header.length = (len(self.__bytearray__()) - len(self.header)) + 1
@abc.abstractmethod
def parse(self, packet): # pragma: no cover
if self.header._typeid == 0:
self.header.parse(packet)
class Signature(SubPacket):
__typeid__ = -1
class UserAttribute(SubPacket):
__typeid__ = -1
class Opaque(Signature, UserAttribute):
__typeid__ = None
@sdproperty
def payload(self):
return self._payload
@payload.register(bytes)
@payload.register(bytearray)
def payload_bin(self, val):
self._payload = bytearray(val)
def __init__(self):
super(Opaque, self).__init__()
self.payload = b''
def __bytearray__(self):
_bytes = super(Opaque, self).__bytearray__()
_bytes += self.payload
return _bytes
def parse(self, packet):
super(Opaque, self).parse(packet)
self.payload = packet[:(self.header.length - 1)]
del packet[:(self.header.length - 1)]
python-pgpy-0.4.3/pgpy/packet/subpackets/userattribute.py 0000664 0000000 0000000 00000006664 13145172200 0023671 0 ustar 00root root 0000000 0000000 """ userattribute.py
"""
import struct
from .types import UserAttribute
from ...constants import ImageEncoding
from ...decorators import sdproperty
from ...memoryview import memoryview
__all__ = ('Image',)
class Image(UserAttribute):
"""
5.12.1. The Image Attribute Subpacket
The Image Attribute subpacket is used to encode an image, presumably
(but not required to be) that of the key owner.
The Image Attribute subpacket begins with an image header. The first
two octets of the image header contain the length of the image
header. Note that unlike other multi-octet numerical values in this
document, due to a historical accident this value is encoded as a
little-endian number. The image header length is followed by a
single octet for the image header version. The only currently
defined version of the image header is 1, which is a 16-octet image
header. The first three octets of a version 1 image header are thus
0x10, 0x00, 0x01.
The fourth octet of a version 1 image header designates the encoding
format of the image. The only currently defined encoding format is
the value 1 to indicate JPEG. Image format types 100 through 110 are
reserved for private or experimental use. The rest of the version 1
image header is made up of 12 reserved octets, all of which MUST be
set to 0.
The rest of the image subpacket contains the image itself. As the
only currently defined image type is JPEG, the image is encoded in
the JPEG File Interchange Format (JFIF), a standard file format for
JPEG images [JFIF].
An implementation MAY try to determine the type of an image by
examination of the image data if it is unable to handle a particular
version of the image header or if a specified encoding format value
is not recognized.
"""
__typeid__ = 0x01
@sdproperty
def version(self):
return self._version
@version.register(int)
def version_int(self, val):
self._version = val
@sdproperty
def iencoding(self):
return self._iencoding
@iencoding.register(int)
@iencoding.register(ImageEncoding)
def iencoding_int(self, val):
try:
self._iencoding = ImageEncoding(val)
except ValueError: # pragma: no cover
self._iencoding = val
@sdproperty
def image(self):
return self._image
@image.register(bytes)
@image.register(bytearray)
def image_bin(self, val):
self._image = bytearray(val)
def __init__(self):
super(Image, self).__init__()
self.version = 1
self.iencoding = 1
self.image = bytearray()
def __bytearray__(self):
_bytes = super(Image, self).__bytearray__()
if self.version == 1:
# v1 image header length is always 16 bytes
# and stored little-endian due to an 'historical accident'
_bytes += struct.pack('