pax_global_header 0000666 0000000 0000000 00000000064 13646725476 0014536 g ustar 00root root 0000000 0000000 52 comment=07e5932fbc7b5c10f28b0d2461e2ec4f581c6f62
pyjks-20.0.0/ 0000775 0000000 0000000 00000000000 13646725476 0012755 5 ustar 00root root 0000000 0000000 pyjks-20.0.0/.gitignore 0000664 0000000 0000000 00000000530 13646725476 0014743 0 ustar 00root root 0000000 0000000 *.py[cod]
docs/_build
# C extensions
*.so
# Packages
.cache/
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
nosetests.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
.idea
.*.swp
.vscode
pyjks-20.0.0/.travis.yml 0000664 0000000 0000000 00000000332 13646725476 0015064 0 ustar 00root root 0000000 0000000 language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
- "3.7"
- "3.8"
- pypy
matrix:
include:
- python: 2.6
dist: trusty
install:
- pip install tox
script:
- tox -e py
pyjks-20.0.0/CHANGELOG.md 0000664 0000000 0000000 00000005364 13646725476 0014576 0 ustar 00root root 0000000 0000000 # PyJKS CHANGELOG
Broadly speaking, the PyJKS has been stable since 2017. The JKS
formats themselves do not change rapidly, either. The original author
no longer uses JKS on a daily basis, but the team is happy to respond
to issues, review PRs, and make new releases as new features are
submitted by active PyJKS users.
PyJKS uses the [CalVer](https://calver.org) versioning scheme (`YY.MINOR.MICRO`).
v19.0.0
-------
*(April 22, 2019)*
A small update, switching to [pycryptodomex][pycryptodomex], for users who may also
need pycrypto for 3rd-party libraries. [#46][i46]
[pycryptodomex]: https://pycryptodome.readthedocs.io/en/latest/src/installation.html
[i46]: https://github.com/kurtbrose/pyjks/pull/46
v18.0.0
-------
*(September 1, 2018)*
A smallish bugfix release:
* Adjusted asn1 encoding so that empty attributes are not included
(fixes [#34][i34])
* Automatically convert aliases to lowercase for keytool compatibility
(fixes [#38][i38])
Note that PyJKS now relies on PyASN1 0.3.5+ (released 2017-09-16).
[i34]: https://github.com/kurtbrose/pyjks/issues/34
[i38]: https://github.com/kurtbrose/pyjks/issues/38
v17.1.1
-------
*(November 6, 2017)*
Fix packaging with a MANIFEST.in. See #35 for details.
v17.1.0
-------
*(May 15, 2017)*
No API changes with PyJKS itself. This release switches PyJKS to rely
on [pycryptodome](https://github.com/Legrandin/pycryptodome), a
maintained fork of [pycrypto](https://github.com/dlitz/pycrypto). This
upstream dependency has wheels, so installs should be less painless.
v17.0.0
-------
*(March 26, 2017)*
First public release, now featuring documentation and support for
creating and saving JKS keystores. Big thanks to Magnus Watn and
voetsjoeba for making this possible!
* `version` attribute on BksKeyStore and UberKeyStore
* Documentation across several modules
* Factored out common AbstractKeystore superclass
* JKS creation and saving using the new `save()` method of KeyStore
objects. See the
[Examples doc](http://pyjks.readthedocs.io/en/latest/examples.html)
for a demo.
v0.5.1
------
*(August 25, 2016)*
Support more Python versions and runtimes. Python 2.6, 3.3, 3.5, and
PyPy are all now tested and supported. Also, improved error messages
when parsing JKS and BKS.
No security critical changes or bugfixes.
v0.5.0
------
*(June 19, 2016)*
Support more keystore formats and fix a couple issues.
* Support for [Bouncy Castle][bc] BKS and UBER keystores.
* Fix an issue with trailing data. ([#21][i21])
* Added `__version__` and `__version_info__` package-level attributes.
* Created this CHANGELOG.
[bc]: https://www.bouncycastle.org/
[i21]: https://github.com/kurtbrose/pyjks/issues/21
v0.4.0
------
*(May 4, 2016)*
First public beta release, complete with support for Sun JKS/JCE, on
both Python 2 and 3.
pyjks-20.0.0/LICENSE 0000664 0000000 0000000 00000002064 13646725476 0013764 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2013 Kurt Rose
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
pyjks-20.0.0/MANIFEST.in 0000664 0000000 0000000 00000000155 13646725476 0014514 0 ustar 00root root 0000000 0000000 include *.md
include *.txt
include LICENSE
exclude *.yml
exclude tox.ini
prune docs
prune tests
prune tools
pyjks-20.0.0/README.md 0000664 0000000 0000000 00000006744 13646725476 0014247 0 ustar 00root root 0000000 0000000 pyjks
=====
A pure python Java KeyStore file parser, including private/secret key decryption.
Can read JKS, JCEKS, BKS and UBER (BouncyCastle) key stores.
The best way to utilize a certificate stored in a jks file up to this point has been
to use the java keytool command to transform to pkcs12, and then openssl to transform to pem.
This is better:
- no security concerns in passwords going into command line arguments, or unencrypted files being left around
- no dependency on a JVM
## Requirements:
* Python 2.6+ or Python 3.3+
* pyasn1 0.3.5+
* pyasn1_modules 0.0.8+
* javaobj-py3 0.1.4+
* pycryptodomex, if you need to read JCEKS, BKS or UBER keystores
* twofish, if you need to read UBER keystores
## Usage examples:
Reading a JKS or JCEKS keystore and dumping out its contents in the PEM format:
```python
from __future__ import print_function
import sys, base64, textwrap
import jks
def print_pem(der_bytes, type):
print("-----BEGIN %s-----" % type)
print("\r\n".join(textwrap.wrap(base64.b64encode(der_bytes).decode('ascii'), 64)))
print("-----END %s-----" % type)
ks = jks.KeyStore.load("keystore.jks", "XXXXXXXX")
# if any of the keys in the store use a password that is not the same as the store password:
# ks.entries["key1"].decrypt("key_password")
for alias, pk in ks.private_keys.items():
print("Private key: %s" % pk.alias)
if pk.algorithm_oid == jks.util.RSA_ENCRYPTION_OID:
print_pem(pk.pkey, "RSA PRIVATE KEY")
else:
print_pem(pk.pkey_pkcs8, "PRIVATE KEY")
for c in pk.cert_chain:
print_pem(c[1], "CERTIFICATE")
print()
for alias, c in ks.certs.items():
print("Certificate: %s" % c.alias)
print_pem(c.cert, "CERTIFICATE")
print()
for alias, sk in ks.secret_keys.items():
print("Secret key: %s" % sk.alias)
print(" Algorithm: %s" % sk.algorithm)
print(" Key size: %d bits" % sk.key_size)
print(" Key: %s" % "".join("{:02x}".format(b) for b in bytearray(sk.key)))
print()
```
Transforming an encrypted JKS/JCEKS file into an OpenSSL context:
```python
import OpenSSL
import jks
_ASN1 = OpenSSL.crypto.FILETYPE_ASN1
def jksfile2context(jks_file, passphrase, key_alias, key_password=None):
keystore = jks.KeyStore.load(jks_file, passphrase)
pk_entry = keystore.private_keys[key_alias]
# if the key could not be decrypted using the store password, decrypt with a custom password now
if not pk_entry.is_decrypted():
pk_entry.decrypt(key_password)
pkey = OpenSSL.crypto.load_privatekey(_ASN1, pk_entry.pkey)
public_cert = OpenSSL.crypto.load_certificate(_ASN1, pk_entry.cert_chain[0][1])
trusted_certs = [OpenSSL.crypto.load_certificate(_ASN1, cert.cert) for alias, cert in keystore.certs]
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
ctx.use_privatekey(pkey)
ctx.use_certificate(public_cert)
ctx.check_privatekey() # want to know ASAP if there is a problem
cert_store = ctx.get_cert_store()
for cert in trusted_certs:
cert_store.add_cert(cert)
return ctx
```
pyjks-20.0.0/appveyor.yml 0000664 0000000 0000000 00000001274 13646725476 0015351 0 ustar 00root root 0000000 0000000 version: build-{build}-{branch}
environment:
matrix:
# http://www.appveyor.com/docs/installed-software#python lists available
# versions
- PYTHON: "C:\\Python27"
- PYTHON: "C:\\Python27-x64"
- PYTHON: "C:\\Python34"
- PYTHON: "C:\\Python34-x64"
- PYTHON: "C:\\Python35"
- PYTHON: "C:\\Python35-x64"
- PYTHON: "C:\\Python36"
- PYTHON: "C:\\Python36-x64"
init:
- "echo %PYTHON%"
install:
- "set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
- python --version
- python -m pip install -U pip
- python -m pip install -U setuptools
- python -m pip install -U wheel virtualenv tox
build: false
test_script:
- tox -v -e py
pyjks-20.0.0/docs/ 0000775 0000000 0000000 00000000000 13646725476 0013705 5 ustar 00root root 0000000 0000000 pyjks-20.0.0/docs/Makefile 0000664 0000000 0000000 00000016662 13646725476 0015360 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
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help
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 " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " epub3 to make an epub3"
@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)"
@echo " coverage to run coverage check of the documentation (if enabled)"
@echo " dummy to check syntax errors of document sources"
.PHONY: clean
clean:
rm -rf $(BUILDDIR)/*
.PHONY: html
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: dirhtml
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
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."
.PHONY: qthelp
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/PyJKS.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyJKS.qhc"
.PHONY: applehelp
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
.PHONY: devhelp
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/PyJKS"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyJKS"
@echo "# devhelp"
.PHONY: epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: epub3
epub3:
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
@echo
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
.PHONY: latex
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)."
.PHONY: latexpdf
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."
.PHONY: latexpdfja
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."
.PHONY: text
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
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)."
.PHONY: info
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."
.PHONY: gettext
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
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."
.PHONY: doctest
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: coverage
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
.PHONY: xml
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
.PHONY: pseudoxml
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
.PHONY: dummy
dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo
@echo "Build finished. Dummy builder generates no files."
pyjks-20.0.0/docs/bks.rst 0000664 0000000 0000000 00000016337 13646725476 0015230 0 ustar 00root root 0000000 0000000 BouncyCastle Keystores (BKS and UBER)
=====================================
.. py:module:: jks.bks
This module implements readers for keystores created by the BouncyCastle cryptographic provider for Java.
Store types
-----------
Pyjks supports two BouncyCastle store types:
- BKS
- UBER
Neither BKS or JKS/JCEKS stores make any effort to hide how many entries are present in the store, what their aliases are, and what type of key each
entry contains. The keys *inside* each entry are still protected, and the store is protected against tampering via the store password,
but anyone can still see the names and types of keys inside.
UBER keystores are similar to BKS, but they have an additional design goal: protect the store from introspection. This is done by additionally
encrypting the entire keystore using (a key derived from) the store password.
.. autoclass:: BksKeyStore
:members:
:show-inheritance:
:member-order: groupwise
:inherited-members:
.. (Note: Explicit py:attribute definitions are needed here because :inherited-members: does not properly inherit
instance variables at the moment)
.. attribute:: entries
A dictionary of all entries in the keystore, mapped by alias.
.. attribute:: store_type
A string indicating the type of keystore that was loaded. Equals ``bks`` for instances of this class.
.. autoclass:: UberKeyStore
:members:
:show-inheritance:
:member-order: groupwise
:inherited-members:
.. (Note: Explicit py:attribute definitions are needed here because :inherited-members: does not properly inherit
instance variables at the moment)
.. attribute:: entries
A dictionary of all entries in the keystore, mapped by alias.
.. attribute:: store_type
A string indicating the type of keystore that was loaded. Equals ``uber`` for instances of this class.
Entry types
-----------
.. automodule:: jks.bks
:members: KEY_TYPE_PRIVATE, KEY_TYPE_PUBLIC, KEY_TYPE_SECRET
:noindex:
.. autoclass:: BksTrustedCertEntry
:members:
:show-inheritance:
:member-order: bysource
:inherited-members:
.. (Note: Explicit py:attribute definitions are needed here because :inherited-members: does not properly inherit
instance variables at the moment)
.. attribute:: type
A string indicating the type of certificate. Unless in exotic applications, this is usually ``X.509``.
.. attribute:: cert
A byte string containing the actual certificate data. In the case of X.509 certificates, this is the DER-encoded
X.509 representation of the certificate.
.. autoclass:: BksKeyEntry
:members:
:show-inheritance:
:member-order: bysource
:inherited-members:
When :attr:`type` is :data:`KEY_TYPE_PRIVATE`, the following attributes are available:
.. attribute:: pkey
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`~jks.bks.BksKeyStore.loads`.
A byte string containing the value of the ``privateKey`` field of the PKCS#8 ``PrivateKeyInfo`` representation of the private key.
See `RFC 5208, section 5: Private-Key Information Syntax `_.
.. attribute:: pkey_pkcs8
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`~jks.bks.BksKeyStore.loads`.
A byte string containing the DER-encoded PKCS#8 ``PrivateKeyInfo`` representation of the private key.
See `RFC 5208, section 5: Private-Key Information Syntax `_.
.. attribute:: algorithm_oid
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`~jks.bks.BksKeyStore.loads`.
A tuple of integers corresponding to the algorithm OID for which the private key is valid.
Common values include:
- ``(1,2,840,113549,1,1,1)`` (alias ``rsaEncryption``)
- ``(1,2,840,10040,4,1)`` (alias ``id-dsa``).
When :attr:`type` is :data:`KEY_TYPE_PUBLIC`, the following attributes are available:
.. attribute:: public_key
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`~jks.bks.BksKeyStore.loads`.
A byte string containing the value of the ``subjectPublicKey`` field of the X.509 ``SubjectPublicKeyInfo`` representation of the public key.
See `RFC 5280, Appendix A. Pseudo-ASN.1 Structures and OIDs `_.
.. attribute:: public_key_info
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`~jks.bks.BksKeyStore.loads`.
A byte string containing the DER-encoded X.509 ``SubjectPublicKeyInfo`` representation of the public key.
See `RFC 5280, Appendix A. Pseudo-ASN.1 Structures and OIDs `_.
.. attribute:: algorithm_oid
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`~jks.bks.BksKeyStore.loads`.
A tuple of integers corresponding to the algorithm OID for which the public key is valid.
Common values include:
- ``(1,2,840,113549,1,1,1)`` (alias ``rsaEncryption``)
- ``(1,2,840,10040,4,1)`` (alias ``id-dsa``).
When :attr:`type` is :data:`KEY_TYPE_SECRET`, the following attributes are available:
.. attribute:: key
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`~jks.bks.BksKeyStore.loads`.
A byte string containing the raw secret key.
.. attribute:: key_size
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`~jks.bks.BksKeyStore.loads`.
An integer containing the size of the key, in bits. For DES and 3DES keys, the sizes 64 bits resp. 192 bits are returned.
.. autoclass:: BksSecretKeyEntry
:members:
:show-inheritance:
:member-order: bysource
:inherited-members:
.. autoclass:: BksSealedKeyEntry
:members:
:show-inheritance:
:member-order: bysource
:inherited-members:
pyjks-20.0.0/docs/concepts.rst 0000664 0000000 0000000 00000004167 13646725476 0016265 0 ustar 00root root 0000000 0000000 Concepts
========
Store and entry passwords
-------------------------
Java keystores usually involve two kinds of passwords:
- Passwords to protect individual key entries
- A password to protect the integrity of the keystore as a whole
These passwords serve different purposes: the individual key passwords serve as secret material for encrypting the
entries with a PBE algorithm (Password-Based Encryption). The store password is typically used to detect tampering of the
store by using it as part of the input to a cryptographic hash calculation or as a key for a MAC.
In the general case, each entry in the store can have a different password associated with it, with an additional final
password being used for the keystore integrity check. To reduce the amount of passwords that needs to be kept track of
though, it is common for a single password to be used for both the store integrity as well as all individual key entries.
To support the common case where key entries are protected using the store password, the ``load`` and ``loads`` class functions
exposed by the different supported store types in pyjks contain a ``try_decrypt_keys`` keyword argument.
If set to ``True``, the function will automatically try to decrypt each key entry it encounters using the store password.
Any entry that fails to decrypt with the store password must therefore have been stored using a different password,
and is left alone for the user to manually call ``decrypt()`` on afterwards.
Store types
-----------
JKS:
- Key protection algorithm: proprietary JavaSoft algorithm (1.3.6.1.4.1.42.2.17.1.1)
- Store signature algorithm: SHA-1 hash
JCEKS:
- Key protection algorithm: proprietary PBE_WITH_MD5_AND_DES3_CBC (1.3.6.1.4.1.42.2.19.1)
- Store signature algorithm: SHA-1 hash
BKS:
- Key protection algorithm: PBEWithSHAAnd3KeyTripleDESCBC (1.2.840.113549.1.12.1.3)
- Store signature algorithm: HMAC-SHA1
UBER:
- Key protection algorithm: PBEWithSHAAnd3KeyTripleDESCBC (1.2.840.113549.1.12.1.3)
- Store signature algorithm: SHA-1 hash
- Store encryption algorithm: PBEWithSHAAndTwofishCBC (unknown OID, proprietary?)
pyjks-20.0.0/docs/conf.py 0000664 0000000 0000000 00000027267 13646725476 0015222 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
#
# PyJKS documentation build configuration file, created by
# sphinx-quickstart on Fri Jun 3 00:40:21 2016.
#
# 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.
# 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.
#
import os
import sys
sys.path.insert(1, os.path.abspath('..'))
from jks import __version__
# -- General configuration ------------------------------------------------
primary_domain = 'py'
# 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.intersphinx',
'sphinx.ext.ifconfig',
'sphinx.ext.viewcode',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
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 = u'PyJKS'
copyright = u'2017, Kurt Rose and contributors'
author = u'Kurt Rose and contributors'
# 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.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
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.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# 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
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = 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 = 'sphinx_rtd_theme'
# 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.
#
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents.
# " v documentation" by default.
#
# html_title = u'PyJKS v0.4.0'
# 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 (relative to this directory) to use as a 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 None, a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
#
# html_last_updated_fmt = None
# 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
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
#
# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# 'ja' uses this config value.
# 'zh' user can custom change `jieba` dictionary path.
#
# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'PyJKSdoc'
# -- 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': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# 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 = [
(master_doc, 'PyJKS.tex', u'PyJKS Documentation',
u'Kurt Rose and contributors', '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 = [
(master_doc, 'pyjks', u'PyJKS Documentation',
[author], 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 = [
(master_doc, 'PyJKS', u'PyJKS Documentation',
author, 'PyJKS', '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
# -- Options for Epub output ----------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
epub_author = author
epub_publisher = author
epub_copyright = copyright
# The basename for the epub file. It defaults to the project name.
# epub_basename = project
# The HTML theme for the epub output. Since the default themes are not
# optimized for small screen space, using the same theme for HTML and epub
# output is usually not wise. This defaults to 'epub', a theme designed to save
# visual space.
#
# epub_theme = 'epub'
# The language of the text. It defaults to the language option
# or 'en' if the language is not set.
#
# epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
# epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
#
# epub_cover = ()
# A sequence of (type, uri, title) tuples for the guide element of content.opf.
#
# epub_guide = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#
# epub_pre_files = []
# HTML files that should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#
# epub_post_files = []
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# The depth of the table of contents in toc.ncx.
#
# epub_tocdepth = 3
# Allow duplicate toc entries.
#
# epub_tocdup = True
# Choose between 'default' and 'includehidden'.
#
# epub_tocscope = 'default'
# Fix unsupported image types using the Pillow.
#
# epub_fix_images = False
# Scale large images.
#
# epub_max_image_width = 0
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#
# epub_show_urls = 'inline'
# If false, no index is generated.
#
# epub_use_index = True
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}
pyjks-20.0.0/docs/examples.rst 0000664 0000000 0000000 00000007135 13646725476 0016263 0 ustar 00root root 0000000 0000000 Examples
========
Building an OpenSSL context using a JKS through PyJKS::
import jks
import OpenSSL
ASN1 = OpenSSL.crypto.FILETYPE_ASN1
def jksfile2context(jks_file, passphrase, key_alias, key_password=None):
keystore = jks.KeyStore.load(jks_file, passphrase)
pk_entry = keystore.private_keys[key_alias]
# if the key could not be decrypted using the store password,
# decrypt with a custom password now
if not pk_entry.is_decrypted():
pk_entry.decrypt(key_password)
pkey = OpenSSL.crypto.load_privatekey(ASN1, pk_entry.pkey)
public_cert = OpenSSL.crypto.load_certificate(ASN1, pk_entry.cert_chain[0][1])
trusted_certs = [OpenSSL.crypto.load_certificate(ASN1, cert.cert)
for alias, cert in keystore.certs]
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
ctx.use_privatekey(pkey)
ctx.use_certificate(public_cert)
ctx.check_privatekey() # want to know ASAP if there is a problem
cert_store = ctx.get_cert_store()
for cert in trusted_certs:
cert_store.add_cert(cert)
return ctx
Reading a JKS or JCEKS keystore and dumping out its contents in the PEM format::
from __future__ import print_function
import sys, base64, textwrap
import jks
def print_pem(der_bytes, type):
print("-----BEGIN %s-----" % type)
print("\r\n".join(textwrap.wrap(base64.b64encode(der_bytes).decode('ascii'), 64)))
print("-----END %s-----" % type)
ks = jks.KeyStore.load("keystore.jks", "XXXXXXXX")
# if any of the keys in the store use a password that is not the same as the store password:
# ks.entries["key1"].decrypt("key_password")
for alias, pk in ks.private_keys.items():
print("Private key: %s" % pk.alias)
if pk.algorithm_oid == jks.util.RSA_ENCRYPTION_OID:
print_pem(pk.pkey, "RSA PRIVATE KEY")
else:
print_pem(pk.pkey_pkcs8, "PRIVATE KEY")
for c in pk.cert_chain:
print_pem(c[1], "CERTIFICATE")
print()
for alias, c in ks.certs.items():
print("Certificate: %s" % c.alias)
print_pem(c.cert, "CERTIFICATE")
print()
for alias, sk in ks.secret_keys.items():
print("Secret key: %s" % sk.alias)
print(" Algorithm: %s" % sk.algorithm)
print(" Key size: %d bits" % sk.key_size)
print(" Key: %s" % "".join("{:02x}".format(b) for b in bytearray(sk.key)))
print()
Generating a basic self signed certificate with OpenSSL and saving it in a jks keystore::
import OpenSSL
import jks
# generate key
key = OpenSSL.crypto.PKey()
key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
# generate a self signed certificate
cert = OpenSSL.crypto.X509()
cert.get_subject().CN = 'my.server.example.com'
cert.set_serial_number(473289472)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(365*24*60*60)
cert.set_issuer(cert.get_subject())
cert.set_pubkey(key)
cert.sign(key, 'sha256')
# dumping the key and cert to ASN1
dumped_cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, cert)
dumped_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_ASN1, key)
# creating a private key entry
pke = jks.PrivateKeyEntry.new('self signed cert', [dumped_cert], dumped_key, 'rsa_raw')
# if we want the private key entry to have a unique password, we can encrypt it beforehand
# if it is not ecrypted when saved, it will be encrypted with the same password as the keystore
#pke.encrypt('my_private_key_password')
# creating a jks keystore with the private key, and saving it
keystore = jks.KeyStore.new('jks', [pke])
keystore.save('./my_keystore.jks', 'my_password')
pyjks-20.0.0/docs/exceptions.rst 0000664 0000000 0000000 00000001365 13646725476 0016625 0 ustar 00root root 0000000 0000000 Exceptions
==========
All exceptions related to keystore loading or parsing derive from a common superclass type :class:`~jks.util.KeystoreException`.
Exception types
---------------
.. automodule:: jks.util
:show-inheritance:
:members: KeystoreException, KeystoreSignatureException, DuplicateAliasException, NotYetDecryptedException,
BadKeystoreFormatException, BadDataLengthException, BadPaddingException, BadHashCheckException,
DecryptionFailureException, UnsupportedKeystoreVersionException, UnexpectedJavaTypeException,
UnexpectedAlgorithmException, UnexpectedKeyEncodingException, UnsupportedKeystoreTypeException,
UnsupportedKeystoreEntryTypeException, UnsupportedKeyFormatException
pyjks-20.0.0/docs/index.rst 0000664 0000000 0000000 00000003240 13646725476 0015545 0 ustar 00root root 0000000 0000000 .. PyJKS documentation master file, created by
sphinx-quickstart on Fri Jun 3 00:40:21 2016.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
PyJKS
=====
PyJKS is *the* pure-Python library for Java KeyStore (JKS) parsing,
decryption, and manipulation. PyJKS supports vanilla JKS, JCEKS, BKS,
and UBER (BouncyCastle) keystore formats.
In the past, Python projects relied on external tools (*keytool*),
intermediate formats (*PKCS12* and *PEM*), and the JVM to work with
encrypted material locked within JKS files. Now, PyJKS changes that.
Examples
--------
See the :doc:`examples` page for usage examples of PyJKS.
Installation
------------
You can install ``pyjks`` with ``pip``:
.. code-block:: console
$ pip install pyjks
If you receive an error like:
.. code-block:: console
error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": https://visualstudio.microsoft.com/downloads/
on Windows you will need to download the Visual C++ build tools by visiting https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16
Save the file, then run it. Choose "Workloads" tab, then select the "C++ build tools". Under the "Optional" installed items, be certain to select all of ``MSVC vxxx - VS 2019 C++ build tools``, ``Windows 10 SDK`` (latest version), and ``C++/CLI support for build tools``. Reboot, then run the ``pip`` command again.
Contents:
---------
.. toctree::
:maxdepth: 2
examples
concepts
jks
bks
exceptions
Indices and tables
------------------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
pyjks-20.0.0/docs/jks.rst 0000664 0000000 0000000 00000010234 13646725476 0015226 0 ustar 00root root 0000000 0000000 JKS and JCEKS keystores
=======================
.. py:module:: jks.jks
Background
----------
The JKS keystore format is the format that originally shipped with Java.
It is implemented by the traditional "Sun" cryptography provider.
JCEKS is an improved keystore format introduced with the Java Cryptography Extension (JCE).
It is implemented by the SunJCE cryptography provider.
JCEKS keystores improve upon JKS keystores in 2 ways:
- A stronger key protection algorithm is used
- They allow for arbitrary (symmetric) secret keys to be stored (e.g. AES, DES, etc.)
Store types
-----------
.. autoclass:: KeyStore
:members:
:show-inheritance:
:member-order: groupwise
:inherited-members:
.. (Note: Explicit py:attribute definitions are needed here because :inherited-members: does not properly inherit
instance variables at the moment)
.. attribute:: entries
A dictionary of all entries in the keystore, mapped by alias.
.. attribute:: store_type
A string indicating the type of keystore that was loaded. Can be one of ``jks``, ``jceks``.
Entry types
-----------
.. autoclass:: TrustedCertEntry
:members:
:show-inheritance:
:member-order: groupwise
:inherited-members:
.. autoclass:: PrivateKeyEntry
:members:
:show-inheritance:
:member-order: bysource
:inherited-members:
.. attribute:: pkey
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`jks.jks.KeyStore.loads`.
A byte string containing the value of the ``privateKey`` field of the PKCS#8 ``PrivateKeyInfo`` representation of the private key.
See `RFC 5208, section 5: Private-Key Information Syntax `_.
.. attribute:: pkey_pkcs8
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`jks.jks.KeyStore.loads`.
A byte string containing the DER-encoded PKCS#8 ``PrivateKeyInfo`` representation of the private key.
See `RFC 5208, section 5: Private-Key Information Syntax `_.
.. attribute:: algorithm_oid
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`jks.jks.KeyStore.loads`.
A tuple of integers corresponding to the algorithm OID for which the private key is valid.
Common values include:
- ``(1,2,840,113549,1,1,1)`` (alias ``rsaEncryption``)
- ``(1,2,840,10040,4,1)`` (alias ``id-dsa``).
.. autoclass:: SecretKeyEntry
:members:
:show-inheritance:
:member-order: bysource
:inherited-members:
.. attribute:: algorithm
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`jks.jks.KeyStore.loads`.
A string containing the name of the algorithm for which the key is valid, as known to the Java cryptography provider
that supplied the corresponding SecretKey object.
.. attribute:: key
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`jks.jks.KeyStore.loads`.
A byte string containing the raw secret key.
.. attribute:: key_size
.. note:: Only accessible after a call to :func:`decrypt`; until then, accessing this attribute will raise
a :class:`~jks.util.NotYetDecryptedException`. See also ``try_decrypt_keys`` on :meth:`jks.jks.KeyStore.loads`.
An integer containing the size of the key, in bits. For DES and 3DES keys, the sizes 64 bits resp. 192 bits are returned.
pyjks-20.0.0/docs/make.bat 0000664 0000000 0000000 00000017062 13646725476 0015320 0 ustar 00root root 0000000 0000000 @ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
: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. epub3 to make an epub3
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over 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
echo. coverage to run coverage check of the documentation if enabled
echo. dummy to check syntax errors of document sources
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
REM Check if sphinx-build is available and fallback to Python version if any
%SPHINXBUILD% 1>NUL 2>NUL
if errorlevel 9009 goto sphinx_python
goto sphinx_ok
:sphinx_python
set SPHINXBUILD=python -m sphinx.__init__
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
:sphinx_ok
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyJKS.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyJKS.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "epub3" (
%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
if "%1" == "coverage" (
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
if errorlevel 1 exit /b 1
echo.
echo.Testing of coverage in the sources finished, look at the ^
results in %BUILDDIR%/coverage/python.txt.
goto end
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
if "%1" == "dummy" (
%SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
if errorlevel 1 exit /b 1
echo.
echo.Build finished. Dummy builder generates no files.
goto end
)
:end
pyjks-20.0.0/jks/ 0000775 0000000 0000000 00000000000 13646725476 0013544 5 ustar 00root root 0000000 0000000 pyjks-20.0.0/jks/__init__.py 0000664 0000000 0000000 00000000125 13646725476 0015653 0 ustar 00root root 0000000 0000000 from .jks import *
from .jks import __version__, __version_info__
from .bks import *
pyjks-20.0.0/jks/bks.py 0000664 0000000 0000000 00000055413 13646725476 0014705 0 ustar 00root root 0000000 0000000 # vim: set et ai ts=4 sts=4 sw=4:
import struct
import hashlib
from pyasn1.codec.ber import decoder
from pyasn1_modules import rfc5208, rfc2459
from Cryptodome.Hash import HMAC, SHA
from .util import *
from .jks import KeyStore, TrustedCertEntry
from . import rfc7292
ENTRY_TYPE_CERTIFICATE = 1
ENTRY_TYPE_KEY = 2 # plaintext key entry as would otherwise be stored inside a sealed entry (type 4); no longer supported at the time of writing (BC 1.54)
ENTRY_TYPE_SECRET = 3 # for keys that were added to the store in already-protected form; can be arbitrary data
ENTRY_TYPE_SEALED = 4 # for keys that were protected by the BC keystore implementation upon adding
KEY_TYPE_PRIVATE = 0 #: Type indicator for private keys in :class:`BksKeyEntry`.
KEY_TYPE_PUBLIC = 1 #: Type indicator for public keys in :class:`BksKeyEntry`.
KEY_TYPE_SECRET = 2 #: Type indicator for secret keys in :class:`BksKeyEntry`. Indicates a key for use with a symmetric encryption algorithm.
class AbstractBksEntry(AbstractKeystoreEntry):
"""Abstract superclass for BKS keystore entry types"""
def __init__(self, **kwargs):
super(AbstractBksEntry, self).__init__(**kwargs)
# All BKS entries can carry an arbitrary number of associated certificates
self.cert_chain = kwargs.get("cert_chain", [])
self._encrypted = kwargs.get("encrypted")
class BksTrustedCertEntry(TrustedCertEntry):
"""Represents a trusted certificate entry in a BKS or UBER keystore."""
pass # identical
class BksKeyEntry(AbstractBksEntry):
"""
Represents a non-encrypted cryptographic key (public, private or secret) stored in a BKS keystore.
May exceptionally appear as a top-level entry type in (very) old keystores, but you are most likely
to encounter these as the nested object inside a :class:`BksSealedKeyEntry` once decrypted.
"""
def __init__(self, type, format, algorithm, encoded, **kwargs):
super(BksKeyEntry, self).__init__(**kwargs)
self.type = type
"""An integer indicating the type of key: one of :const:`KEY_TYPE_PRIVATE`, :const:`KEY_TYPE_PUBLIC`, :const:`KEY_TYPE_SECRET`."""
self.format = format
"""A string indicating the format or encoding in which the key is stored. One of: ``PKCS8``, ``PKCS#8``, ``X.509``, ``X509``, ``RAW``."""
self.algorithm = algorithm
"""A string indicating the algorithm for which the key is valid."""
self.encoded = encoded
"""A byte string containing the key, formatted as indicated by the :attr:`format` attribute."""
if self.type == KEY_TYPE_PRIVATE:
if self.format not in ["PKCS8", "PKCS#8"]:
raise UnexpectedKeyEncodingException("Unexpected encoding for private key entry: '%s'" % self.format)
# self.encoded is a PKCS#8 PrivateKeyInfo
private_key_info = decoder.decode(self.encoded, asn1Spec=rfc5208.PrivateKeyInfo())[0]
self.pkey_pkcs8 = self.encoded
self.pkey = private_key_info['privateKey'].asOctets()
self.algorithm_oid = private_key_info['privateKeyAlgorithm']['algorithm'].asTuple()
elif self.type == KEY_TYPE_PUBLIC:
if self.format not in ["X.509", "X509"]:
raise UnexpectedKeyEncodingException("Unexpected encoding for public key entry: '%s'" % self.format)
# self.encoded is an X.509 SubjectPublicKeyInfo
spki = decoder.decode(self.encoded, asn1Spec=rfc2459.SubjectPublicKeyInfo())[0]
self.public_key_info = self.encoded
self.public_key = bitstring_to_bytes(spki['subjectPublicKey'])
self.algorithm_oid = spki['algorithm']['algorithm'].asTuple()
elif self.type == KEY_TYPE_SECRET:
if self.format != "RAW":
raise UnexpectedKeyEncodingException("Unexpected encoding for raw key entry: '%s'" % self.format)
# self.encoded is an unwrapped/raw cryptographic key
self.key = encoded
self.key_size = len(encoded)*8
else:
raise UnexpectedKeyEncodingException("Key format '%s' not recognized" % self.format)
def is_decrypted(self):
"""Always returns ``True`` for this entry type."""
return True
def decrypt(self, key_password):
"""Does nothing for this entry type; these entries are stored in non-encrypted form."""
pass
@classmethod
def type2str(cls, t):
"""
Returns a string representation of the given key type. Returns one of ``PRIVATE``, ``PUBLIC`` or ``SECRET``, or ``None``
if no such key type is known.
:param int t: Key type constant. One of :const:`KEY_TYPE_PRIVATE`, :const:`KEY_TYPE_PUBLIC`, :const:`KEY_TYPE_SECRET`.
"""
if t == KEY_TYPE_PRIVATE:
return "PRIVATE"
elif t == KEY_TYPE_PUBLIC:
return "PUBLIC"
elif t == KEY_TYPE_SECRET:
return "SECRET"
return None
class BksSecretKeyEntry(AbstractBksEntry): # TODO: consider renaming this to SecretValueEntry, since it's arbitrary secret data
"""
Conceptually similar to, but not to be confused with, :class:`BksKeyEntry` objects of type :const:`KEY_TYPE_SECRET`:
- :class:`BksSecretKeyEntry` objects store the result of arbitrary user-supplied byte[]s, which, per the Java Keystore SPI, keystores are
obligated to assume have already been protected by the user in some unspecified way. Because of this assumption, no password is
provided for these entries when adding them to the keystore, and keystores are thus forced to store these bytes as-is.
Produced by a call to ``KeyStore.setKeyEntry(String alias, byte[] key, Certificate[] chain)`` call.
The bouncycastle project appears to have completely abandoned these entry types well over a decade ago now, and it is no
longer possible to retrieve these entries through the Java APIs in any (remotely) recent BC version.
- :class:`BksKeyEntry` objects of type :const:`KEY_TYPE_SECRET` store the result of a getEncoded() call on proper Java objects of type SecretKey.
Produced by a call to ``KeyStore.setKeyEntry(String alias, Key key, char[] password, Certificate[] chain)``.
The difference here is that the KeyStore implementation knows it's getting a proper (Secret)Key Java object, and can decide
for itself how to store it given the password supplied by the user. I.e., in this version of setKeyEntry it is left up to
the keystore implementation to encode and protect the supplied Key object, instead of in advance by the user.
"""
def __init__(self, **kwargs):
super(BksSecretKeyEntry, self).__init__(**kwargs)
self.key = self._encrypted
"""A byte string containing the secret key/value."""
def is_decrypted(self):
"""Always returns ``True`` for this entry type."""
return True
def decrypt(self, key_password):
"""Does nothing for this entry type; these entries stored arbitrary user-supplied data, unclear how to decrypt (may not be encrypted at all)."""
pass
class BksSealedKeyEntry(AbstractBksEntry):
"""
PBEWithSHAAnd3-KeyTripleDES-CBC-encrypted wrapper around a :class:`BksKeyEntry`. The contained key type is unknown until decrypted.
Once decrypted, objects of this type can be used in the same way as :class:`BksKeyEntry`: attribute accesses are forwarded
to the wrapped :class:`BksKeyEntry` object.
"""
def __init__(self, **kwargs):
super(BksSealedKeyEntry, self).__init__(**kwargs)
self._nested = None # nested BksKeyEntry once decrypted
def __getattr__(self, name):
if not self.is_decrypted():
raise NotYetDecryptedException("Cannot access attribute '%s'; entry not yet decrypted, call decrypt() with the correct password first" % name)
# if it's an attribute that exists here, return it; otherwise forward the request to the nested entry
if "_"+name in self.__dict__:
return self.__dict__["_"+name]
else:
return getattr(self._nested, name)
def is_decrypted(self):
return (not self._encrypted)
def decrypt(self, key_password):
if self.is_decrypted():
return
pos = 0
data = self._encrypted
salt, pos = BksKeyStore._read_data(data, pos)
iteration_count = b4.unpack_from(data, pos)[0]; pos += 4
encrypted_blob = data[pos:]
# The intention of the BKS entry decryption routine in BcKeyStoreSpi.StoreEntry.getObject(char[] password) appears to be:
# - try to decrypt with "PBEWithSHAAnd3-KeyTripleDES-CBC" first (1.2.840.113549.1.12.1.3);
# - if that fails, try again with "BrokenPBEWithSHAAnd3-KeyTripleDES-CBC";
# - if that still fails, try again with "OldPBEWithSHAAnd3-KeyTripleDES-CBC"
# - give up with an UnrecoverableKeyException
#
# However, at the time of writing (bcprov-jdk15on-1.53 and 1.54), the second and third cases can never successfully execute
# because their implementation requests non-existent SecretKeyFactory objects for the Broken/Old algorithm names.
# Inquiry through the BC developer mailing list tells us that this is indeed old functionality that has been retired long ago
# and is not expected to be operational anymore, and should be cleaned up.
#
# So in practice, the real behaviour is:
# - try to decrypt with "PBEWithSHAAnd3-KeyTripleDES-CBC" (1.2.840.113549.1.12.1.3);
# - give up with an UnrecoverableKeyException
#
# Implementation classes:
# PBEWithSHAAnd3-KeyTripleDES-CBC -> org.bouncycastle.jcajce.provider.symmetric.DESede$PBEWithSHAAndDES3Key
# BrokenPBEWithSHAAnd3-KeyTripleDES-CBC -> org.bouncycastle.jce.provider.BrokenJCEBlockCipher$BrokePBEWithSHAAndDES3Key
# OldPBEWithSHAAnd3-KeyTripleDES-CBC -> org.bouncycastle.jce.provider.BrokenJCEBlockCipher$OldPBEWithSHAAndDES3Key
#
try:
decrypted = rfc7292.decrypt_PBEWithSHAAnd3KeyTripleDESCBC(encrypted_blob, key_password, salt, iteration_count)
except BadDataLengthException:
raise BadKeystoreFormatException("Bad BKS entry format: %s" % str(e))
except BadPaddingException:
raise DecryptionFailureException("Failed to decrypt data for key '%s'; wrong password?" % self.alias)
# the plaintext content of a SealedEntry is a KeyEntry
key_entry, dummy = BksKeyStore._read_bks_key(decrypted, 0, self.store_type)
key_entry.store_type = self.store_type
key_entry.cert_chain = self.cert_chain
key_entry.alias = self.alias
key_entry.timestamp = self.timestamp
self._nested = key_entry
self._encrypted = None
decrypt.__doc__ = AbstractBksEntry.decrypt.__doc__
is_decrypted.__doc__ = AbstractBksEntry.is_decrypted.__doc__
class BksKeyStore(AbstractKeystore):
"""
Bouncycastle "BKS" keystore parser. Supports both the current V2 and old V1 formats.
"""
def __init__(self, store_type, entries, version=2):
super(BksKeyStore, self).__init__(store_type, entries)
self.version = version
"""Version of the keystore format, if loaded."""
@property
def certs(self):
"""A subset of the :attr:`entries` dictionary, filtered down to only
those entries of type :class:`BksTrustedCertEntry`."""
return dict([(a, e) for a, e in self.entries.items()
if isinstance(e, BksTrustedCertEntry)])
@property
def secret_keys(self):
"""A subset of the :attr:`entries` dictionary, filtered down to only
those entries of type :class:`BksSecretKeyEntry`."""
return dict([(a, e) for a, e in self.entries.items()
if isinstance(e, BksSecretKeyEntry)])
@property
def sealed_keys(self):
"""A subset of the :attr:`entries` dictionary, filtered down to only
those entries of type :class:`BksSealedKeyEntry`."""
return dict([(a, e) for a, e in self.entries.items()
if isinstance(e, BksSealedKeyEntry)])
@property
def plain_keys(self):
"""A subset of the :attr:`entries` dictionary, filtered down to only
those entries of type :class:`BksKeyEntry`."""
return dict([(a, e) for a, e in self.entries.items()
if isinstance(e, BksKeyEntry)])
@classmethod
def loads(cls, data, store_password, try_decrypt_keys=True):
"""
See :meth:`jks.jks.KeyStore.loads`.
:param bytes data: Byte string representation of the keystore to be loaded.
:param str password: Keystore password string
:param bool try_decrypt_keys: Whether to automatically try to decrypt any encountered key entries using the same password
as the keystore password.
:returns: A loaded :class:`BksKeyStore` instance, if the keystore could be successfully parsed and the supplied store password is correct.
If the ``try_decrypt_keys`` parameters was set to ``True``, any keys that could be successfully decrypted using the
store password have already been decrypted; otherwise, no atttempt to decrypt any key entries is made.
:raises BadKeystoreFormatException: If the keystore is malformed in some way
:raises UnsupportedKeystoreVersionException: If the keystore contains an unknown format version number
:raises KeystoreSignatureException: If the keystore signature could not be verified using the supplied store password
:raises DuplicateAliasException: If the keystore contains duplicate aliases
"""
try:
pos = 0
version = b4.unpack_from(data, pos)[0]; pos += 4
if version not in [1,2]:
raise UnsupportedKeystoreVersionException("Unsupported BKS keystore version; only V1 and V2 supported, found v"+repr(version))
salt, pos = cls._read_data(data, pos)
iteration_count = b4.unpack_from(data, pos)[0]; pos += 4
store_type = "bks"
entries, size = cls._load_bks_entries(data[pos:], store_type, store_password, try_decrypt_keys=try_decrypt_keys)
hmac_fn = hashlib.sha1
hmac_digest_size = hmac_fn().digest_size
hmac_key_size = hmac_digest_size*8 if version != 1 else hmac_digest_size
hmac_key = rfc7292.derive_key(hmac_fn, rfc7292.PURPOSE_MAC_MATERIAL, store_password, salt, iteration_count, hmac_key_size//8)
store_data = data[pos:pos+size]
store_hmac = data[pos+size:pos+size+hmac_digest_size]
if len(store_hmac) != hmac_digest_size:
raise BadKeystoreFormatException("Bad HMAC size; found %d bytes, expected %d bytes" % (len(store_hmac), hmac_digest_size))
hmac = HMAC.new(hmac_key, digestmod=SHA)
hmac.update(store_data)
computed_hmac = hmac.digest()
if store_hmac != computed_hmac:
raise KeystoreSignatureException("Hash mismatch; incorrect keystore password?")
return cls(store_type, entries, version=version)
except struct.error as e:
raise BadKeystoreFormatException(e)
@classmethod
def _load_bks_entries(cls, data, store_type, store_password, try_decrypt_keys=False):
entries = {}
pos = 0
while pos < len(data):
_type = b1.unpack_from(data, pos)[0]; pos += 1
if _type == 0:
break
alias, pos = cls._read_utf(data, pos, kind="entry alias")
timestamp = int(b8.unpack_from(data, pos)[0]); pos += 8
chain_length = b4.unpack_from(data, pos)[0]; pos += 4
cert_chain = []
for n in range(chain_length):
entry, pos = cls._read_bks_cert(data, pos, store_type)
cert_chain.append(entry)
if _type == 1: # certificate
entry, pos = cls._read_bks_cert(data, pos, store_type)
elif _type == 2: # key: plaintext key entry, i.e. same as sealed key but without the PBEWithSHAAnd3KeyTripleDESCBC layer
entry, pos = cls._read_bks_key(data, pos, store_type)
elif _type == 3: # secret key: opaque arbitrary data blob, stored as-is by the keystore; can be anything (assumed to already be protected when supplied).
entry, pos = cls._read_bks_secret(data, pos, store_type)
elif _type == 4: # sealed key; a well-formatted certificate, private key or public key, encrypted by the BKS implementation with a standard algorithm at save time
entry, pos = cls._read_bks_sealed(data, pos, store_type)
else:
raise BadKeystoreFormatException("Unexpected keystore entry type %d", tag)
entry.alias = alias
entry.timestamp = timestamp
entry.cert_chain = cert_chain
if try_decrypt_keys:
try:
entry.decrypt(store_password)
except DecryptionFailureException:
pass # ok, let user call .decrypt() manually afterwards
if alias in entries:
raise DuplicateAliasException("Found duplicate alias '%s'" % alias)
entries[alias] = entry
return (entries, pos)
@classmethod
def _read_bks_cert(cls, data, pos, store_type):
cert_type, pos = cls._read_utf(data, pos, kind="certificate type")
cert_data, pos = cls._read_data(data, pos)
entry = BksTrustedCertEntry(type=cert_type, cert=cert_data, store_type=store_type)
return entry, pos
@classmethod
def _read_bks_key(cls, data, pos, store_type):
"""Given a data stream, attempt to parse a stored BKS key entry at the given position, and return it as a BksKeyEntry."""
key_type = b1.unpack_from(data, pos)[0]; pos += 1
key_format, pos = BksKeyStore._read_utf(data, pos, kind="key format")
key_algorithm, pos = BksKeyStore._read_utf(data, pos, kind="key algorithm")
key_enc, pos = BksKeyStore._read_data(data, pos)
entry = BksKeyEntry(key_type, key_format, key_algorithm, key_enc, store_type=store_type)
return entry, pos
@classmethod
def _read_bks_secret(cls, data, pos, store_type):
secret_data, pos = cls._read_data(data, pos)
entry = BksSecretKeyEntry(store_type=store_type, encrypted=secret_data)
return entry, pos
@classmethod
def _read_bks_sealed(cls, data, pos, store_type):
sealed_data, pos = cls._read_data(data, pos)
entry = BksSealedKeyEntry(store_type=store_type, encrypted=sealed_data)
return entry, pos
class UberKeyStore(BksKeyStore):
"""
BouncyCastle "UBER" keystore format parser.
"""
@classmethod
def loads(cls, data, store_password, try_decrypt_keys=True):
"""
See :meth:`jks.jks.KeyStore.loads`.
:param bytes data: Byte string representation of the keystore to be loaded.
:param str password: Keystore password string
:param bool try_decrypt_keys: Whether to automatically try to decrypt any encountered key entries using the same password
as the keystore password.
:returns: A loaded :class:`UberKeyStore` instance, if the keystore could be successfully parsed and the supplied store password is correct.
If the ``try_decrypt_keys`` parameters was set to ``True``, any keys that could be successfully decrypted using the
store password have already been decrypted; otherwise, no atttempt to decrypt any key entries is made.
:raises BadKeystoreFormatException: If the keystore is malformed in some way
:raises UnsupportedKeystoreVersionException: If the keystore contains an unknown format version number
:raises KeystoreSignatureException: If the keystore signature could not be verified using the supplied store password
:raises DecryptionFailureException: If the keystore contents could not be decrypted using the supplied store password
:raises DuplicateAliasException: If the keystore contains duplicate aliases
"""
# Uber keystores contain the same entry data as BKS keystores, except they wrap it differently:
# BKS = BKS_store || HMAC-SHA1(BKS_store)
# UBER = PBEWithSHAAndTwofish-CBC(BKS_store || SHA1(BKS_store))
#
# where BKS_store represents the entry format shared by both keystore types.
#
# The Twofish key size is 256 bits, the PBE key derivation scheme is that as outlined by PKCS#12 (RFC 7292),
# and the padding scheme for the Twofish cipher is PKCS#7.
try:
pos = 0
version = b4.unpack_from(data, pos)[0]; pos += 4
if version != 1:
raise UnsupportedKeystoreVersionException('Unsupported UBER keystore version; only v1 supported, found v'+repr(version))
salt, pos = cls._read_data(data, pos)
iteration_count = b4.unpack_from(data, pos)[0]; pos += 4
encrypted_bks_store = data[pos:]
try:
decrypted = rfc7292.decrypt_PBEWithSHAAndTwofishCBC(encrypted_bks_store, store_password, salt, iteration_count)
except BadDataLengthException as e:
raise BadKeystoreFormatException("Bad UBER keystore format: %s" % str(e))
except BadPaddingException as e:
raise DecryptionFailureException("Failed to decrypt UBER keystore: bad password?")
# Note: we can assume that the hash must be present at the last 20 bytes of the decrypted data (i.e. without first
# parsing through to see where the entry data actually ends), because valid UBER keystores generators should not put
# any trailing bytes after the hash prior to encrypting.
hash_fn = hashlib.sha1
hash_digest_size = hash_fn().digest_size
bks_store = decrypted[:-hash_digest_size]
bks_hash = decrypted[-hash_digest_size:]
if len(bks_hash) != hash_digest_size:
raise BadKeystoreFormatException("Insufficient signature bytes; found %d bytes, expected %d bytes" % (len(bks_hash), hash_digest_size))
if hash_fn(bks_store).digest() != bks_hash:
raise KeystoreSignatureException("Hash mismatch; incorrect keystore password?")
store_type = "uber"
entries, size = cls._load_bks_entries(bks_store, store_type, store_password, try_decrypt_keys=try_decrypt_keys)
return cls(store_type, entries, version=version)
except struct.error as e:
raise BadKeystoreFormatException(e)
def __init__(self, store_type, entries, version=1):
super(UberKeyStore, self).__init__(store_type, entries, version=version)
self.version = version # only here so Sphinx documents the field
"""Version of the keystore format, if loaded."""
pyjks-20.0.0/jks/jks.py 0000664 0000000 0000000 00000102651 13646725476 0014712 0 ustar 00root root 0000000 0000000 # vim: set et ai ts=4 sts=4 sw=4:
"""JKS/JCEKS file format decoder. Use in conjunction with PyOpenSSL
to translate to PEM, or load private key and certs directly into
openssl structs and wrap sockets.
Notes on Python2/3 compatibility:
Whereever possible, we rely on the 'natural' byte string
representation of each Python version, i.e. 'str' in Python2 and
'bytes' in Python3.
Python2.6+ aliases the 'bytes' type to 'str', so we can universally
write bytes(...) or b"" to get each version's natural byte string
representation.
The libraries we interact with are written to expect these natural
types in their respective Py2/Py3 versions, so this works well.
Things get slightly more complicated when we need to manipulate
individual bytes from a byte string. str[x] returns a 'str' in Python2
and an 'int' in Python3. You can't do 'int' operations on a 'str' and
vice-versa, so we need some form of common data type. We use
bytearray() for this purpose; in both Python2 and Python3, this will
return individual elements as an 'int'.
"""
from __future__ import print_function
import struct
import ctypes
import hashlib
import javaobj
import time
from pyasn1.codec.ber import encoder, decoder
from pyasn1_modules import rfc5208
from pyasn1_modules.rfc2459 import AlgorithmIdentifier
from pyasn1.type import univ, namedtype
from . import rfc2898
from . import sun_crypto
from .util import *
try:
from StringIO import StringIO as BytesIO # python 2
except ImportError:
from io import BytesIO # python3
__version_info__ = (20, 0, 0)
__version__ = ".".join(str(x) for x in __version_info__ if str(x))
MAGIC_NUMBER_JKS = b4.pack(0xFEEDFEED)
MAGIC_NUMBER_JCEKS = b4.pack(0xCECECECE)
SIGNATURE_WHITENING = b"Mighty Aphrodite"
class TrustedCertEntry(AbstractKeystoreEntry):
"""Represents a trusted certificate entry in a JKS or JCEKS keystore."""
def __init__(self, **kwargs):
super(TrustedCertEntry, self).__init__(**kwargs)
self.type = kwargs.get("type")
"""A string indicating the type of certificate. Unless in exotic applications, this is usually ``X.509``."""
self.cert = kwargs.get("cert")
"""A byte string containing the actual certificate data. In the case of X.509 certificates, this is the DER-encoded
X.509 representation of the certificate."""
@classmethod
def new(cls, alias, cert):
"""
Helper function to create a new TrustedCertEntry.
:param str alias: The alias for the Trusted Cert Entry
:param str certs: The certificate, as a byte string.
:returns: A loaded :class:`TrustedCertEntry` instance, ready
to be placed in a keystore.
"""
timestamp = int(time.time()) * 1000
tke = cls(timestamp = timestamp,
# Alias must be lower case or it will corrupt the keystore for Java Keytool and Keytool Explorer
alias = alias.lower(),
cert = cert)
return tke
def is_decrypted(self):
"""Always returns ``True`` for this entry type."""
return True
def decrypt(self, key_password):
"""Does nothing for this entry type; certificates are inherently public data and are not stored in encrypted form."""
return
def encrypt(self, key_password):
"""Does nothing for this entry type; certificates are inherently public data and are not stored in encrypted form."""
return
class PrivateKeyEntry(AbstractKeystoreEntry):
"""Represents a private key entry in a JKS or JCEKS keystore (e.g. an RSA or DSA private key)."""
def __init__(self, **kwargs):
super(PrivateKeyEntry, self).__init__(**kwargs)
self.cert_chain = kwargs.get("cert_chain")
"""
A list of tuples, representing the certificate chain associated with the private key. Each element of the list of a 2-tuple
containing the following data:
- ``[0]``: A string indicating the type of certificate. Unless in exotic applications, this is usually ``X.509``.
- ``[1]``: A byte string containing the actual certificate data. In the case of X.509 certificates, this is the DER-encoded X.509 representation of the certificate.
"""
self._encrypted = kwargs.get("encrypted")
self._pkey = kwargs.get("pkey")
self._pkey_pkcs8 = kwargs.get("pkey_pkcs8")
self._algorithm_oid = kwargs.get("algorithm_oid")
@classmethod
def new(cls, alias, certs, key, key_format='pkcs8'):
"""
Helper function to create a new PrivateKeyEntry.
:param str alias: The alias for the Private Key Entry
:param list certs: An list of certificates, as byte strings.
The first one should be the one belonging to the private key,
the others the chain (in correct order).
:param str key: A byte string containing the private key in the
format specified in the key_format parameter (default pkcs8).
:param str key_format: The format of the provided private key.
Valid options are pkcs8 or rsa_raw. Defaults to pkcs8.
:returns: A loaded :class:`PrivateKeyEntry` instance, ready
to be placed in a keystore.
:raises UnsupportedKeyFormatException: If the key format is
unsupported.
"""
timestamp = int(time.time()) * 1000
cert_chain = []
for cert in certs:
cert_chain.append(('X.509', cert))
pke = cls(timestamp = timestamp,
# Alias must be lower case or it will corrupt the keystore for Java Keytool and Keytool Explorer
alias = alias.lower(),
cert_chain = cert_chain)
if key_format == 'pkcs8':
private_key_info = decoder.decode(key, asn1Spec=rfc5208.PrivateKeyInfo())[0]
pke._algorithm_oid = private_key_info['privateKeyAlgorithm']['algorithm'].asTuple()
pke.pkey = private_key_info['privateKey'].asOctets()
pke.pkey_pkcs8 = key
elif key_format == 'rsa_raw':
pke._algorithm_oid = RSA_ENCRYPTION_OID
# We must encode it to pkcs8
private_key_info = rfc5208.PrivateKeyInfo()
private_key_info.setComponentByName('version','v1')
a = AlgorithmIdentifier()
a.setComponentByName('algorithm', pke._algorithm_oid)
a.setComponentByName('parameters', '\x05\x00')
private_key_info.setComponentByName('privateKeyAlgorithm', a)
private_key_info.setComponentByName('privateKey', key)
pke.pkey_pkcs8 = encoder.encode(private_key_info, ifNotEmpty=True)
pke.pkey = key
else:
raise UnsupportedKeyFormatException("Key Format '%s' is not supported" % key_format)
return pke
def __getattr__(self, name):
if not self.is_decrypted():
raise NotYetDecryptedException("Cannot access attribute '%s'; entry not yet decrypted, call decrypt() with the correct password first" % name)
return self.__dict__['_' + name]
def is_decrypted(self):
return (not self._encrypted)
def decrypt(self, key_password):
"""
Decrypts the entry using the given password. Has no effect if the entry has already been decrypted.
:param str key_password: The password to decrypt the entry with. If the entry was loaded from a JCEKS keystore,
the password must not contain any characters outside of the ASCII character set.
:raises DecryptionFailureException: If the entry could not be decrypted using the given password.
:raises UnexpectedAlgorithmException: If the entry was encrypted with an unknown or unexpected algorithm
:raise ValueError: If the entry was loaded from a JCEKS keystore and the password contains non-ASCII characters.
"""
if self.is_decrypted():
return
encrypted_info = decoder.decode(self._encrypted, asn1Spec=rfc5208.EncryptedPrivateKeyInfo())[0]
algo_id = encrypted_info['encryptionAlgorithm']['algorithm'].asTuple()
algo_params = encrypted_info['encryptionAlgorithm']['parameters'].asOctets()
encrypted_private_key = encrypted_info['encryptedData'].asOctets()
plaintext = None
try:
if algo_id == sun_crypto.SUN_JKS_ALGO_ID:
plaintext = sun_crypto.jks_pkey_decrypt(encrypted_private_key, key_password)
elif algo_id == sun_crypto.SUN_JCE_ALGO_ID:
if self.store_type != "jceks":
raise UnexpectedAlgorithmException("Encountered JCEKS private key protection algorithm in JKS keystore")
# see RFC 2898, section A.3: PBES1 and definitions of AlgorithmIdentifier and PBEParameter
params = decoder.decode(algo_params, asn1Spec=rfc2898.PBEParameter())[0]
salt = params['salt'].asOctets()
iteration_count = int(params['iterationCount'])
plaintext = sun_crypto.jce_pbe_decrypt(encrypted_private_key, key_password, salt, iteration_count)
else:
raise UnexpectedAlgorithmException("Unknown %s private key protection algorithm: %s" % (self.store_type.upper(), algo_id))
except (BadHashCheckException, BadPaddingException):
raise DecryptionFailureException("Failed to decrypt data for private key '%s'; wrong password?" % self.alias)
# at this point, 'plaintext' is a PKCS#8 PrivateKeyInfo (see RFC 5208)
private_key_info = decoder.decode(plaintext, asn1Spec=rfc5208.PrivateKeyInfo())[0]
key = private_key_info['privateKey'].asOctets()
algorithm_oid = private_key_info['privateKeyAlgorithm']['algorithm'].asTuple()
self._encrypted = None
self._pkey = key
self._pkey_pkcs8 = plaintext
self._algorithm_oid = algorithm_oid
def encrypt(self, key_password):
"""
Encrypts the private key, so that it can be saved to a keystore.
This will make it necessary to decrypt it again if it is going to be used later.
Has no effect if the entry is already encrypted.
:param str key_password: The password to encrypt the entry with.
"""
if not self.is_decrypted():
return
encrypted_private_key = sun_crypto.jks_pkey_encrypt(self.pkey_pkcs8, key_password)
a = AlgorithmIdentifier()
a.setComponentByName('algorithm', sun_crypto.SUN_JKS_ALGO_ID)
a.setComponentByName('parameters', '\x05\x00')
epki = rfc5208.EncryptedPrivateKeyInfo()
epki.setComponentByName('encryptionAlgorithm',a)
epki.setComponentByName('encryptedData', encrypted_private_key)
self._encrypted = encoder.encode(epki)
self._pkey = None
self._pkey_pkcs8 = None
self._algorithm_oid = None
is_decrypted.__doc__ = AbstractKeystoreEntry.is_decrypted.__doc__
class SecretKeyEntry(AbstractKeystoreEntry):
"""Represents a secret (symmetric) key entry in a JCEKS keystore (e.g. an AES or DES key)."""
def __init__(self, **kwargs):
super(SecretKeyEntry, self).__init__(**kwargs)
self._encrypted = kwargs.get("sealed_obj")
self._algorithm = kwargs.get("algorithm")
self._key = kwargs.get("key")
self._key_size = kwargs.get("key_size")
@classmethod
def new(cls, alias, sealed_obj, algorithm, key, key_size):
"""
Helper function to create a new SecretKeyEntry.
:returns: A loaded :class:`SecretKeyEntry` instance, ready
to be placed in a keystore.
"""
timestamp = int(time.time()) * 1000
raise NotImplementedError("Creating Secret Keys not implemented")
def __getattr__(self, name):
if not self.is_decrypted():
raise NotYetDecryptedException("Cannot access attribute '%s'; entry not yet decrypted, call decrypt() with the correct password first" % name)
return self.__dict__['_' + name]
def is_decrypted(self):
return (not self._encrypted)
def decrypt(self, key_password):
"""
Decrypts the entry using the given password. Has no effect if the entry has already been decrypted.
:param str key_password: The password to decrypt the entry with. Must not contain any characters outside
of the ASCII character set.
:raises DecryptionFailureException: If the entry could not be decrypted using the given password.
:raises UnexpectedAlgorithmException: If the entry was encrypted with an unknown or unexpected algorithm
:raise ValueError: If the password contains non-ASCII characters.
"""
if self.is_decrypted():
return
plaintext = None
sealed_obj = self._encrypted
if sealed_obj.sealAlg == "PBEWithMD5AndTripleDES":
# if the object was sealed with PBEWithMD5AndTripleDES
# then the parameters should apply to the same algorithm
# and not be empty or null
if sealed_obj.paramsAlg != sealed_obj.sealAlg:
raise UnexpectedAlgorithmException("Unexpected parameters algorithm used in SealedObject; should match sealing algorithm '%s' but found '%s'" % (sealed_obj.sealAlg, sealed_obj.paramsAlg))
if sealed_obj.encodedParams is None or len(sealed_obj.encodedParams) == 0:
raise UnexpectedJavaTypeException("No parameters found in SealedObject instance for sealing algorithm '%s'; need at least a salt and iteration count to decrypt" % sealed_obj.sealAlg)
params_asn1 = decoder.decode(sealed_obj.encodedParams, asn1Spec=rfc2898.PBEParameter())[0]
salt = params_asn1['salt'].asOctets()
iteration_count = int(params_asn1['iterationCount'])
try:
plaintext = sun_crypto.jce_pbe_decrypt(sealed_obj.encryptedContent, key_password, salt, iteration_count)
except sun_crypto.BadPaddingException:
raise DecryptionFailureException("Failed to decrypt data for secret key '%s'; bad password?" % self.alias)
else:
raise UnexpectedAlgorithmException("Unexpected algorithm used for encrypting SealedObject: sealAlg=%s" % sealed_obj.sealAlg)
# The plaintext here is another serialized Java object; this
# time it's an object implementing the javax.crypto.SecretKey
# interface. When using the default SunJCE provider, these
# are usually either javax.crypto.spec.SecretKeySpec objects,
# or some other specialized ones like those found in the
# com.sun.crypto.provider package (e.g. DESKey and DESedeKey).
#
# Additionally, things are further complicated by the fact
# that some of these specialized SecretKey implementations
# (i.e. other than SecretKeySpec) implement a writeReplace()
# method, causing Java's serialization runtime to swap out the
# object for a completely different one at serialization time.
# Again for SunJCE, the subsitute object that gets serialized
# is usually a java.security.KeyRep object.
obj, dummy = KeyStore._read_java_obj(plaintext, 0)
clazz = obj.get_class()
if clazz.name == "javax.crypto.spec.SecretKeySpec":
algorithm = obj.algorithm
key = KeyStore._java_bytestring(obj.key)
key_size = len(key)*8
elif clazz.name == "java.security.KeyRep":
assert (obj.type.constant == "SECRET"), "Expected value 'SECRET' for KeyRep.type enum value, found '%s'" % obj.type.constant
key_bytes = KeyStore._java_bytestring(obj.encoded)
key_encoding = obj.format
if key_encoding == "RAW":
pass # ok, no further processing needed
elif key_encoding == "X.509":
raise NotImplementedError("X.509 encoding for KeyRep objects not yet implemented")
elif key_encoding == "PKCS#8":
raise NotImplementedError("PKCS#8 encoding for KeyRep objects not yet implemented")
else:
raise UnexpectedKeyEncodingException("Unexpected key encoding '%s' found in serialized java.security.KeyRep object; expected one of 'RAW', 'X.509', 'PKCS#8'." % key_encoding)
algorithm = obj.algorithm
key = key_bytes
key_size = len(key)*8
else:
raise UnexpectedJavaTypeException("Unexpected object of type '%s' found inside SealedObject; don't know how to handle it" % clazz.name)
self._encrypted = None
self._algorithm = algorithm
self._key = key
self._key_size = key_size
is_decrypted.__doc__ = AbstractKeystoreEntry.is_decrypted.__doc__
def encrypt(self, key_password):
"""
Encrypts the Secret Key so that the keystore can be saved
"""
raise NotImplementedError("Encrypting of Secret Keys not implemented")
# --------------------------------------------------------------------------
class KeyStore(AbstractKeystore):
"""
Represents a loaded JKS or JCEKS keystore.
"""
@classmethod
def new(cls, store_type, store_entries):
"""
Helper function to create a new KeyStore.
:param string store_type: What kind of keystore
the store should be. Valid options are jks or jceks.
:param list store_entries: Existing entries that
should be added to the keystore.
:returns: A loaded :class:`KeyStore` instance,
with the specified entries.
:raises DuplicateAliasException: If some of the
entries have the same alias.
:raises UnsupportedKeyStoreTypeException: If the keystore is of
an unsupported type
:raises UnsupportedKeyStoreEntryTypeException: If some
of the keystore entries are unsupported (in this keystore type)
"""
if store_type not in ['jks', 'jceks']:
raise UnsupportedKeystoreTypeException("The Keystore Type '%s' is not supported" % store_type)
entries = {}
for entry in store_entries:
if not isinstance(entry, AbstractKeystoreEntry):
raise UnsupportedKeystoreEntryTypeException("Entries must be a KeyStore Entry")
if store_type != 'jceks' and isinstance(entry, SecretKeyEntry):
raise UnsupportedKeystoreEntryTypeException('Secret Key only allowed in JCEKS keystores')
alias = entry.alias
if alias in entries:
raise DuplicateAliasException("Found duplicate alias '%s'" % alias)
entries[alias] = entry
return cls(store_type, entries)
@classmethod
def loads(cls, data, store_password, try_decrypt_keys=True):
"""Loads the given keystore file using the supplied password for
verifying its integrity, and returns a :class:`KeyStore` instance.
Note that entries in the store that represent some form of
cryptographic key material are stored in encrypted form, and
therefore require decryption before becoming accessible.
Upon original creation of a key entry in a Java keystore,
users are presented with the choice to either use the same
password as the store password, or use a custom one. The most
common choice is to use the store password for the individual
key entries as well.
For ease of use in this typical scenario, this function will
attempt to decrypt each key entry it encounters with the store
password:
- If the key can be successfully decrypted with the store
password, the entry is returned in its decrypted form, and
its attributes are immediately accessible.
- If the key cannot be decrypted with the store password, the
entry is returned in its encrypted form, and requires a
manual follow-up decrypt(key_password) call from the user
before its individual attributes become accessible.
Setting ``try_decrypt_keys`` to ``False`` disables this automatic
decryption attempt, and returns all key entries in encrypted
form.
You can query whether a returned entry object has already been
decrypted by calling the :meth:`is_decrypted` method on it.
Attempting to access attributes of an entry that has not yet
been decrypted will result in a
:class:`~jks.util.NotYetDecryptedException`.
:param bytes data: Byte string representation of the keystore
to be loaded.
:param str password: Keystore password string
:param bool try_decrypt_keys: Whether to automatically try to
decrypt any encountered key entries using the same password
as the keystore password.
:returns: A loaded :class:`KeyStore` instance, if the keystore
could be successfully parsed and the supplied store password
is correct.
If the ``try_decrypt_keys`` parameter was set to ``True``, any
keys that could be successfully decrypted using the store
password have already been decrypted; otherwise, no atttempt
to decrypt any key entries is made.
:raises BadKeystoreFormatException: If the keystore is malformed
in some way
:raises UnsupportedKeystoreVersionException: If the keystore
contains an unknown format version number
:raises KeystoreSignatureException: If the keystore signature
could not be verified using the supplied store password
:raises DuplicateAliasException: If the keystore contains
duplicate aliases
"""
store_type = ""
magic_number = data[:4]
if magic_number == MAGIC_NUMBER_JKS:
store_type = "jks"
elif magic_number == MAGIC_NUMBER_JCEKS:
store_type = "jceks"
else:
raise BadKeystoreFormatException('Not a JKS or JCEKS keystore'
' (magic number wrong; expected'
' FEEDFEED or CECECECE)')
try:
version = b4.unpack_from(data, 4)[0]
if version != 2:
tmpl = 'Unsupported keystore version; expected v2, found v%r'
raise UnsupportedKeystoreVersionException(tmpl % version)
entries = {}
entry_count = b4.unpack_from(data, 8)[0]
pos = 12
for i in range(entry_count):
tag = b4.unpack_from(data, pos)[0]; pos += 4
alias, pos = cls._read_utf(data, pos, kind="entry alias")
timestamp = int(b8.unpack_from(data, pos)[0]); pos += 8 # milliseconds since UNIX epoch
if tag == 1:
entry, pos = cls._read_private_key(data, pos, store_type)
elif tag == 2:
entry, pos = cls._read_trusted_cert(data, pos, store_type)
elif tag == 3:
if store_type != "jceks":
raise BadKeystoreFormatException("Unexpected entry tag {0} encountered in JKS keystore; only supported in JCEKS keystores".format(tag))
entry, pos = cls._read_secret_key(data, pos, store_type)
else:
raise BadKeystoreFormatException("Unexpected keystore entry tag %d", tag)
entry.alias = alias
entry.timestamp = timestamp
if try_decrypt_keys:
try:
entry.decrypt(store_password)
except DecryptionFailureException:
pass # ok, let user call decrypt() manually
if alias in entries:
raise DuplicateAliasException("Found duplicate alias '%s'" % alias)
entries[alias] = entry
except struct.error as e:
raise BadKeystoreFormatException(e)
# skip integrity check if no password is provided
if store_password is None:
return cls(store_type, entries)
# check keystore integrity (uses UTF-16BE encoding of the password)
hash_fn = hashlib.sha1
hash_digest_size = hash_fn().digest_size
store_password_utf16 = store_password.encode('utf-16be')
expected_hash = hash_fn(store_password_utf16 + SIGNATURE_WHITENING + data[:pos]).digest()
found_hash = data[pos:pos+hash_digest_size]
if len(found_hash) != hash_digest_size:
tmpl = "Bad signature size; found %d bytes, expected %d bytes"
raise BadKeystoreFormatException(tmpl % (len(found_hash),
hash_digest_size))
if expected_hash != found_hash:
raise KeystoreSignatureException("Hash mismatch; incorrect keystore password?")
return cls(store_type, entries)
@classmethod
def _write_private_key(cls, alias, item, key_password):
private_key_entry = b4.pack(1) # private key
private_key_entry += cls._write_utf(alias)
private_key_entry += b8.pack(item.timestamp)
item.encrypt(key_password)
private_key_entry += cls._write_data(item._encrypted)
private_key_entry += b4.pack(len(item.cert_chain))
for cert in item.cert_chain:
private_key_entry += cls._write_utf(cert[0])
private_key_entry += cls._write_data(cert[1])
return private_key_entry
@classmethod
def _write_trusted_cert(cls, alias, item):
trusted_cert = b4.pack(2) # trusted cert
trusted_cert += cls._write_utf(alias)
trusted_cert += b8.pack(item.timestamp)
trusted_cert += cls._write_utf('X.509')
trusted_cert += cls._write_data(item.cert)
return trusted_cert
def saves(self, store_password):
"""
Saves the keystore so that it can be read by other applications.
If any of the private keys are unencrypted, they will be encrypted
with the same password as the keystore.
:param str store_password: Password for the created keystore
(and for any unencrypted keys)
:returns: A byte string representation of the keystore.
:raises UnsupportedKeystoreTypeException: If the keystore
is of an unsupported type
:raises UnsupportedKeystoreEntryTypeException: If the keystore
contains an unsupported entry type
"""
if self.store_type == 'jks':
keystore = MAGIC_NUMBER_JKS
elif self.store_type == 'jceks':
raise NotImplementedError("Saving of JCEKS keystores is not implemented")
else:
raise UnsupportedKeystoreTypeException("Only JKS and JCEKS keystores are supported")
keystore += b4.pack(2) # version 2
keystore += b4.pack(len(self.entries))
for alias, item in self.entries.items():
if isinstance(item, TrustedCertEntry):
keystore += self._write_trusted_cert(alias, item)
elif isinstance(item, PrivateKeyEntry):
keystore += self._write_private_key(alias, item, store_password)
elif isinstance(item, SecretKeyEntry):
if self.store_type != 'jceks':
raise UnsupportedKeystoreEntryTypeException('Secret Key only allowed in JCEKS keystores')
raise NotImplementedError("Saving of Secret Keys not implemented")
else:
raise UnsupportedKeystoreEntryTypeException("Unknown entry type in keystore")
hash_fn = hashlib.sha1
store_password_utf16 = store_password.encode('utf-16be')
hash = hash_fn(store_password_utf16 + SIGNATURE_WHITENING + keystore).digest()
keystore += hash
return keystore
def __init__(self, store_type, entries):
super(KeyStore, self).__init__(store_type, entries)
@property
def certs(self):
"""A subset of the :attr:`entries` dictionary, filtered down to only
those entries of type :class:`TrustedCertEntry`."""
return dict([(a, e) for a, e in self.entries.items()
if isinstance(e, TrustedCertEntry)])
@property
def secret_keys(self):
"""A subset of the :attr:`entries` dictionary, filtered down to only
those entries of type :class:`SecretKeyEntry`."""
return dict([(a, e) for a, e in self.entries.items()
if isinstance(e, SecretKeyEntry)])
@property
def private_keys(self):
"""A subset of the :attr:`entries` dictionary, filtered down to only
those entries of type :class:`PrivateKeyEntry`."""
return dict([(a, e) for a, e in self.entries.items()
if isinstance(e, PrivateKeyEntry)])
@classmethod
def _read_trusted_cert(cls, data, pos, store_type):
cert_type, pos = cls._read_utf(data, pos, kind="certificate type")
cert_data, pos = cls._read_data(data, pos)
entry = TrustedCertEntry(type=cert_type, cert=cert_data, store_type=store_type)
return entry, pos
@classmethod
def _read_private_key(cls, data, pos, store_type):
ber_data, pos = cls._read_data(data, pos)
chain_len = b4.unpack_from(data, pos)[0]
pos += 4
cert_chain = []
for j in range(chain_len):
cert_type, pos = cls._read_utf(data, pos, kind="certificate type")
cert_data, pos = cls._read_data(data, pos)
cert_chain.append((cert_type, cert_data))
entry = PrivateKeyEntry(cert_chain=cert_chain, encrypted=ber_data, store_type=store_type)
return entry, pos
@classmethod
def _read_secret_key(cls, data, pos, store_type):
# SecretKeys are stored in the key store file through Java's
# serialization mechanism, i.e. as an actual serialized Java
# object embedded inside the file. The objects that get stored
# are not the SecretKey instances themselves though, as that
# would trivially expose the key without the need for a
# passphrase to gain access to it.
#
# Instead, an object of type javax.crypto.SealedObject is
# written. The purpose of this class is specifically to
# securely serialize objects that contain secret values by
# applying a password-based encryption scheme to the
# serialized form of the object to be protected. Only the
# resulting ciphertext is then stored by the serialized form
# of the SealedObject instance.
#
# To decrypt the SealedObject, the correct passphrase must be
# given to be able to decrypt the underlying object's
# serialized form. Once decrypted, one more de-serialization
# will result in the original object being restored.
#
# The default key protector used by the SunJCE provider
# returns an instance of type SealedObjectForKeyProtector, a
# (direct) subclass of SealedObject, which uses Java's
# custom/unpublished PBEWithMD5AndTripleDES algorithm.
#
# Class member structure:
#
# SealedObjectForKeyProtector:
# static final long serialVersionUID = -3650226485480866989L;
#
# SealedObject:
# static final long serialVersionUID = 4482838265551344752L;
# private byte[] encryptedContent; # The serialized underlying object, in encrypted format.
# private String sealAlg; # The algorithm that was used to seal this object.
# private String paramsAlg; # The algorithm of the parameters used.
# protected byte[] encodedParams; # The cryptographic parameters used by the sealing Cipher, encoded in the default format.
sealed_obj, pos = cls._read_java_obj(data, pos, ignore_remaining_data=True)
if not cls._java_is_subclass(sealed_obj, "javax.crypto.SealedObject"):
raise UnexpectedJavaTypeException("Unexpected sealed object type '%s'; not a subclass of javax.crypto.SealedObject" % sealed_obj.get_class().name)
if sealed_obj.encryptedContent:
sealed_obj.encryptedContent = cls._java_bytestring(sealed_obj.encryptedContent)
if sealed_obj.encodedParams:
sealed_obj.encodedParams = KeyStore._java_bytestring(sealed_obj.encodedParams)
entry = SecretKeyEntry(sealed_obj=sealed_obj, store_type=store_type)
return entry, pos
@classmethod
def _read_java_obj(cls, data, pos, ignore_remaining_data=False):
data_stream = BytesIO(data[pos:])
obj = javaobj.load(data_stream, ignore_remaining_data=ignore_remaining_data)
obj_size = data_stream.tell()
return obj, pos + obj_size
@classmethod
def _java_is_subclass(cls, obj, class_name):
"""Given a deserialized JavaObject as returned by the javaobj library,
determine whether it's a subclass of the given class name.
"""
clazz = obj.get_class()
while clazz:
if clazz.name == class_name:
return True
clazz = clazz.superclass
return False
@classmethod
def _java_bytestring(cls, java_byte_list):
"""Convert the value returned by javaobj for a byte[] to a byte
string. Java's bytes are signed and numeric (i.e. not chars),
so javaobj returns Java byte arrays as a list of Python
integers in the range [-128, 127].
For ease of use we want to get a byte string representation of
that, so we reinterpret each integer as an unsigned byte, take
its new value as another Python int (now remapped to the range
[0, 255]), and use struct.pack() to create the matching byte
string.
"""
args = [ctypes.c_ubyte(sb).value for sb in java_byte_list]
return struct.pack("%dB" % len(java_byte_list), *args)
pyjks-20.0.0/jks/rfc2898.py 0000664 0000000 0000000 00000000425 13646725476 0015224 0 ustar 00root root 0000000 0000000 # vim: set ai et ts=4 sts=4 sw=4:
from pyasn1.type import univ, namedtype
class PBEParameter(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('salt', univ.OctetString()),
namedtype.NamedType('iterationCount', univ.Integer())
)
pyjks-20.0.0/jks/rfc7292.py 0000664 0000000 0000000 00000014006 13646725476 0015215 0 ustar 00root root 0000000 0000000 # vim: set ai et ts=4 sts=4 sw=4:
from __future__ import print_function
import hashlib
import ctypes
from pyasn1.type import univ, namedtype
from Cryptodome.Cipher import DES3
from .util import xor_bytearrays, add_pkcs7_padding, strip_pkcs7_padding, BadDataLengthException
PBE_WITH_SHA1_AND_TRIPLE_DES_CBC_OID = (1,2,840,113549,1,12,1,3)
PURPOSE_KEY_MATERIAL = 1
PURPOSE_IV_MATERIAL = 2
PURPOSE_MAC_MATERIAL = 3
class Pkcs12PBEParams(univ.Sequence):
"""Virtually identical to PKCS#5's PBEParameter, but nevertheless has its own definition in its own RFC, so gets its own class."""
componentType = namedtype.NamedTypes(
namedtype.NamedType('salt', univ.OctetString()),
namedtype.NamedType('iterations', univ.Integer())
)
def derive_key(hashfn, purpose_byte, password_str, salt, iteration_count, desired_key_size):
"""
Implements PKCS#12 key derivation as specified in RFC 7292, Appendix B, "Deriving Keys and IVs from Passwords and Salt".
Ported from BC's implementation in org.bouncycastle.crypto.generators.PKCS12ParametersGenerator.
hashfn: hash function to use (expected to support the hashlib interface and attributes)
password_str: text string (not yet transformed into bytes)
salt: byte sequence
purpose: "purpose byte", signifies the purpose of the generated pseudorandom key material
desired_key_size: desired amount of bytes of key material to generate
"""
password_bytes = (password_str.encode('utf-16be') + b"\x00\x00") if len(password_str) > 0 else b""
u = hashfn().digest_size # in bytes
v = hashfn().block_size # in bytes
_salt = bytearray(salt)
_password_bytes = bytearray(password_bytes)
D = bytearray([purpose_byte])*v
S_len = ((len(_salt) + v -1)//v)*v
S = bytearray([_salt[n % len(_salt)] for n in range(S_len)])
P_len = ((len(_password_bytes) + v -1)//v)*v
P = bytearray([_password_bytes[n % len(_password_bytes)] for n in range(P_len)])
I = S + P
c = (desired_key_size + u - 1)//u
derived_key = b""
for i in range(1,c+1):
A = hashfn(bytes(D + I)).digest()
for j in range(iteration_count - 1):
A = hashfn(A).digest()
A = bytearray(A)
B = bytearray([A[n % len(A)] for n in range(v)])
# Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit
# blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by
# setting I_j=(I_j+B+1) mod 2^v for each j.
for j in range(len(I)//v):
_adjust(I, j*v, B)
derived_key += bytes(A)
# truncate derived_key to the desired size
derived_key = derived_key[:desired_key_size]
return derived_key
def _adjust(a, a_offset, b):
"""
a = bytearray
a_offset = int
b = bytearray
"""
x = (b[-1] & 0xFF) + (a[a_offset + len(b) - 1] & 0xFF) + 1
a[a_offset + len(b) - 1] = ctypes.c_ubyte(x).value
x >>= 8
for i in range(len(b)-2, -1, -1):
x += (b[i] & 0xFF) + (a[a_offset + i] & 0xFF)
a[a_offset + i] = ctypes.c_ubyte(x).value
x >>= 8
def decrypt_PBEWithSHAAnd3KeyTripleDESCBC(data, password_str, salt, iteration_count):
iv = derive_key(hashlib.sha1, PURPOSE_IV_MATERIAL, password_str, salt, iteration_count, 64//8)
key = derive_key(hashlib.sha1, PURPOSE_KEY_MATERIAL, password_str, salt, iteration_count, 192//8)
if len(data) % 8 != 0:
raise BadDataLengthException("encrypted data length is not a multiple of 8 bytes")
des3 = DES3.new(key, DES3.MODE_CBC, IV=iv)
decrypted = des3.decrypt(data)
decrypted = strip_pkcs7_padding(decrypted, 8)
return decrypted
def decrypt_PBEWithSHAAndTwofishCBC(encrypted_data, password, salt, iteration_count):
"""
Decrypts PBEWithSHAAndTwofishCBC, assuming PKCS#12-generated PBE parameters.
(Not explicitly defined as an algorithm in RFC 7292, but defined here nevertheless because of the assumption of PKCS#12 parameters).
"""
iv = derive_key(hashlib.sha1, PURPOSE_IV_MATERIAL, password, salt, iteration_count, 16)
key = derive_key(hashlib.sha1, PURPOSE_KEY_MATERIAL, password, salt, iteration_count, 256//8)
encrypted_data = bytearray(encrypted_data)
encrypted_data_len = len(encrypted_data)
if encrypted_data_len % 16 != 0:
raise BadDataLengthException("encrypted data length is not a multiple of 16 bytes")
plaintext = bytearray()
# slow and dirty CBC decrypt
from twofish import Twofish
cipher = Twofish(key)
last_cipher_block = bytearray(iv)
for block_offset in range(0, encrypted_data_len, 16):
cipher_block = encrypted_data[block_offset:block_offset+16]
plaintext_block = xor_bytearrays(bytearray(cipher.decrypt(bytes(cipher_block))), last_cipher_block)
plaintext.extend(plaintext_block)
last_cipher_block = cipher_block
plaintext = strip_pkcs7_padding(plaintext, 16)
return bytes(plaintext)
def encrypt_PBEWithSHAAndTwofishCBC(plaintext_data, password, salt, iteration_count):
"""
Encrypts a value with PBEWithSHAAndTwofishCBC, assuming PKCS#12-generated PBE parameters.
(Not explicitly defined as an algorithm in RFC 7292, but defined here nevertheless because of the assumption of PKCS#12 parameters).
"""
iv = derive_key(hashlib.sha1, PURPOSE_IV_MATERIAL, password, salt, iteration_count, 16)
key = derive_key(hashlib.sha1, PURPOSE_KEY_MATERIAL, password, salt, iteration_count, 256//8)
plaintext_data = add_pkcs7_padding(plaintext_data, 16)
plaintext_data = bytearray(plaintext_data)
plaintext_len = len(plaintext_data)
assert plaintext_len % 16 == 0
ciphertext = bytearray()
from twofish import Twofish
cipher = Twofish(key)
last_cipher_block = bytearray(iv)
for block_offset in range(0, plaintext_len, 16):
plaintext_block = plaintext_data[block_offset:block_offset+16]
cipher_block = bytearray(cipher.encrypt(bytes(xor_bytearrays(plaintext_block, last_cipher_block))))
ciphertext.extend(cipher_block)
last_cipher_block = cipher_block
return bytes(ciphertext)
pyjks-20.0.0/jks/sun_crypto.py 0000664 0000000 0000000 00000013350 13646725476 0016325 0 ustar 00root root 0000000 0000000 # vim: set et ai ts=4 sts=4 sw=4:
import os
import hashlib
from .util import *
SUN_JKS_ALGO_ID = (1,3,6,1,4,1,42,2,17,1,1) # JavaSoft proprietary key-protection algorithm
SUN_JCE_ALGO_ID = (1,3,6,1,4,1,42,2,19,1) # PBE_WITH_MD5_AND_DES3_CBC_OID (non-published, modified version of PKCS#5 PBEWithMD5AndDES)
def jks_pkey_encrypt(key, password_str):
"""
Encrypts the private key with password protection algorithm used by JKS keystores.
"""
password_bytes = password_str.encode('utf-16be') # Java chars are UTF-16BE code units
iv = os.urandom(20)
key = bytearray(key)
xoring = zip(key, _jks_keystream(iv, password_bytes))
data = bytearray([d^k for d,k in xoring])
check = hashlib.sha1(bytes(password_bytes + key)).digest()
return bytes(iv + data + check)
def jks_pkey_decrypt(data, password_str):
"""
Decrypts the private key password protection algorithm used by JKS keystores.
The JDK sources state that 'the password is expected to be in printable ASCII', though this does not appear to be enforced;
the password is converted into bytes simply by taking each individual Java char and appending its raw 2-byte representation.
See sun/security/provider/KeyProtector.java in the JDK sources.
"""
password_bytes = password_str.encode('utf-16be') # Java chars are UTF-16BE code units
data = bytearray(data)
iv, data, check = data[:20], data[20:-20], data[-20:]
xoring = zip(data, _jks_keystream(iv, password_bytes))
key = bytearray([d^k for d,k in xoring])
if hashlib.sha1(bytes(password_bytes + key)).digest() != check:
raise BadHashCheckException("Bad hash check on private key; wrong password?")
key = bytes(key)
return key
def _jks_keystream(iv, password):
"""Helper keystream generator for _jks_pkey_decrypt"""
cur = iv
while 1:
xhash = hashlib.sha1(bytes(password + cur)) # hashlib.sha1 in python 2.6 does not accept a bytearray argument
cur = bytearray(xhash.digest()) # make sure we iterate over ints in both Py2 and Py3
for byte in cur:
yield byte
def jce_pbe_decrypt(data, password, salt, iteration_count):
"""
Decrypts Sun's custom PBEWithMD5AndTripleDES password-based encryption scheme.
It is based on password-based encryption as defined by the PKCS #5 standard, except that it uses triple DES instead of DES.
Here's how this algorithm works:
1. Create random salt and split it in two halves. If the two halves are identical, invert(*) the first half.
2. Concatenate password with each of the halves.
3. Digest each concatenation with c iterations, where c is the iterationCount. Concatenate the output from each digest round with the password,
and use the result as the input to the next digest operation. The digest algorithm is MD5.
4. After c iterations, use the 2 resulting digests as follows: The 16 bytes of the first digest and the 1st 8 bytes of the 2nd digest
form the triple DES key, and the last 8 bytes of the 2nd digest form the IV.
(*) Not actually an inversion operation due to an implementation bug in com.sun.crypto.provider.PBECipherCore. See _jce_invert_salt_half for details.
See http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b27/com/sun/crypto/provider/PBECipherCore.java#PBECipherCore.deriveCipherKey%28java.security.Key%29
"""
key, iv = _jce_pbe_derive_key_and_iv(password, salt, iteration_count)
from Cryptodome.Cipher import DES3
des3 = DES3.new(key, DES3.MODE_CBC, IV=iv)
padded = des3.decrypt(data)
result = strip_pkcs5_padding(padded)
return result
def _jce_pbe_derive_key_and_iv(password, salt, iteration_count):
if len(salt) != 8:
raise ValueError("Expected 8-byte salt for PBEWithMD5AndTripleDES (OID %s), found %d bytes" % (".".join(str(i) for i in SUN_JCE_ALGO_ID), len(salt)))
# Note: unlike JKS, the PBEWithMD5AndTripleDES algorithm as implemented for JCE keystores uses an ASCII string for the password, not a regular Java/UTF-16BE string.
# It validates this explicitly and will throw an InvalidKeySpecException if non-ASCII byte codes are present in the password.
# See PBEKey's constructor in com/sun/crypto/provider/PBEKey.java.
try:
password_bytes = password.encode('ascii')
except (UnicodeDecodeError, UnicodeEncodeError):
raise ValueError("Key password contains non-ASCII characters")
salt_halves = [salt[0:4], salt[4:8]]
if salt_halves[0] == salt_halves[1]:
salt_halves[0] = _jce_invert_salt_half(salt_halves[0])
derived = b""
for i in range(2):
to_be_hashed = salt_halves[i]
for k in range(iteration_count):
to_be_hashed = hashlib.md5(to_be_hashed + password_bytes).digest()
derived += to_be_hashed
key = derived[:-8] # = 24 bytes
iv = derived[-8:]
return key, iv
def _jce_invert_salt_half(salt_half):
"""
JCE's proprietary PBEWithMD5AndTripleDES algorithm as described in the OpenJDK sources calls for inverting the first salt half if the two halves are equal.
However, there appears to be a bug in the original JCE implementation of com.sun.crypto.provider.PBECipherCore causing it to perform a different operation:
for (i=0; i<2; i++) {
byte tmp = salt[i];
salt[i] = salt[3-i];
salt[3-1] = tmp; // <-- typo '1' instead of 'i'
}
The result is transforming [a,b,c,d] into [d,a,b,d] instead of [d,c,b,a] (verified going back to the original JCE 1.2.2 release for JDK 1.2).
See source (or bytecode) of com.sun.crypto.provider.PBECipherCore (JRE <= 7) and com.sun.crypto.provider.PBES1Core (JRE 8+):
"""
salt = bytearray(salt_half)
salt[2] = salt[1]
salt[1] = salt[0]
salt[0] = salt[3]
return bytes(salt)
pyjks-20.0.0/jks/util.py 0000664 0000000 0000000 00000023045 13646725476 0015077 0 ustar 00root root 0000000 0000000 # vim: set et ai ts=4 sts=4 sw=4:
from __future__ import print_function
import textwrap
import base64
import struct
b8 = struct.Struct('>Q')
b4 = struct.Struct('>L') # unsigned
b2 = struct.Struct('>H')
b1 = struct.Struct('B') # unsigned
py23basestring = ("".__class__, u"".__class__) # useful for isinstance checks
RSA_ENCRYPTION_OID = (1,2,840,113549,1,1,1)
DSA_OID = (1,2,840,10040,4,1) # identifier for DSA public/private keys; see RFC 3279, section 2.2.2 (e.g. in PKCS#8 PrivateKeyInfo or X.509 SubjectPublicKeyInfo)
DSA_WITH_SHA1_OID = (1,2,840,10040,4,3) # identifier for the DSA signature algorithm; see RFC 3279, section 2.3.2 (e.g. in X.509 signatures)
class KeystoreException(Exception):
"""Superclass for all pyjks exceptions."""
pass
class KeystoreSignatureException(KeystoreException):
"""Signifies that the supplied password for a keystore integrity check is incorrect."""
pass
class DuplicateAliasException(KeystoreException):
"""Signifies that duplicate aliases were encountered in a keystore."""
pass
class NotYetDecryptedException(KeystoreException):
"""
Signifies that an attribute of a key store entry can not be accessed because the entry has not yet been decrypted.
By default, the keystore ``load`` and ``loads`` methods automatically try to decrypt all key entries using the store password.
Any keys for which that attempt fails are returned undecrypted, and will raise this exception when its attributes are accessed.
To resolve, first call decrypt() with the correct password on the entry object whose attributes you want to access.
"""
pass
class BadKeystoreFormatException(KeystoreException):
"""Signifies that a structural error was encountered during key store parsing."""
pass
class BadDataLengthException(KeystoreException):
"""Signifies that given input data was of wrong or unexpected length."""
pass
class BadPaddingException(KeystoreException):
"""Signifies that bad padding was encountered during decryption."""
pass
class BadHashCheckException(KeystoreException):
"""Signifies that a hash computation did not match an expected value."""
pass
class DecryptionFailureException(KeystoreException):
"""Signifies failure to decrypt a value."""
pass
class UnsupportedKeystoreVersionException(KeystoreException):
"""Signifies an unexpected or unsupported keystore format version."""
pass
class UnexpectedJavaTypeException(KeystoreException):
"""Signifies that a serialized Java object of unexpected type was encountered."""
pass
class UnexpectedAlgorithmException(KeystoreException):
"""Signifies that an unexpected cryptographic algorithm was used in a keystore."""
pass
class UnexpectedKeyEncodingException(KeystoreException):
"""Signifies that a key was stored in an unexpected format or encoding."""
pass
class UnsupportedKeystoreTypeException(KeystoreException):
"""Signifies that the keystore was an unsupported type."""
pass
class UnsupportedKeystoreEntryTypeException(KeystoreException):
"""Signifies that the keystore entry was an unsupported type."""
pass
class UnsupportedKeyFormatException(KeystoreException):
"""Signifies that the key format was an unsupported type."""
pass
class AbstractKeystore(object):
"""
Abstract superclass for keystores.
"""
def __init__(self, store_type, entries):
self.store_type = store_type #: A string indicating the type of keystore that was loaded.
self.entries = dict(entries) #: A dictionary of all entries in the keystore, mapped by alias.
@classmethod
def load(cls, filename, store_password, try_decrypt_keys=True):
"""
Convenience wrapper function; reads the contents of the given file
and passes it through to :func:`loads`. See :func:`loads`.
"""
with open(filename, 'rb') as file:
input_bytes = file.read()
ret = cls.loads(input_bytes,
store_password,
try_decrypt_keys=try_decrypt_keys)
return ret
def save(self, filename, store_password):
"""
Convenience wrapper function; calls the :func:`saves`
and saves the content to a file.
"""
with open(filename, 'wb') as file:
keystore_bytes = self.saves(store_password)
file.write(keystore_bytes)
@classmethod
def _read_utf(cls, data, pos, kind=None):
"""
:param kind: Optional; a human-friendly identifier for the kind of UTF-8 data we're loading (e.g. is it a keystore alias? an algorithm identifier? something else?).
Used to construct more informative exception messages when a decoding error occurs.
"""
size = b2.unpack_from(data, pos)[0]
pos += 2
try:
return data[pos:pos+size].decode('utf-8'), pos+size
except (UnicodeEncodeError, UnicodeDecodeError) as e:
raise BadKeystoreFormatException(("Failed to read %s, contains bad UTF-8 data: %s" % (kind, str(e))) if kind else \
("Encountered bad UTF-8 data: %s" % str(e)))
@classmethod
def _read_data(cls, data, pos):
size = b4.unpack_from(data, pos)[0]
pos += 4
return data[pos:pos+size], pos+size
@classmethod
def _write_utf(cls, text):
encoded_text = text.encode('utf-8')
size = len(encoded_text)
result = b2.pack(size)
result += encoded_text
return result
@classmethod
def _write_data(cls, data):
size = len(data)
result = b4.pack(size)
result += data
return result
class AbstractKeystoreEntry(object):
"""Abstract superclass for keystore entries."""
def __init__(self, **kwargs):
super(AbstractKeystoreEntry, self).__init__()
self.store_type = kwargs.get("store_type")
self.alias = kwargs.get("alias")
self.timestamp = kwargs.get("timestamp")
@classmethod
def new(cls, alias):
"""
Helper function to create a new KeyStoreEntry.
"""
raise NotImplementedError("Abstract method")
def is_decrypted(self):
"""
Returns ``True`` if the entry has already been decrypted, ``False`` otherwise.
"""
raise NotImplementedError("Abstract method")
def decrypt(self, key_password):
"""
Decrypts the entry using the given password. Has no effect if the entry has already been decrypted.
:param str key_password: The password to decrypt the entry with.
:raises DecryptionFailureException: If the entry could not be decrypted using the given password.
:raises UnexpectedAlgorithmException: If the entry was encrypted with an unknown or unexpected algorithm
"""
raise NotImplementedError("Abstract method")
def encrypt(self, key_password):
"""
Encrypts the entry using the given password, so that it can be saved.
:param str key_password: The password to encrypt the entry with.
"""
raise NotImplementedError("Abstract method")
def as_hex(ba):
return "".join("{:02x}".format(b) for b in bytearray(ba))
def as_pem(der_bytes, type):
result = "-----BEGIN %s-----\n" % type
result += "\n".join(textwrap.wrap(base64.b64encode(der_bytes).decode('ascii'), 64))
result += "\n-----END %s-----" % type
return result
def bitstring_to_bytes(bitstr):
"""
Converts a pyasn1 univ.BitString instance to byte sequence of type 'bytes'.
The bit string is interpreted big-endian and is left-padded with 0 bits to form a multiple of 8.
"""
bitlist = list(bitstr)
bits_missing = (8 - len(bitlist) % 8) % 8
bitlist = [0]*bits_missing + bitlist # pad with 0 bits to a multiple of 8
result = bytearray()
for i in range(0, len(bitlist), 8):
byte = 0
for j in range(8):
byte = (byte << 1) | bitlist[i+j]
result.append(byte)
return bytes(result)
def xor_bytearrays(a, b):
return bytearray([x^y for x,y in zip(a,b)])
def print_pem(der_bytes, type):
print(as_pem(der_bytes, type))
def pkey_as_pem(pk):
if pk.algorithm_oid == RSA_ENCRYPTION_OID:
return as_pem(pk.pkey, "RSA PRIVATE KEY")
else:
return as_pem(pk.pkey_pkcs8, "PRIVATE KEY")
def strip_pkcs5_padding(m):
"""
Drop PKCS5 padding: 8-(||M|| mod 8) octets each with value 8-(||M|| mod 8)
Note: ideally we would use pycrypto for this, but it doesn't provide padding functionality and the project is virtually dead at this point.
"""
return strip_pkcs7_padding(m, 8)
def strip_pkcs7_padding(m, block_size):
"""
Same as PKCS#5 padding, except generalized to block sizes other than 8.
"""
if len(m) < block_size or len(m) % block_size != 0:
raise BadPaddingException("Unable to strip padding: invalid message length")
m = bytearray(m) # py2/3 compatibility: always returns individual indexed elements as ints
last_byte = m[-1]
# the bytes of m must all have value , otherwise something's wrong
if (last_byte <= 0 or last_byte > block_size) or (m[-last_byte:] != bytearray([last_byte])*last_byte):
raise BadPaddingException("Unable to strip padding: invalid padding found")
return bytes(m[:-last_byte]) # back to 'str'/'bytes'
def add_pkcs7_padding(m, block_size):
if block_size <= 0 or block_size > 255:
raise ValueError("Invalid block size")
m = bytearray(m)
num_padding_bytes = block_size - (len(m) % block_size)
m = m + bytearray([num_padding_bytes]*num_padding_bytes)
return bytes(m)
pyjks-20.0.0/requirements-test.txt 0000664 0000000 0000000 00000000153 13646725476 0017215 0 ustar 00root root 0000000 0000000 -r requirements.txt
py<1.6.0
pytest==3.0.7
tox==2.9.1
virtualenv==15.0.2
tox-virtualenv-no-download==1.0.2
pyjks-20.0.0/requirements.txt 0000664 0000000 0000000 00000000133 13646725476 0016236 0 ustar 00root root 0000000 0000000 pyasn1==0.3.5
pyasn1_modules==0.0.8
javaobj-py3==0.2.1
pycryptodomex==3.4.5
twofish==0.3.0
pyjks-20.0.0/setup.cfg 0000664 0000000 0000000 00000000071 13646725476 0014574 0 ustar 00root root 0000000 0000000 [wheel]
universal = 1
[metadata]
license_file = LICENSE
pyjks-20.0.0/setup.py 0000664 0000000 0000000 00000004375 13646725476 0014500 0 ustar 00root root 0000000 0000000 """PyJKS enables Python projects to load and manipulate Java KeyStore
(JKS) data without a JVM dependency. PyJKS supports JKS, JCEKS, BKS
and UBER (BouncyCastle) keystores. Simply::
pip install pyjks
Or::
easy_install pyjks
Then::
import jks
keystore = jks.KeyStore.load('keystore.jks', 'passphrase')
print(keystore.private_keys)
print(keystore.certs)
print(keystore.secret_keys)
And that's barely scratching the surface. Check out `the usage examples on
GitHub `_ for
more!
"""
from setuptools import setup, find_packages
setup(
name='pyjks',
version='20.0.0',
author="Kurt Rose, Jeroen De Ridder",
author_email="kurt@kurtrose.com",
description='Pure-Python Java Keystore (JKS) library',
keywords="JKS JCEKS java keystore security ssl",
license="MIT",
url="http://github.com/kurtbrose/pyjks",
long_description=__doc__,
classifiers=[
'Development Status :: 6 - Mature',
'License :: OSI Approved :: MIT License',
'Topic :: Utilities',
'Topic :: Software Development :: Libraries',
'Intended Audience :: Developers',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: Implementation :: PyPy',
],
packages=find_packages(exclude=['tests']),
install_requires=['pyasn1>=0.3.5',
'pyasn1_modules',
'javaobj-py3',
'pycryptodomex',
'twofish'],
test_suite="tests.test_jks",
)
"""
Releasing:
* Update version in setup.py, as well as __version__ and __version_info__ in jks.py
* Final test (currently, tox)
* Commit: "bumping version for x.x.x release"
* Run: python setup.py sdist bdist_wheel upload
* git tag -a vx.x.x -m "summary"
* Update CHANGELOG.md
* Update versions again for dev
* Commit: "bumping version for x.x.x+1 dev"
* git push && git push --tags
"""
pyjks-20.0.0/tests/ 0000775 0000000 0000000 00000000000 13646725476 0014117 5 ustar 00root root 0000000 0000000 pyjks-20.0.0/tests/__init__.py 0000664 0000000 0000000 00000000065 13646725476 0016231 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
"""
Test package for pyjks
"""
pyjks-20.0.0/tests/expected/ 0000775 0000000 0000000 00000000000 13646725476 0015720 5 ustar 00root root 0000000 0000000 pyjks-20.0.0/tests/expected/DSA2048.py 0000664 0000000 0000000 00000026136 13646725476 0017227 0 ustar 00root root 0000000 0000000 public_key = b"\x30\x82\x03\x42\x30\x82\x02\x35\x06\x07\x2a\x86\x48\xce\x38\x04\x01\x30\x82\x02\x28\x02\x82\x01\x01\x00\x8f\x79\x35\xd9\xb9\xaa" + \
b"\xe9\xbf\xab\xed\x88\x7a\xcf\x49\x51\xb6\xf3\x2e\xc5\x9e\x3b\xaf\x37\x18\xe8\xea\xc4\x96\x1f\x3e\xfd\x36\x06\xe7\x43\x51\xa9\xc4" + \
b"\x18\x33\x39\xb8\x09\xe7\xc2\xae\x1c\x53\x9b\xa7\x47\x5b\x85\xd0\x11\xad\xb8\xb4\x79\x87\x75\x49\x84\x69\x5c\xac\x0e\x8f\x14\xb3" + \
b"\x36\x08\x28\xa2\x2f\xfa\x27\x11\x0a\x3d\x62\xa9\x93\x45\x34\x09\xa0\xfe\x69\x6c\x46\x58\xf8\x4b\xdd\x20\x81\x9c\x37\x09\xa0\x10" + \
b"\x57\xb1\x95\xad\xcd\x00\x23\x3d\xba\x54\x84\xb6\x29\x1f\x9d\x64\x8e\xf8\x83\x44\x86\x77\x97\x9c\xec\x04\xb4\x34\xa6\xac\x2e\x75" + \
b"\xe9\x98\x5d\xe2\x3d\xb0\x29\x2f\xc1\x11\x8c\x9f\xfa\x9d\x81\x81\xe7\x33\x8d\xb7\x92\xb7\x30\xd7\xb9\xe3\x49\x59\x2f\x68\x09\x98" + \
b"\x72\x15\x39\x15\xea\x3d\x6b\x8b\x46\x53\xc6\x33\x45\x8f\x80\x3b\x32\xa4\xc2\xe0\xf2\x72\x90\x25\x6e\x4e\x3f\x8a\x3b\x08\x38\xa1" + \
b"\xc4\x50\xe4\xe1\x8c\x1a\x29\xa3\x7d\xdf\x5e\xa1\x43\xde\x4b\x66\xff\x04\x90\x3e\xd5\xcf\x16\x23\xe1\x58\xd4\x87\xc6\x08\xe9\x7f" + \
b"\x21\x1c\xd8\x1d\xca\x23\xcb\x6e\x38\x07\x65\xf8\x22\xe3\x42\xbe\x48\x4c\x05\x76\x39\x39\x60\x1c\xd6\x67\x02\x1d\x00\xba\xf6\x96" + \
b"\xa6\x85\x78\xf7\xdf\xde\xe7\xfa\x67\xc9\x77\xc7\x85\xef\x32\xb2\x33\xba\xe5\x80\xc0\xbc\xd5\x69\x5d\x02\x82\x01\x00\x16\xa6\x5c" + \
b"\x58\x20\x48\x50\x70\x4e\x75\x02\xa3\x97\x57\x04\x0d\x34\xda\x3a\x34\x78\xc1\x54\xd4\xe4\xa5\xc0\x2d\x24\x2e\xe0\x4f\x96\xe6\x1e" + \
b"\x4b\xd0\x90\x4a\xbd\xac\x8f\x37\xee\xb1\xe0\x9f\x31\x82\xd2\x3c\x90\x43\xcb\x64\x2f\x88\x00\x41\x60\xed\xf9\xca\x09\xb3\x20\x76" + \
b"\xa7\x9c\x32\xa6\x27\xf2\x47\x3e\x91\x87\x9b\xa2\xc4\xe7\x44\xbd\x20\x81\x54\x4c\xb5\x5b\x80\x2c\x36\x8d\x1f\xa8\x3e\xd4\x89\xe9" + \
b"\x4e\x0f\xa0\x68\x8e\x32\x42\x8a\x5c\x78\xc4\x78\xc6\x8d\x05\x27\xb7\x1c\x9a\x3a\xbb\x0b\x0b\xe1\x2c\x44\x68\x96\x39\xe7\xd3\xce" + \
b"\x74\xdb\x10\x1a\x65\xaa\x2b\x87\xf6\x4c\x68\x26\xdb\x3e\xc7\x2f\x4b\x55\x99\x83\x4b\xb4\xed\xb0\x2f\x7c\x90\xe9\xa4\x96\xd3\xa5" + \
b"\x5d\x53\x5b\xeb\xfc\x45\xd4\xf6\x19\xf6\x3f\x3d\xed\xbb\x87\x39\x25\xc2\xf2\x24\xe0\x77\x31\x29\x6d\xa8\x87\xec\x1e\x47\x48\xf8" + \
b"\x7e\xfb\x5f\xde\xb7\x54\x84\x31\x6b\x22\x32\xde\xe5\x53\xdd\xaf\x02\x11\x2b\x0d\x1f\x02\xda\x30\x97\x32\x24\xfe\x27\xae\xda\x8b" + \
b"\x9d\x4b\x29\x22\xd9\xba\x8b\xe3\x9e\xd9\xe1\x03\xa6\x3c\x52\x81\x0b\xc6\x88\xb7\xe2\xed\x43\x16\xe1\xef\x17\xdb\xde\x03\x82\x01" + \
b"\x05\x00\x02\x82\x01\x00\x6b\x68\xe8\x2e\xa9\x71\x5e\xfd\xdb\x0c\x6c\xdc\x2b\xb3\xc3\x68\x75\x26\x80\x70\x29\x18\xb8\xcb\x19\x5d" + \
b"\x23\x76\xaa\xd2\xa3\xe7\xbc\x75\xb3\x93\x04\x16\x01\xb6\x35\xd6\xe6\xad\x07\xc0\x2a\xbc\xd4\x6c\x13\xae\x36\xc8\x53\xec\x0e\x27" + \
b"\x27\xcd\x46\x04\xb5\x79\x67\xf7\x0f\xcb\x42\x02\x86\x7f\x7b\xab\xc3\x82\xb6\xcf\x88\x28\x85\xe8\xc8\xda\x64\x04\xa1\xab\x7e\xae" + \
b"\xe0\x58\xa3\x78\x9f\x11\xd3\xbe\x03\x12\x2a\x2a\xe7\x0a\xfd\x6d\x3c\x32\xf9\x93\x29\x73\x22\xdd\x08\x9b\x5f\x92\x5e\x1d\xd3\x86" + \
b"\x17\x47\xe4\xb4\xa1\x0f\x9b\xa4\xdb\xc6\x4a\x90\x02\x18\x9b\x02\x52\x4b\x07\xf0\x0c\x9d\x75\xee\x37\x2b\xa5\x32\x3f\xb7\xdb\xed" + \
b"\x49\xfb\xf0\xd6\x68\x12\x78\xd1\x2b\x38\xfc\xb5\x9a\x70\xec\x90\x09\x5f\x41\xe5\xd9\xcd\x94\x8f\x5f\x74\xf3\x91\x85\x2a\x0d\x6d" + \
b"\x05\xbe\x73\x80\x73\xcf\x8f\x57\x00\xc0\xde\x22\x73\x12\xc5\x49\x91\xf4\x25\x21\x32\x6a\xbb\xb8\x29\xc2\x5d\x43\xd4\xab\x23\xd5" + \
b"\xdf\xe4\x46\xfe\xed\x13\x0f\xf5\x6c\xba\x9b\x81\xa2\xe2\x22\xb3\x4d\x9f\x92\xfd\xdf\xce\x0c\x54\xcc\xda\x92\xa6\x97\x05\x06\x2c" + \
b"\xde\xf6\x47\xe5\x81\x2f"
private_key = b"\x30\x82\x02\x5c\x02\x01\x00\x30\x82\x02\x35\x06\x07\x2a\x86\x48\xce\x38\x04\x01\x30\x82\x02\x28\x02\x82\x01\x01\x00\x8f\x79\x35" + \
b"\xd9\xb9\xaa\xe9\xbf\xab\xed\x88\x7a\xcf\x49\x51\xb6\xf3\x2e\xc5\x9e\x3b\xaf\x37\x18\xe8\xea\xc4\x96\x1f\x3e\xfd\x36\x06\xe7\x43" + \
b"\x51\xa9\xc4\x18\x33\x39\xb8\x09\xe7\xc2\xae\x1c\x53\x9b\xa7\x47\x5b\x85\xd0\x11\xad\xb8\xb4\x79\x87\x75\x49\x84\x69\x5c\xac\x0e" + \
b"\x8f\x14\xb3\x36\x08\x28\xa2\x2f\xfa\x27\x11\x0a\x3d\x62\xa9\x93\x45\x34\x09\xa0\xfe\x69\x6c\x46\x58\xf8\x4b\xdd\x20\x81\x9c\x37" + \
b"\x09\xa0\x10\x57\xb1\x95\xad\xcd\x00\x23\x3d\xba\x54\x84\xb6\x29\x1f\x9d\x64\x8e\xf8\x83\x44\x86\x77\x97\x9c\xec\x04\xb4\x34\xa6" + \
b"\xac\x2e\x75\xe9\x98\x5d\xe2\x3d\xb0\x29\x2f\xc1\x11\x8c\x9f\xfa\x9d\x81\x81\xe7\x33\x8d\xb7\x92\xb7\x30\xd7\xb9\xe3\x49\x59\x2f" + \
b"\x68\x09\x98\x72\x15\x39\x15\xea\x3d\x6b\x8b\x46\x53\xc6\x33\x45\x8f\x80\x3b\x32\xa4\xc2\xe0\xf2\x72\x90\x25\x6e\x4e\x3f\x8a\x3b" + \
b"\x08\x38\xa1\xc4\x50\xe4\xe1\x8c\x1a\x29\xa3\x7d\xdf\x5e\xa1\x43\xde\x4b\x66\xff\x04\x90\x3e\xd5\xcf\x16\x23\xe1\x58\xd4\x87\xc6" + \
b"\x08\xe9\x7f\x21\x1c\xd8\x1d\xca\x23\xcb\x6e\x38\x07\x65\xf8\x22\xe3\x42\xbe\x48\x4c\x05\x76\x39\x39\x60\x1c\xd6\x67\x02\x1d\x00" + \
b"\xba\xf6\x96\xa6\x85\x78\xf7\xdf\xde\xe7\xfa\x67\xc9\x77\xc7\x85\xef\x32\xb2\x33\xba\xe5\x80\xc0\xbc\xd5\x69\x5d\x02\x82\x01\x00" + \
b"\x16\xa6\x5c\x58\x20\x48\x50\x70\x4e\x75\x02\xa3\x97\x57\x04\x0d\x34\xda\x3a\x34\x78\xc1\x54\xd4\xe4\xa5\xc0\x2d\x24\x2e\xe0\x4f" + \
b"\x96\xe6\x1e\x4b\xd0\x90\x4a\xbd\xac\x8f\x37\xee\xb1\xe0\x9f\x31\x82\xd2\x3c\x90\x43\xcb\x64\x2f\x88\x00\x41\x60\xed\xf9\xca\x09" + \
b"\xb3\x20\x76\xa7\x9c\x32\xa6\x27\xf2\x47\x3e\x91\x87\x9b\xa2\xc4\xe7\x44\xbd\x20\x81\x54\x4c\xb5\x5b\x80\x2c\x36\x8d\x1f\xa8\x3e" + \
b"\xd4\x89\xe9\x4e\x0f\xa0\x68\x8e\x32\x42\x8a\x5c\x78\xc4\x78\xc6\x8d\x05\x27\xb7\x1c\x9a\x3a\xbb\x0b\x0b\xe1\x2c\x44\x68\x96\x39" + \
b"\xe7\xd3\xce\x74\xdb\x10\x1a\x65\xaa\x2b\x87\xf6\x4c\x68\x26\xdb\x3e\xc7\x2f\x4b\x55\x99\x83\x4b\xb4\xed\xb0\x2f\x7c\x90\xe9\xa4" + \
b"\x96\xd3\xa5\x5d\x53\x5b\xeb\xfc\x45\xd4\xf6\x19\xf6\x3f\x3d\xed\xbb\x87\x39\x25\xc2\xf2\x24\xe0\x77\x31\x29\x6d\xa8\x87\xec\x1e" + \
b"\x47\x48\xf8\x7e\xfb\x5f\xde\xb7\x54\x84\x31\x6b\x22\x32\xde\xe5\x53\xdd\xaf\x02\x11\x2b\x0d\x1f\x02\xda\x30\x97\x32\x24\xfe\x27" + \
b"\xae\xda\x8b\x9d\x4b\x29\x22\xd9\xba\x8b\xe3\x9e\xd9\xe1\x03\xa6\x3c\x52\x81\x0b\xc6\x88\xb7\xe2\xed\x43\x16\xe1\xef\x17\xdb\xde" + \
b"\x04\x1e\x02\x1c\x2c\x85\x0e\x58\x66\xe6\x77\x61\x67\xcc\x9a\xc5\x88\xd0\x2a\xed\xe7\x3f\x6c\xa1\x62\x39\x4d\xf2\x6f\x63\x8b\x27"
certs = [b"\x30\x82\x03\xf2\x30\x82\x03\xa1\xa0\x03\x02\x01\x02\x02\x01\x00\x30\x09\x06\x07\x2a\x86\x48\xce\x38\x04\x03\x30\x12\x31\x10\x30" + \
b"\x0e\x06\x03\x55\x04\x03\x0c\x07\x44\x53\x41\x32\x30\x34\x38\x30\x1e\x17\x0d\x31\x36\x30\x35\x31\x35\x31\x38\x35\x31\x30\x32\x5a" + \
b"\x17\x0d\x31\x38\x30\x35\x31\x35\x31\x38\x35\x31\x30\x32\x5a\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x44\x53\x41\x32" + \
b"\x30\x34\x38\x30\x82\x03\x42\x30\x82\x02\x35\x06\x07\x2a\x86\x48\xce\x38\x04\x01\x30\x82\x02\x28\x02\x82\x01\x01\x00\x8f\x79\x35" + \
b"\xd9\xb9\xaa\xe9\xbf\xab\xed\x88\x7a\xcf\x49\x51\xb6\xf3\x2e\xc5\x9e\x3b\xaf\x37\x18\xe8\xea\xc4\x96\x1f\x3e\xfd\x36\x06\xe7\x43" + \
b"\x51\xa9\xc4\x18\x33\x39\xb8\x09\xe7\xc2\xae\x1c\x53\x9b\xa7\x47\x5b\x85\xd0\x11\xad\xb8\xb4\x79\x87\x75\x49\x84\x69\x5c\xac\x0e" + \
b"\x8f\x14\xb3\x36\x08\x28\xa2\x2f\xfa\x27\x11\x0a\x3d\x62\xa9\x93\x45\x34\x09\xa0\xfe\x69\x6c\x46\x58\xf8\x4b\xdd\x20\x81\x9c\x37" + \
b"\x09\xa0\x10\x57\xb1\x95\xad\xcd\x00\x23\x3d\xba\x54\x84\xb6\x29\x1f\x9d\x64\x8e\xf8\x83\x44\x86\x77\x97\x9c\xec\x04\xb4\x34\xa6" + \
b"\xac\x2e\x75\xe9\x98\x5d\xe2\x3d\xb0\x29\x2f\xc1\x11\x8c\x9f\xfa\x9d\x81\x81\xe7\x33\x8d\xb7\x92\xb7\x30\xd7\xb9\xe3\x49\x59\x2f" + \
b"\x68\x09\x98\x72\x15\x39\x15\xea\x3d\x6b\x8b\x46\x53\xc6\x33\x45\x8f\x80\x3b\x32\xa4\xc2\xe0\xf2\x72\x90\x25\x6e\x4e\x3f\x8a\x3b" + \
b"\x08\x38\xa1\xc4\x50\xe4\xe1\x8c\x1a\x29\xa3\x7d\xdf\x5e\xa1\x43\xde\x4b\x66\xff\x04\x90\x3e\xd5\xcf\x16\x23\xe1\x58\xd4\x87\xc6" + \
b"\x08\xe9\x7f\x21\x1c\xd8\x1d\xca\x23\xcb\x6e\x38\x07\x65\xf8\x22\xe3\x42\xbe\x48\x4c\x05\x76\x39\x39\x60\x1c\xd6\x67\x02\x1d\x00" + \
b"\xba\xf6\x96\xa6\x85\x78\xf7\xdf\xde\xe7\xfa\x67\xc9\x77\xc7\x85\xef\x32\xb2\x33\xba\xe5\x80\xc0\xbc\xd5\x69\x5d\x02\x82\x01\x00" + \
b"\x16\xa6\x5c\x58\x20\x48\x50\x70\x4e\x75\x02\xa3\x97\x57\x04\x0d\x34\xda\x3a\x34\x78\xc1\x54\xd4\xe4\xa5\xc0\x2d\x24\x2e\xe0\x4f" + \
b"\x96\xe6\x1e\x4b\xd0\x90\x4a\xbd\xac\x8f\x37\xee\xb1\xe0\x9f\x31\x82\xd2\x3c\x90\x43\xcb\x64\x2f\x88\x00\x41\x60\xed\xf9\xca\x09" + \
b"\xb3\x20\x76\xa7\x9c\x32\xa6\x27\xf2\x47\x3e\x91\x87\x9b\xa2\xc4\xe7\x44\xbd\x20\x81\x54\x4c\xb5\x5b\x80\x2c\x36\x8d\x1f\xa8\x3e" + \
b"\xd4\x89\xe9\x4e\x0f\xa0\x68\x8e\x32\x42\x8a\x5c\x78\xc4\x78\xc6\x8d\x05\x27\xb7\x1c\x9a\x3a\xbb\x0b\x0b\xe1\x2c\x44\x68\x96\x39" + \
b"\xe7\xd3\xce\x74\xdb\x10\x1a\x65\xaa\x2b\x87\xf6\x4c\x68\x26\xdb\x3e\xc7\x2f\x4b\x55\x99\x83\x4b\xb4\xed\xb0\x2f\x7c\x90\xe9\xa4" + \
b"\x96\xd3\xa5\x5d\x53\x5b\xeb\xfc\x45\xd4\xf6\x19\xf6\x3f\x3d\xed\xbb\x87\x39\x25\xc2\xf2\x24\xe0\x77\x31\x29\x6d\xa8\x87\xec\x1e" + \
b"\x47\x48\xf8\x7e\xfb\x5f\xde\xb7\x54\x84\x31\x6b\x22\x32\xde\xe5\x53\xdd\xaf\x02\x11\x2b\x0d\x1f\x02\xda\x30\x97\x32\x24\xfe\x27" + \
b"\xae\xda\x8b\x9d\x4b\x29\x22\xd9\xba\x8b\xe3\x9e\xd9\xe1\x03\xa6\x3c\x52\x81\x0b\xc6\x88\xb7\xe2\xed\x43\x16\xe1\xef\x17\xdb\xde" + \
b"\x03\x82\x01\x05\x00\x02\x82\x01\x00\x6b\x68\xe8\x2e\xa9\x71\x5e\xfd\xdb\x0c\x6c\xdc\x2b\xb3\xc3\x68\x75\x26\x80\x70\x29\x18\xb8" + \
b"\xcb\x19\x5d\x23\x76\xaa\xd2\xa3\xe7\xbc\x75\xb3\x93\x04\x16\x01\xb6\x35\xd6\xe6\xad\x07\xc0\x2a\xbc\xd4\x6c\x13\xae\x36\xc8\x53" + \
b"\xec\x0e\x27\x27\xcd\x46\x04\xb5\x79\x67\xf7\x0f\xcb\x42\x02\x86\x7f\x7b\xab\xc3\x82\xb6\xcf\x88\x28\x85\xe8\xc8\xda\x64\x04\xa1" + \
b"\xab\x7e\xae\xe0\x58\xa3\x78\x9f\x11\xd3\xbe\x03\x12\x2a\x2a\xe7\x0a\xfd\x6d\x3c\x32\xf9\x93\x29\x73\x22\xdd\x08\x9b\x5f\x92\x5e" + \
b"\x1d\xd3\x86\x17\x47\xe4\xb4\xa1\x0f\x9b\xa4\xdb\xc6\x4a\x90\x02\x18\x9b\x02\x52\x4b\x07\xf0\x0c\x9d\x75\xee\x37\x2b\xa5\x32\x3f" + \
b"\xb7\xdb\xed\x49\xfb\xf0\xd6\x68\x12\x78\xd1\x2b\x38\xfc\xb5\x9a\x70\xec\x90\x09\x5f\x41\xe5\xd9\xcd\x94\x8f\x5f\x74\xf3\x91\x85" + \
b"\x2a\x0d\x6d\x05\xbe\x73\x80\x73\xcf\x8f\x57\x00\xc0\xde\x22\x73\x12\xc5\x49\x91\xf4\x25\x21\x32\x6a\xbb\xb8\x29\xc2\x5d\x43\xd4" + \
b"\xab\x23\xd5\xdf\xe4\x46\xfe\xed\x13\x0f\xf5\x6c\xba\x9b\x81\xa2\xe2\x22\xb3\x4d\x9f\x92\xfd\xdf\xce\x0c\x54\xcc\xda\x92\xa6\x97" + \
b"\x05\x06\x2c\xde\xf6\x47\xe5\x81\x2f\x30\x09\x06\x07\x2a\x86\x48\xce\x38\x04\x03\x03\x40\x00\x30\x3d\x02\x1d\x00\x82\xbb\x22\x2b" + \
b"\xb4\xc4\x38\xc7\xe4\x1f\xaf\x4e\xc2\xb3\x22\xa3\xf7\x85\x3c\x8b\xb9\xa8\xf9\x47\xbe\xc3\xf6\x42\x02\x1c\x74\x52\xb0\x99\xfb\xc5" + \
b"\x0c\x0b\x09\xb9\xfa\xc8\x6b\xf6\x29\x49\xda\xf6\x97\x29\x75\x19\x50\x9a\x01\xf0\xa4\xd3"]
pyjks-20.0.0/tests/expected/RSA1024.py 0000664 0000000 0000000 00000012750 13646725476 0017233 0 ustar 00root root 0000000 0000000 public_key = b"\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xc4\x78\x0b" + \
b"\xdf\xf0\xc6\x38\x3d\xb7\x52\x66\x28\x48\xdc\x0f\x5a\x68\xe9\x79\x95\xb9\xcc\x59\xfc\x49\x6b\x0b\x1d\x3c\x29\x8f\x72\x1e\x89\x5e" + \
b"\x27\x9a\xe8\x2a\x83\x4f\x80\x87\xbb\xf6\x63\x2d\xdd\x0f\x57\xb5\x59\xbb\x81\xe7\xc5\x3d\x7b\xa1\x73\x64\xb1\xfe\xb8\xc7\xae\x4e" + \
b"\xc3\xdb\x91\xc4\x60\xad\x10\xe6\xe5\x7f\xdf\x79\xad\xb6\x86\x77\x0e\xb7\x89\x92\xef\x93\x72\xb7\xe3\xaa\x70\x3b\xf7\x8e\xc5\x43" + \
b"\x82\x1f\x0d\x55\x39\xb9\xfb\x46\xf5\xcc\xdd\x45\xb5\x08\x34\x38\xdb\x87\x8e\x3e\xda\x33\x75\x94\x9e\x30\xde\x00\x47\x02\x03\x01" + \
b"\x00\x01"
private_key = b"\x30\x82\x02\x76\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x04\x82\x02\x60\x30\x82\x02\x5c\x02\x01" + \
b"\x00\x02\x81\x81\x00\xc4\x78\x0b\xdf\xf0\xc6\x38\x3d\xb7\x52\x66\x28\x48\xdc\x0f\x5a\x68\xe9\x79\x95\xb9\xcc\x59\xfc\x49\x6b\x0b" + \
b"\x1d\x3c\x29\x8f\x72\x1e\x89\x5e\x27\x9a\xe8\x2a\x83\x4f\x80\x87\xbb\xf6\x63\x2d\xdd\x0f\x57\xb5\x59\xbb\x81\xe7\xc5\x3d\x7b\xa1" + \
b"\x73\x64\xb1\xfe\xb8\xc7\xae\x4e\xc3\xdb\x91\xc4\x60\xad\x10\xe6\xe5\x7f\xdf\x79\xad\xb6\x86\x77\x0e\xb7\x89\x92\xef\x93\x72\xb7" + \
b"\xe3\xaa\x70\x3b\xf7\x8e\xc5\x43\x82\x1f\x0d\x55\x39\xb9\xfb\x46\xf5\xcc\xdd\x45\xb5\x08\x34\x38\xdb\x87\x8e\x3e\xda\x33\x75\x94" + \
b"\x9e\x30\xde\x00\x47\x02\x03\x01\x00\x01\x02\x81\x80\x01\x67\xee\x85\xd2\xbe\x48\x66\xc0\xaa\x19\x4b\x0e\x82\x6c\xa5\xb3\xfc\x7f" + \
b"\xbf\x3f\x8e\x23\xd2\xb7\x5f\xc9\xbb\x92\xd3\xa5\x50\x26\xc1\xca\xe7\xb3\xa7\x17\xae\xca\xe1\xdb\x96\xcf\xc3\x93\xef\x72\x0d\xa9" + \
b"\xa1\x93\xc2\xf1\x3a\xab\x1e\xf8\x5f\xd0\x07\xaa\x0f\xfb\x40\x13\xe8\x0a\x84\xc3\x33\x0e\xad\x8a\x22\xf0\x2b\xed\x35\x30\x02\x59" + \
b"\x17\x2a\xc7\xe5\x68\x58\xb0\x6d\x8f\x69\x19\x81\x69\xff\x67\x1e\x85\xe2\x35\x89\xd8\xc7\x53\xc5\xa4\x57\x1a\x80\x4b\xa4\xa3\xa7" + \
b"\x21\x59\x49\x0f\xf1\x42\x50\xac\x35\x7c\x63\xfd\x31\x02\x41\x00\xe2\x3a\xed\x49\xd8\x99\x4b\xf9\x9b\xc5\x24\x61\xa7\x6c\xff\xd5" + \
b"\xd9\xf5\xd8\x66\xec\xce\xda\x52\xa4\x63\x63\x35\xd4\x52\xc3\x8e\xaf\xd4\x60\x2c\x04\x7b\xba\xb9\x9e\x5d\x7e\x58\x47\xad\x33\xb2" + \
b"\xe9\x5f\xc1\xe4\xeb\x9b\x79\x88\x72\x1c\x82\x1d\xa4\xe7\x73\x09\x02\x41\x00\xde\x52\x8b\x71\x0b\xcf\xaf\xa1\x8a\x25\xa6\xf6\x4d" + \
b"\xe0\x87\x6d\x4d\xce\xca\x96\x65\x2b\x0f\xd0\xbc\x6b\x71\x26\xa7\x37\xef\xac\xa7\xbf\x41\xc3\xf6\xdb\xce\xba\x6b\x02\x3c\x45\xc7" + \
b"\x52\x3d\x98\xce\xf9\xb5\x30\x3d\xd6\xce\xf8\x18\xb8\x4f\x01\x09\xcb\x1c\xcf\x02\x40\x31\x7d\x94\xa5\x80\x05\xe1\x32\x04\xda\xb6" + \
b"\xdf\xca\x21\xb5\x42\x12\x41\x8f\x0a\xcd\x29\x5f\x67\x8e\xe0\xd3\x36\x56\x71\x98\xa8\x61\x5c\xc3\x81\x3d\xa5\xd7\xae\x7d\xaf\x94" + \
b"\x51\x39\xb4\xf1\x47\x65\x78\x76\x51\x5f\x1d\x8f\x13\xc3\x6a\xeb\x28\x13\x08\x33\x09\x02\x40\x4d\xff\xe5\xde\x3e\x87\x9a\x15\xf1" + \
b"\xd2\xed\xf6\x02\x32\xa1\x30\xef\x18\x7b\x29\x32\xcb\x5d\xdc\x1d\x0f\x10\xfe\xbf\xb2\x37\x4b\x7a\xfa\xf6\x06\xdb\xc8\x18\x8a\x7c" + \
b"\xda\xa6\xec\xd0\x56\x81\x37\xe8\x7d\xe1\x5c\xd0\x85\x59\xcd\xdf\x56\x62\x99\x79\xa7\x22\x2f\x02\x41\x00\xcc\x59\xe0\x6e\x40\xba" + \
b"\x16\xdc\x4c\x81\xeb\x84\x1c\x2a\x84\xee\x10\xd9\xa5\x93\x28\xe2\x04\x30\x9d\xfa\x0b\x19\xbf\x37\xc4\xb1\x84\x73\x49\xb1\x84\xac" + \
b"\x27\xd3\x81\xc3\x0d\x44\xdd\x0e\x50\x68\x06\xc1\xaf\x80\x71\x7a\x36\x6e\x0a\xf1\x7c\xd9\xdd\x31\x74\xed"
certs = [b"\x30\x82\x01\x98\x30\x82\x01\x01\xa0\x03\x02\x01\x02\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30" + \
b"\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x52\x53\x41\x31\x30\x32\x34\x30\x1e\x17\x0d\x31\x36\x30\x35\x31\x35\x31\x38\x35" + \
b"\x31\x30\x31\x5a\x17\x0d\x31\x38\x30\x35\x31\x35\x31\x38\x35\x31\x30\x31\x5a\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07" + \
b"\x52\x53\x41\x31\x30\x32\x34\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89" + \
b"\x02\x81\x81\x00\xc4\x78\x0b\xdf\xf0\xc6\x38\x3d\xb7\x52\x66\x28\x48\xdc\x0f\x5a\x68\xe9\x79\x95\xb9\xcc\x59\xfc\x49\x6b\x0b\x1d" + \
b"\x3c\x29\x8f\x72\x1e\x89\x5e\x27\x9a\xe8\x2a\x83\x4f\x80\x87\xbb\xf6\x63\x2d\xdd\x0f\x57\xb5\x59\xbb\x81\xe7\xc5\x3d\x7b\xa1\x73" + \
b"\x64\xb1\xfe\xb8\xc7\xae\x4e\xc3\xdb\x91\xc4\x60\xad\x10\xe6\xe5\x7f\xdf\x79\xad\xb6\x86\x77\x0e\xb7\x89\x92\xef\x93\x72\xb7\xe3" + \
b"\xaa\x70\x3b\xf7\x8e\xc5\x43\x82\x1f\x0d\x55\x39\xb9\xfb\x46\xf5\xcc\xdd\x45\xb5\x08\x34\x38\xdb\x87\x8e\x3e\xda\x33\x75\x94\x9e" + \
b"\x30\xde\x00\x47\x02\x03\x01\x00\x01\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x81\x81\x00\xbb\x77\x85\xb5" + \
b"\x4f\x1c\x4b\x39\x27\x15\x10\x5e\x41\x02\x91\xae\xcb\x97\x1a\xf0\xe8\xae\xf7\xd7\x01\x46\x84\x82\x04\x97\x3f\x77\x90\xf5\x90\xe8" + \
b"\x08\xd0\xc6\xa7\x65\xeb\xc2\xca\xdf\x93\xf0\xd9\xcd\xed\xf4\x1f\xe3\x10\x1d\xc5\x20\x6a\xd7\xc1\xd0\x27\x29\xfc\x3f\x7c\x98\x1d" + \
b"\xf6\x2c\x3d\xca\xb1\xc5\xb1\x3d\x4b\x74\xc6\xdc\xac\xda\xa3\xd3\xe0\x2e\xaf\xb0\x0e\x57\x24\x1b\xb1\x90\x5e\xee\xba\x8c\x2d\xd9" + \
b"\x02\x1b\x93\x09\xe1\x60\xc3\x0c\xf0\x7a\x85\xed\x1c\x2d\x95\xe8\x13\x66\x33\xbd\x61\x25\x60\xe3\x64\x66\x42\x61"]
pyjks-20.0.0/tests/expected/RSA2048_3certs.py 0000664 0000000 0000000 00000053471 13646725476 0020532 0 ustar 00root root 0000000 0000000 public_key = b"\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01" + \
b"\x00\xc0\x86\x99\x9a\x76\x74\x9a\xf5\x04\xb8\x48\xf0\x7e\x67\xc3\x90\x51\x17\xe6\xcd\xb3\x97\x6b\x41\xbc\x4e\x4e\x0c\x30\xff\x57" + \
b"\xf1\x47\x99\xd5\xc3\x2a\x67\x26\x6c\x28\x1e\x18\x03\x0f\xaf\xd2\x70\xd2\xb2\xab\xae\x44\x7a\x1c\xcd\xce\x6f\xcb\xfb\x56\x96\x1b" + \
b"\x12\x52\x11\x43\x01\x26\x07\x4c\xf9\x3e\x51\x6b\xd3\x9a\x7d\xd8\x26\x92\xa1\xab\x5a\x6e\x2f\x66\x61\x31\x62\x23\x0f\x8b\x0a\x6a" + \
b"\x17\xe7\xd0\x0a\xc1\x69\x8a\x60\xcf\xba\x6c\x30\x33\x88\x2c\xaf\x13\x9a\xa6\x49\x83\x74\xb4\xdf\x23\xad\xe4\x01\x21\x52\x51\xfe" + \
b"\xcc\x4f\x4e\x3e\xd1\xa8\xce\x92\xc0\x34\x0e\x1d\x6c\xa7\x4d\x6f\x7d\xd7\x65\xb8\xf4\x50\xa1\x58\xa6\x8a\xae\x9e\x21\x50\x84\x01" + \
b"\x38\x1d\x85\x6e\xcb\x7d\xc9\xb5\x6b\xda\x94\xe5\x1c\xa8\xf5\x15\x5e\x8f\xbc\x95\xe0\x76\xcb\x0d\x7e\x79\x11\x0c\x81\xd2\x60\xf1" + \
b"\xaf\x84\xcc\xa0\x3b\x06\xe8\xa2\x42\x9f\xb3\xd4\x94\x8e\xd0\xbc\xe8\x7e\xfa\x01\x6b\x0d\xd0\x16\x60\x47\x86\x9c\xb9\x7f\x12\xd9" + \
b"\x21\x5c\xf7\x8e\x35\x7e\x85\x18\xfa\xc8\x86\xed\xbe\xb3\x0e\x4a\x56\xd7\xd6\x92\x3d\x23\x3c\xbc\xcc\xef\x3a\x33\xd8\x48\xd8\xca" + \
b"\x55\x02\x03\x01\x00\x01"
raw_private_key = b"\x30\x82\x04\xa3\x02\x01\x00\x02\x82\x01\x01\x00\xc0\x86\x99\x9a\x76\x74\x9a\xf5\x04\xb8\x48\xf0\x7e\x67\xc3\x90\x51\x17\xe6\xcd" + \
b"\xb3\x97\x6b\x41\xbc\x4e\x4e\x0c\x30\xff\x57\xf1\x47\x99\xd5\xc3\x2a\x67\x26\x6c\x28\x1e\x18\x03\x0f\xaf\xd2\x70\xd2\xb2\xab\xae" + \
b"\x44\x7a\x1c\xcd\xce\x6f\xcb\xfb\x56\x96\x1b\x12\x52\x11\x43\x01\x26\x07\x4c\xf9\x3e\x51\x6b\xd3\x9a\x7d\xd8\x26\x92\xa1\xab\x5a" + \
b"\x6e\x2f\x66\x61\x31\x62\x23\x0f\x8b\x0a\x6a\x17\xe7\xd0\x0a\xc1\x69\x8a\x60\xcf\xba\x6c\x30\x33\x88\x2c\xaf\x13\x9a\xa6\x49\x83" + \
b"\x74\xb4\xdf\x23\xad\xe4\x01\x21\x52\x51\xfe\xcc\x4f\x4e\x3e\xd1\xa8\xce\x92\xc0\x34\x0e\x1d\x6c\xa7\x4d\x6f\x7d\xd7\x65\xb8\xf4" + \
b"\x50\xa1\x58\xa6\x8a\xae\x9e\x21\x50\x84\x01\x38\x1d\x85\x6e\xcb\x7d\xc9\xb5\x6b\xda\x94\xe5\x1c\xa8\xf5\x15\x5e\x8f\xbc\x95\xe0" + \
b"\x76\xcb\x0d\x7e\x79\x11\x0c\x81\xd2\x60\xf1\xaf\x84\xcc\xa0\x3b\x06\xe8\xa2\x42\x9f\xb3\xd4\x94\x8e\xd0\xbc\xe8\x7e\xfa\x01\x6b" + \
b"\x0d\xd0\x16\x60\x47\x86\x9c\xb9\x7f\x12\xd9\x21\x5c\xf7\x8e\x35\x7e\x85\x18\xfa\xc8\x86\xed\xbe\xb3\x0e\x4a\x56\xd7\xd6\x92\x3d" + \
b"\x23\x3c\xbc\xcc\xef\x3a\x33\xd8\x48\xd8\xca\x55\x02\x03\x01\x00\x01\x02\x82\x01\x00\x72\xd6\xd7\x00\xdf\xef\xa6\x0e\xc9\x05\xf3" + \
b"\xdc\x20\x4a\x5c\xc8\xd4\xd6\x61\x02\x0a\x42\x23\xe4\x4e\x22\x97\x43\x86\x66\x89\x5a\x8e\xcf\x20\x47\x0e\x20\x01\x37\x3a\xa6\xd8" + \
b"\xc3\xda\xb3\x91\xdf\x62\x8e\xd9\x01\x56\x2f\x50\xc7\x22\x80\x65\x38\x42\xe9\xbe\xb4\x2a\xe7\xc9\x04\x02\x5f\x10\x77\x0b\xc9\x1d" + \
b"\x7c\x57\x07\x01\xcb\xe0\x63\x37\x06\xf5\xfa\xa8\x23\x09\x85\xf4\x4b\xed\x30\x05\x20\xc5\x51\xbf\x58\xa7\x4f\xb7\x77\xb1\x47\x9f" + \
b"\x68\xdd\xad\x69\xb1\x53\xea\x24\xa7\x99\x11\xda\x98\x39\xbd\x6b\x3f\x29\x0f\x67\xe0\x04\x6d\x58\x02\xd2\x96\xd9\x2f\x88\x40\xbe" + \
b"\x4b\x3e\x82\x81\xff\xa5\x1f\xf8\x76\x43\x8f\x31\x84\xbf\xc9\xd2\x3f\xa6\xc2\x74\x8a\x93\x61\xd1\x00\x06\x28\xee\x29\xb9\x14\x2c" + \
b"\x6f\xb3\xed\xe0\xb3\xff\x54\x5e\x0d\x7c\x91\xcb\xae\x8b\xf2\x7f\x41\x47\x72\x89\xcc\xa8\x19\x7f\x0c\x24\x76\xbd\xde\x07\x09\xff" + \
b"\x7f\xf7\xb8\x97\x7d\x76\xc1\xf4\xff\x87\xda\x0a\x57\xcc\x92\x0d\xc2\x06\x5e\xa1\xb8\x24\x84\x0a\x73\x6b\x15\xa6\x0f\xb8\x09\x5a" + \
b"\x81\x0f\x21\x50\x52\x66\xcd\x78\xab\xfa\x14\xb7\xd8\x51\x3f\xce\x46\x8a\x27\x9a\x81\x02\x81\x81\x00\xf7\xb2\x3c\x6f\x9c\xae\xa2" + \
b"\x75\x95\xbb\xae\xd2\x78\xad\xc3\xb6\x44\x18\xad\x92\x01\xc5\xdd\x6f\x33\x04\x2a\x4f\x00\xb9\x3f\x18\xf4\x39\xe3\xc7\x08\x58\xc7" + \
b"\x9f\x35\x31\x13\x56\x88\x33\xc5\x5d\xeb\x6d\x8c\xdf\x1d\x45\x8b\xdb\xf8\x09\x25\x4a\x80\x32\xec\xef\xc0\x24\x56\xf7\xd3\x2a\x6e" + \
b"\xd0\x2e\x73\x07\xb5\xb6\x37\x67\x10\x28\xfa\xfc\xb1\xef\x59\xed\xfc\x79\x4d\xd4\xa5\x4f\xb3\xf2\x15\xfb\x26\x0a\xa8\x2a\xdd\x40" + \
b"\x0d\xf6\xdb\xb4\x8b\x47\x60\xec\x4c\xa6\xe5\x15\x1f\x2b\x70\xa5\xbf\xfc\x54\xae\x9d\x91\xfd\x01\x7d\x02\x81\x81\x00\xc6\xfa\xe2" + \
b"\x22\xe6\xbb\xed\xd6\xfc\x69\x48\x83\xf0\x08\x86\x55\x58\xae\x0f\x16\xc9\xef\xb2\x4d\xc8\x5d\x4b\xc2\xa4\x05\x26\x92\x42\xce\x5b" + \
b"\x40\xa8\xd4\x26\xe9\x98\x98\x32\xe0\xd1\x60\x05\x84\xd8\x89\xd5\x42\x3e\x27\x20\x22\xdc\x2e\x6a\x1c\xb0\x88\xb2\x37\x12\x7e\x61" + \
b"\x60\x84\x60\x97\xa2\xec\x35\xe2\xfd\xf8\x6b\xf8\xae\xfc\x70\xc8\x84\xa5\x46\x63\xff\x2c\x46\x48\x61\x2b\x91\x75\x21\xd0\xf6\x02" + \
b"\x7e\xa5\xd2\x39\x06\x30\x17\xea\x1a\x7f\xb3\x57\xe2\xcc\xda\xc1\x58\x34\xee\x50\x54\xd8\x8b\x41\xc3\x4a\x4b\x43\xb9\x02\x81\x81" + \
b"\x00\xae\x6a\x6f\x6c\x18\x64\x50\x39\x84\x4a\x38\x7c\x34\x46\x07\x7e\x1c\xcd\x53\xcb\x70\x3c\x28\x04\xd9\x63\xa1\x77\x28\x07\x49" + \
b"\x8b\x04\xce\x8e\xb9\xe4\x02\xbf\xee\x37\xc2\x6a\xdf\x8f\xe1\x04\xa5\x71\xd6\x1e\x50\x2d\x88\x7f\x47\x51\x8c\xff\x19\x4a\xd4\x91" + \
b"\x4a\xf1\x7d\xa5\x4f\xb4\xfe\x38\x31\x97\xc3\xa0\x36\x30\x2d\x2b\x01\x92\x19\xca\x3e\x71\x50\x5d\xe8\x5e\x72\x93\xbe\x24\x35\x8d" + \
b"\xce\x34\x9f\x40\xf9\xd1\xd5\x21\xf9\xb3\x4e\x59\xff\x89\x2f\x92\xb5\x17\x00\x50\xb3\x36\x1f\x88\x57\x7c\x13\x15\x32\x17\x4e\x94" + \
b"\xf1\x02\x81\x80\x59\xcd\x9d\x05\xf7\x70\xd5\xb4\xf3\x92\x68\xc1\xf3\x31\x45\xbf\x7b\x18\x83\x82\xdb\x7c\xac\xd2\x62\x1d\x89\x35" + \
b"\xbd\x64\xfd\xb5\x81\x25\x35\x16\x07\x9c\x48\x3b\xa1\x3c\xff\xa9\x6b\x95\x94\xa8\x12\x3a\x92\xdf\x24\xc1\xef\xc5\x0b\xee\x7e\xc1" + \
b"\x98\x02\xf9\xbb\xd5\x42\xe8\x9b\xf0\xe2\xcf\x4d\x1e\xa2\x6b\x62\x08\x1e\x62\xcc\x46\xee\x77\xf1\x35\xce\x81\x0f\x07\x62\x69\x04" + \
b"\x41\xef\x92\x17\xc3\x01\x64\xba\xd8\x07\xfa\xe8\x8a\x08\x21\x05\xf8\xa0\x6e\x87\xd3\xc0\xdf\x05\xfa\x4d\x9c\x3f\xce\xc3\x7a\xd8" + \
b"\xb2\xcd\x29\x31\x02\x81\x80\x59\x7c\x94\xf2\x28\x9c\x8c\x24\x1c\xc3\x84\xac\xf6\x49\xf6\xc7\xb1\x98\x66\xf5\x6c\x59\x31\xe5\x30" + \
b"\xb2\xc1\xc0\xcc\x15\x94\x4d\x5a\xa0\x5c\xcb\x30\x46\xaa\xdc\x25\x60\xe2\x64\x5f\x1e\x35\xc3\x82\x3c\x47\x06\xc7\x4f\x39\xcc\x4f" + \
b"\xda\xf7\xf5\x28\x30\xde\x9a\xa3\xb9\xf6\xa4\x4a\x43\xff\x9e\x1a\x01\xcb\x03\x51\x37\xd4\xb8\xea\xab\xd5\xb6\x36\xf9\x76\x1e\x08" + \
b"\x9e\xcd\x58\x71\x8b\x6d\xa6\xf4\x43\xa3\x63\xf8\xd5\x72\xc1\x4b\x76\x78\x03\x15\x8d\xbf\xb3\x1f\x63\x3f\x99\xec\xfb\xd1\x5d\x09" + \
b"\x1d\xa6\xe6\xd0\xc6\x3c\xf9"
private_key = b"\x30\x82\x04\xbd\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x04\x82\x04\xa7\x30\x82\x04\xa3\x02\x01" + \
b"\x00\x02\x82\x01\x01\x00\xc0\x86\x99\x9a\x76\x74\x9a\xf5\x04\xb8\x48\xf0\x7e\x67\xc3\x90\x51\x17\xe6\xcd\xb3\x97\x6b\x41\xbc\x4e" + \
b"\x4e\x0c\x30\xff\x57\xf1\x47\x99\xd5\xc3\x2a\x67\x26\x6c\x28\x1e\x18\x03\x0f\xaf\xd2\x70\xd2\xb2\xab\xae\x44\x7a\x1c\xcd\xce\x6f" + \
b"\xcb\xfb\x56\x96\x1b\x12\x52\x11\x43\x01\x26\x07\x4c\xf9\x3e\x51\x6b\xd3\x9a\x7d\xd8\x26\x92\xa1\xab\x5a\x6e\x2f\x66\x61\x31\x62" + \
b"\x23\x0f\x8b\x0a\x6a\x17\xe7\xd0\x0a\xc1\x69\x8a\x60\xcf\xba\x6c\x30\x33\x88\x2c\xaf\x13\x9a\xa6\x49\x83\x74\xb4\xdf\x23\xad\xe4" + \
b"\x01\x21\x52\x51\xfe\xcc\x4f\x4e\x3e\xd1\xa8\xce\x92\xc0\x34\x0e\x1d\x6c\xa7\x4d\x6f\x7d\xd7\x65\xb8\xf4\x50\xa1\x58\xa6\x8a\xae" + \
b"\x9e\x21\x50\x84\x01\x38\x1d\x85\x6e\xcb\x7d\xc9\xb5\x6b\xda\x94\xe5\x1c\xa8\xf5\x15\x5e\x8f\xbc\x95\xe0\x76\xcb\x0d\x7e\x79\x11" + \
b"\x0c\x81\xd2\x60\xf1\xaf\x84\xcc\xa0\x3b\x06\xe8\xa2\x42\x9f\xb3\xd4\x94\x8e\xd0\xbc\xe8\x7e\xfa\x01\x6b\x0d\xd0\x16\x60\x47\x86" + \
b"\x9c\xb9\x7f\x12\xd9\x21\x5c\xf7\x8e\x35\x7e\x85\x18\xfa\xc8\x86\xed\xbe\xb3\x0e\x4a\x56\xd7\xd6\x92\x3d\x23\x3c\xbc\xcc\xef\x3a" + \
b"\x33\xd8\x48\xd8\xca\x55\x02\x03\x01\x00\x01\x02\x82\x01\x00\x72\xd6\xd7\x00\xdf\xef\xa6\x0e\xc9\x05\xf3\xdc\x20\x4a\x5c\xc8\xd4" + \
b"\xd6\x61\x02\x0a\x42\x23\xe4\x4e\x22\x97\x43\x86\x66\x89\x5a\x8e\xcf\x20\x47\x0e\x20\x01\x37\x3a\xa6\xd8\xc3\xda\xb3\x91\xdf\x62" + \
b"\x8e\xd9\x01\x56\x2f\x50\xc7\x22\x80\x65\x38\x42\xe9\xbe\xb4\x2a\xe7\xc9\x04\x02\x5f\x10\x77\x0b\xc9\x1d\x7c\x57\x07\x01\xcb\xe0" + \
b"\x63\x37\x06\xf5\xfa\xa8\x23\x09\x85\xf4\x4b\xed\x30\x05\x20\xc5\x51\xbf\x58\xa7\x4f\xb7\x77\xb1\x47\x9f\x68\xdd\xad\x69\xb1\x53" + \
b"\xea\x24\xa7\x99\x11\xda\x98\x39\xbd\x6b\x3f\x29\x0f\x67\xe0\x04\x6d\x58\x02\xd2\x96\xd9\x2f\x88\x40\xbe\x4b\x3e\x82\x81\xff\xa5" + \
b"\x1f\xf8\x76\x43\x8f\x31\x84\xbf\xc9\xd2\x3f\xa6\xc2\x74\x8a\x93\x61\xd1\x00\x06\x28\xee\x29\xb9\x14\x2c\x6f\xb3\xed\xe0\xb3\xff" + \
b"\x54\x5e\x0d\x7c\x91\xcb\xae\x8b\xf2\x7f\x41\x47\x72\x89\xcc\xa8\x19\x7f\x0c\x24\x76\xbd\xde\x07\x09\xff\x7f\xf7\xb8\x97\x7d\x76" + \
b"\xc1\xf4\xff\x87\xda\x0a\x57\xcc\x92\x0d\xc2\x06\x5e\xa1\xb8\x24\x84\x0a\x73\x6b\x15\xa6\x0f\xb8\x09\x5a\x81\x0f\x21\x50\x52\x66" + \
b"\xcd\x78\xab\xfa\x14\xb7\xd8\x51\x3f\xce\x46\x8a\x27\x9a\x81\x02\x81\x81\x00\xf7\xb2\x3c\x6f\x9c\xae\xa2\x75\x95\xbb\xae\xd2\x78" + \
b"\xad\xc3\xb6\x44\x18\xad\x92\x01\xc5\xdd\x6f\x33\x04\x2a\x4f\x00\xb9\x3f\x18\xf4\x39\xe3\xc7\x08\x58\xc7\x9f\x35\x31\x13\x56\x88" + \
b"\x33\xc5\x5d\xeb\x6d\x8c\xdf\x1d\x45\x8b\xdb\xf8\x09\x25\x4a\x80\x32\xec\xef\xc0\x24\x56\xf7\xd3\x2a\x6e\xd0\x2e\x73\x07\xb5\xb6" + \
b"\x37\x67\x10\x28\xfa\xfc\xb1\xef\x59\xed\xfc\x79\x4d\xd4\xa5\x4f\xb3\xf2\x15\xfb\x26\x0a\xa8\x2a\xdd\x40\x0d\xf6\xdb\xb4\x8b\x47" + \
b"\x60\xec\x4c\xa6\xe5\x15\x1f\x2b\x70\xa5\xbf\xfc\x54\xae\x9d\x91\xfd\x01\x7d\x02\x81\x81\x00\xc6\xfa\xe2\x22\xe6\xbb\xed\xd6\xfc" + \
b"\x69\x48\x83\xf0\x08\x86\x55\x58\xae\x0f\x16\xc9\xef\xb2\x4d\xc8\x5d\x4b\xc2\xa4\x05\x26\x92\x42\xce\x5b\x40\xa8\xd4\x26\xe9\x98" + \
b"\x98\x32\xe0\xd1\x60\x05\x84\xd8\x89\xd5\x42\x3e\x27\x20\x22\xdc\x2e\x6a\x1c\xb0\x88\xb2\x37\x12\x7e\x61\x60\x84\x60\x97\xa2\xec" + \
b"\x35\xe2\xfd\xf8\x6b\xf8\xae\xfc\x70\xc8\x84\xa5\x46\x63\xff\x2c\x46\x48\x61\x2b\x91\x75\x21\xd0\xf6\x02\x7e\xa5\xd2\x39\x06\x30" + \
b"\x17\xea\x1a\x7f\xb3\x57\xe2\xcc\xda\xc1\x58\x34\xee\x50\x54\xd8\x8b\x41\xc3\x4a\x4b\x43\xb9\x02\x81\x81\x00\xae\x6a\x6f\x6c\x18" + \
b"\x64\x50\x39\x84\x4a\x38\x7c\x34\x46\x07\x7e\x1c\xcd\x53\xcb\x70\x3c\x28\x04\xd9\x63\xa1\x77\x28\x07\x49\x8b\x04\xce\x8e\xb9\xe4" + \
b"\x02\xbf\xee\x37\xc2\x6a\xdf\x8f\xe1\x04\xa5\x71\xd6\x1e\x50\x2d\x88\x7f\x47\x51\x8c\xff\x19\x4a\xd4\x91\x4a\xf1\x7d\xa5\x4f\xb4" + \
b"\xfe\x38\x31\x97\xc3\xa0\x36\x30\x2d\x2b\x01\x92\x19\xca\x3e\x71\x50\x5d\xe8\x5e\x72\x93\xbe\x24\x35\x8d\xce\x34\x9f\x40\xf9\xd1" + \
b"\xd5\x21\xf9\xb3\x4e\x59\xff\x89\x2f\x92\xb5\x17\x00\x50\xb3\x36\x1f\x88\x57\x7c\x13\x15\x32\x17\x4e\x94\xf1\x02\x81\x80\x59\xcd" + \
b"\x9d\x05\xf7\x70\xd5\xb4\xf3\x92\x68\xc1\xf3\x31\x45\xbf\x7b\x18\x83\x82\xdb\x7c\xac\xd2\x62\x1d\x89\x35\xbd\x64\xfd\xb5\x81\x25" + \
b"\x35\x16\x07\x9c\x48\x3b\xa1\x3c\xff\xa9\x6b\x95\x94\xa8\x12\x3a\x92\xdf\x24\xc1\xef\xc5\x0b\xee\x7e\xc1\x98\x02\xf9\xbb\xd5\x42" + \
b"\xe8\x9b\xf0\xe2\xcf\x4d\x1e\xa2\x6b\x62\x08\x1e\x62\xcc\x46\xee\x77\xf1\x35\xce\x81\x0f\x07\x62\x69\x04\x41\xef\x92\x17\xc3\x01" + \
b"\x64\xba\xd8\x07\xfa\xe8\x8a\x08\x21\x05\xf8\xa0\x6e\x87\xd3\xc0\xdf\x05\xfa\x4d\x9c\x3f\xce\xc3\x7a\xd8\xb2\xcd\x29\x31\x02\x81" + \
b"\x80\x59\x7c\x94\xf2\x28\x9c\x8c\x24\x1c\xc3\x84\xac\xf6\x49\xf6\xc7\xb1\x98\x66\xf5\x6c\x59\x31\xe5\x30\xb2\xc1\xc0\xcc\x15\x94" + \
b"\x4d\x5a\xa0\x5c\xcb\x30\x46\xaa\xdc\x25\x60\xe2\x64\x5f\x1e\x35\xc3\x82\x3c\x47\x06\xc7\x4f\x39\xcc\x4f\xda\xf7\xf5\x28\x30\xde" + \
b"\x9a\xa3\xb9\xf6\xa4\x4a\x43\xff\x9e\x1a\x01\xcb\x03\x51\x37\xd4\xb8\xea\xab\xd5\xb6\x36\xf9\x76\x1e\x08\x9e\xcd\x58\x71\x8b\x6d" + \
b"\xa6\xf4\x43\xa3\x63\xf8\xd5\x72\xc1\x4b\x76\x78\x03\x15\x8d\xbf\xb3\x1f\x63\x3f\x99\xec\xfb\xd1\x5d\x09\x1d\xa6\xe6\xd0\xc6\x3c" + \
b"\xf9"
certs = [b"\x30\x82\x02\xb5\x30\x82\x01\x9d\xa0\x03\x02\x01\x02\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30" + \
b"\x1e\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x52\x53\x41\x32\x30\x34\x38\x31\x0a\x30\x08\x06\x03\x55\x04\x0a\x0c\x01\x31\x30" + \
b"\x1e\x17\x0d\x31\x36\x30\x35\x31\x35\x31\x38\x35\x38\x30\x34\x5a\x17\x0d\x31\x38\x30\x35\x31\x35\x31\x38\x35\x38\x30\x34\x5a\x30" + \
b"\x1e\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x52\x53\x41\x32\x30\x34\x38\x31\x0a\x30\x08\x06\x03\x55\x04\x0a\x0c\x01\x31\x30" + \
b"\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00" + \
b"\xc0\x86\x99\x9a\x76\x74\x9a\xf5\x04\xb8\x48\xf0\x7e\x67\xc3\x90\x51\x17\xe6\xcd\xb3\x97\x6b\x41\xbc\x4e\x4e\x0c\x30\xff\x57\xf1" + \
b"\x47\x99\xd5\xc3\x2a\x67\x26\x6c\x28\x1e\x18\x03\x0f\xaf\xd2\x70\xd2\xb2\xab\xae\x44\x7a\x1c\xcd\xce\x6f\xcb\xfb\x56\x96\x1b\x12" + \
b"\x52\x11\x43\x01\x26\x07\x4c\xf9\x3e\x51\x6b\xd3\x9a\x7d\xd8\x26\x92\xa1\xab\x5a\x6e\x2f\x66\x61\x31\x62\x23\x0f\x8b\x0a\x6a\x17" + \
b"\xe7\xd0\x0a\xc1\x69\x8a\x60\xcf\xba\x6c\x30\x33\x88\x2c\xaf\x13\x9a\xa6\x49\x83\x74\xb4\xdf\x23\xad\xe4\x01\x21\x52\x51\xfe\xcc" + \
b"\x4f\x4e\x3e\xd1\xa8\xce\x92\xc0\x34\x0e\x1d\x6c\xa7\x4d\x6f\x7d\xd7\x65\xb8\xf4\x50\xa1\x58\xa6\x8a\xae\x9e\x21\x50\x84\x01\x38" + \
b"\x1d\x85\x6e\xcb\x7d\xc9\xb5\x6b\xda\x94\xe5\x1c\xa8\xf5\x15\x5e\x8f\xbc\x95\xe0\x76\xcb\x0d\x7e\x79\x11\x0c\x81\xd2\x60\xf1\xaf" + \
b"\x84\xcc\xa0\x3b\x06\xe8\xa2\x42\x9f\xb3\xd4\x94\x8e\xd0\xbc\xe8\x7e\xfa\x01\x6b\x0d\xd0\x16\x60\x47\x86\x9c\xb9\x7f\x12\xd9\x21" + \
b"\x5c\xf7\x8e\x35\x7e\x85\x18\xfa\xc8\x86\xed\xbe\xb3\x0e\x4a\x56\xd7\xd6\x92\x3d\x23\x3c\xbc\xcc\xef\x3a\x33\xd8\x48\xd8\xca\x55" + \
b"\x02\x03\x01\x00\x01\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x19\x9d\xca\x8f\x2b\x4a\xfd" + \
b"\xb6\x30\x4c\x1a\x45\xe1\x8a\x30\x72\x5e\xf3\xb4\x3c\x81\xf7\x00\xd2\x72\x44\x65\x49\x78\x3a\x62\xc9\x5d\xad\xb7\x39\x6c\x40\x50" + \
b"\x85\xd3\xc9\x0e\x69\x7a\xcc\x3e\x12\x68\x7e\x84\xe2\xcd\x33\xb7\x08\xf0\x6e\xda\x7f\xfb\x40\x96\xd1\x5f\x1f\x6e\x42\xa5\x42\xd2" + \
b"\xf5\x45\x27\x6f\xcc\x3a\x49\xc6\x61\xf0\xd7\x22\x0e\x2e\xc3\xb7\x77\xbf\xc7\x35\x63\x47\x6e\x80\x4a\x5a\x0b\xd1\x7e\xb5\xd3\x2a" + \
b"\x1d\xdf\x34\x72\xbd\x36\x01\x0b\x07\xe5\xcf\xef\x4c\x3d\xba\xe8\x5d\x74\x62\xe7\xd4\xb7\x76\xfd\xc8\x1e\x04\x0c\x0d\x0c\xdf\x0e" + \
b"\xbe\x72\x8f\x1c\xa0\xfa\x88\x23\x2c\xed\x86\x3b\xd0\xb3\x67\x62\x84\x2a\x23\xe5\x05\xbf\xd1\x95\xd7\xac\x82\xd9\x02\x51\x61\xa3" + \
b"\x10\xc7\xb1\x52\xbc\xe7\xa9\xc2\xf7\x8c\x82\x26\xcd\xf2\x76\x48\x2e\xa0\xfa\xd3\x8d\xe5\x19\xd9\x55\x73\x4e\x1a\x96\xb4\x86\x75" + \
b"\x8e\xda\xaa\x69\xd0\x15\x7c\x42\x9c\x8e\xeb\x9c\x94\xa0\xf6\x74\xbd\x3a\x9b\xda\x9a\x34\xef\xfe\xd6\x2b\x74\x43\x61\xc2\xd0\xf5" + \
b"\xb5\x2f\x51\x9d\xcb\xb9\x04\x6b\x21\x81\x01\x24\xfb\xf1\xbe\x1a\x14\xf0\x68\xdf\x4a\x31\x2f\x91\x2e",
b"\x30\x82\x02\xb5\x30\x82\x01\x9d\xa0\x03\x02\x01\x02\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30" + \
b"\x1e\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x52\x53\x41\x32\x30\x34\x38\x31\x0a\x30\x08\x06\x03\x55\x04\x0a\x0c\x01\x32\x30" + \
b"\x1e\x17\x0d\x31\x36\x30\x35\x31\x35\x31\x38\x35\x38\x30\x34\x5a\x17\x0d\x31\x38\x30\x35\x31\x35\x31\x38\x35\x38\x30\x34\x5a\x30" + \
b"\x1e\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x52\x53\x41\x32\x30\x34\x38\x31\x0a\x30\x08\x06\x03\x55\x04\x0a\x0c\x01\x32\x30" + \
b"\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00" + \
b"\xc0\x86\x99\x9a\x76\x74\x9a\xf5\x04\xb8\x48\xf0\x7e\x67\xc3\x90\x51\x17\xe6\xcd\xb3\x97\x6b\x41\xbc\x4e\x4e\x0c\x30\xff\x57\xf1" + \
b"\x47\x99\xd5\xc3\x2a\x67\x26\x6c\x28\x1e\x18\x03\x0f\xaf\xd2\x70\xd2\xb2\xab\xae\x44\x7a\x1c\xcd\xce\x6f\xcb\xfb\x56\x96\x1b\x12" + \
b"\x52\x11\x43\x01\x26\x07\x4c\xf9\x3e\x51\x6b\xd3\x9a\x7d\xd8\x26\x92\xa1\xab\x5a\x6e\x2f\x66\x61\x31\x62\x23\x0f\x8b\x0a\x6a\x17" + \
b"\xe7\xd0\x0a\xc1\x69\x8a\x60\xcf\xba\x6c\x30\x33\x88\x2c\xaf\x13\x9a\xa6\x49\x83\x74\xb4\xdf\x23\xad\xe4\x01\x21\x52\x51\xfe\xcc" + \
b"\x4f\x4e\x3e\xd1\xa8\xce\x92\xc0\x34\x0e\x1d\x6c\xa7\x4d\x6f\x7d\xd7\x65\xb8\xf4\x50\xa1\x58\xa6\x8a\xae\x9e\x21\x50\x84\x01\x38" + \
b"\x1d\x85\x6e\xcb\x7d\xc9\xb5\x6b\xda\x94\xe5\x1c\xa8\xf5\x15\x5e\x8f\xbc\x95\xe0\x76\xcb\x0d\x7e\x79\x11\x0c\x81\xd2\x60\xf1\xaf" + \
b"\x84\xcc\xa0\x3b\x06\xe8\xa2\x42\x9f\xb3\xd4\x94\x8e\xd0\xbc\xe8\x7e\xfa\x01\x6b\x0d\xd0\x16\x60\x47\x86\x9c\xb9\x7f\x12\xd9\x21" + \
b"\x5c\xf7\x8e\x35\x7e\x85\x18\xfa\xc8\x86\xed\xbe\xb3\x0e\x4a\x56\xd7\xd6\x92\x3d\x23\x3c\xbc\xcc\xef\x3a\x33\xd8\x48\xd8\xca\x55" + \
b"\x02\x03\x01\x00\x01\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x6b\xe1\x9a\x78\x2f\x13\x79" + \
b"\xa7\xbb\xf4\xb3\x2b\x6a\x99\x1b\xfb\x99\xeb\xce\xd4\x36\x7f\x1e\xe5\x3f\xcd\x4d\xaf\xe7\x2d\xa3\x81\x5b\x5c\xf5\x86\x71\x3d\x80" + \
b"\x81\x26\xdb\x78\x18\x5b\xd8\x97\x4c\x9d\xa4\xa2\xf7\xb8\xce\xb9\x00\x29\xba\x30\xa0\x5c\xff\xed\x68\xcb\xd1\x10\x22\xc0\x1a\xd4" + \
b"\x80\x9c\x3c\xb2\xd5\x41\xad\x75\x6a\x21\x6e\x77\xd7\x49\x65\xa2\x0b\x47\xfe\x92\xf5\x26\x2a\xea\x59\xe3\xa6\xcc\x1d\xdd\x9c\x09" + \
b"\x23\xfa\xa9\x99\x59\xf4\xda\x49\x1f\xd3\x11\xea\xff\x36\xef\xe5\xcb\x5a\xb4\xa7\x2b\x57\x07\x00\xeb\x1b\x64\x9c\x2f\xa5\x47\x0f" + \
b"\x33\x6d\x7f\x42\x87\xe1\x25\x19\x31\xd7\x08\x55\x82\xe9\xfa\x8c\x6d\xda\x4e\x72\x78\xbe\xe1\x68\x32\x08\xf1\x7f\xb4\x4d\xa0\xd8" + \
b"\xd6\x4f\xad\x61\x01\x83\xf4\xb3\xef\xae\xc2\x86\xee\xd6\xca\x5f\x5b\x8b\x6b\x5a\x13\x98\x6c\x23\xff\x17\x53\x5f\x43\x31\x9e\xf7" + \
b"\x6d\xfd\x0a\x5b\xce\x25\x85\xe4\xc2\x49\x99\x09\x38\x98\xe0\x68\x88\xb8\x2d\xb8\x78\xc7\xbd\x3b\x33\x1e\xdf\x3f\xd8\xae\xfa\x8b" + \
b"\x1d\x4d\xb8\x95\x2f\x2f\x9f\x47\x1c\xbd\xa5\x2c\x74\x40\xc3\x62\x76\x1e\x99\xe5\xd9\x84\x62\x1c\x9c",
b"\x30\x82\x02\xb5\x30\x82\x01\x9d\xa0\x03\x02\x01\x02\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30" + \
b"\x1e\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x52\x53\x41\x32\x30\x34\x38\x31\x0a\x30\x08\x06\x03\x55\x04\x0a\x0c\x01\x33\x30" + \
b"\x1e\x17\x0d\x31\x36\x30\x35\x31\x35\x31\x38\x35\x38\x30\x34\x5a\x17\x0d\x31\x38\x30\x35\x31\x35\x31\x38\x35\x38\x30\x34\x5a\x30" + \
b"\x1e\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x52\x53\x41\x32\x30\x34\x38\x31\x0a\x30\x08\x06\x03\x55\x04\x0a\x0c\x01\x33\x30" + \
b"\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00" + \
b"\xc0\x86\x99\x9a\x76\x74\x9a\xf5\x04\xb8\x48\xf0\x7e\x67\xc3\x90\x51\x17\xe6\xcd\xb3\x97\x6b\x41\xbc\x4e\x4e\x0c\x30\xff\x57\xf1" + \
b"\x47\x99\xd5\xc3\x2a\x67\x26\x6c\x28\x1e\x18\x03\x0f\xaf\xd2\x70\xd2\xb2\xab\xae\x44\x7a\x1c\xcd\xce\x6f\xcb\xfb\x56\x96\x1b\x12" + \
b"\x52\x11\x43\x01\x26\x07\x4c\xf9\x3e\x51\x6b\xd3\x9a\x7d\xd8\x26\x92\xa1\xab\x5a\x6e\x2f\x66\x61\x31\x62\x23\x0f\x8b\x0a\x6a\x17" + \
b"\xe7\xd0\x0a\xc1\x69\x8a\x60\xcf\xba\x6c\x30\x33\x88\x2c\xaf\x13\x9a\xa6\x49\x83\x74\xb4\xdf\x23\xad\xe4\x01\x21\x52\x51\xfe\xcc" + \
b"\x4f\x4e\x3e\xd1\xa8\xce\x92\xc0\x34\x0e\x1d\x6c\xa7\x4d\x6f\x7d\xd7\x65\xb8\xf4\x50\xa1\x58\xa6\x8a\xae\x9e\x21\x50\x84\x01\x38" + \
b"\x1d\x85\x6e\xcb\x7d\xc9\xb5\x6b\xda\x94\xe5\x1c\xa8\xf5\x15\x5e\x8f\xbc\x95\xe0\x76\xcb\x0d\x7e\x79\x11\x0c\x81\xd2\x60\xf1\xaf" + \
b"\x84\xcc\xa0\x3b\x06\xe8\xa2\x42\x9f\xb3\xd4\x94\x8e\xd0\xbc\xe8\x7e\xfa\x01\x6b\x0d\xd0\x16\x60\x47\x86\x9c\xb9\x7f\x12\xd9\x21" + \
b"\x5c\xf7\x8e\x35\x7e\x85\x18\xfa\xc8\x86\xed\xbe\xb3\x0e\x4a\x56\xd7\xd6\x92\x3d\x23\x3c\xbc\xcc\xef\x3a\x33\xd8\x48\xd8\xca\x55" + \
b"\x02\x03\x01\x00\x01\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x64\xd9\x2e\x77\xcb\xb8\x6c" + \
b"\xad\xf0\x99\x8b\x02\xfd\x11\xf4\x1b\x82\xfe\x44\xcf\x06\x76\xe0\xb5\xd7\xd1\xa9\xc0\xeb\xd6\x9f\x8e\xfc\x51\x1c\x51\xb4\x83\xc7" + \
b"\xf0\x3b\x7c\xc9\xde\x0b\x4a\x06\xf3\xdd\xe1\xcb\xd3\x65\xf3\x2a\xdb\x25\x9c\x09\xdd\xce\xd8\x2c\x76\x92\x0a\x3b\x39\x37\x23\x23" + \
b"\xe1\x44\x7c\x10\x2c\xe5\x67\x69\xf2\x85\x7f\x1d\xa4\xd3\xac\x5b\x14\x43\x2d\xff\xe2\x48\xe0\xce\x4c\x00\x02\xb7\xcf\xa1\xe5\xe6" + \
b"\x53\x06\x15\x50\x4a\xd9\x77\xe3\x4e\x8d\x78\x88\xea\x60\x9b\x15\xa8\x2d\x95\xdf\x01\x71\x38\xf8\xeb\x48\x65\xe0\x5f\x46\x55\x24" + \
b"\xd4\xea\xa0\xfb\xbd\x59\x68\x1d\x14\x38\xc6\x46\x69\x2d\x2a\xd5\xfa\xed\xf9\x0e\xe7\xaf\x6d\xf1\x25\x8b\x3c\x97\x0e\xeb\x6e\x28" + \
b"\x46\x11\x54\x7e\x49\xfa\x14\x26\xeb\x2a\x86\x62\x79\x4d\x13\x1b\x20\x4f\x67\x43\xfb\x03\xcd\x30\x70\x02\xfb\x5c\x67\xe9\x93\x4c" + \
b"\xd3\x07\xf5\x5c\x03\x46\xc6\x7b\xe6\x47\x1b\xc1\x55\xcb\xda\x13\x41\x9f\xd3\x85\x7c\x99\xf5\xea\x30\x0e\xf5\x36\x7a\xa5\x97\x00" + \
b"\x7d\x8f\xc4\xb3\xae\xad\x99\xbd\x13\x52\xde\x5b\x07\xa4\x54\xe9\x48\x3b\x2c\xe6\x4b\xae\x79\xfb\x72"]
pyjks-20.0.0/tests/expected/__init__.py 0000664 0000000 0000000 00000000257 13646725476 0020035 0 ustar 00root root 0000000 0000000 from . import RSA1024
from . import RSA2048_3certs
from . import DSA2048
from . import jks_non_ascii_password
from . import custom_entry_passwords
from . import bks_christmas
pyjks-20.0.0/tests/expected/bks_christmas.py 0000664 0000000 0000000 00000012750 13646725476 0021133 0 ustar 00root root 0000000 0000000 public_key = b"\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xb7\x20\x1e" + \
b"\xdb\xbf\x26\x5b\xb2\x53\xc2\x99\x53\x37\x04\xdf\x2c\x99\x09\x78\xc1\x6e\x97\xa0\x4b\x95\x56\xa6\xaf\x1d\xf1\x1e\x60\xe5\xe1\x38" + \
b"\x50\x2f\xc1\x33\x78\x79\xdf\xdd\x44\x61\xed\xe4\xf0\x8b\x33\x03\xfd\x1b\x80\xbe\xfb\x1b\xe0\x9d\x9c\x3f\xcf\xfc\x2c\x1c\xae\xb9" + \
b"\xc8\x3d\x01\x42\xda\xc3\x9d\x7c\x34\x1b\xd4\xbc\x07\xb7\xfe\xe2\x3c\x16\x29\x41\xf4\xb4\xfb\x22\x1d\x9f\x93\x38\x8c\xce\x2b\x21" + \
b"\xbe\xff\xdd\x45\x8b\xe9\x24\x4b\xab\xf3\x4e\x28\xf2\x5a\xe6\x20\xb4\xb8\x83\x61\x7b\xb5\xe9\x85\x13\x64\xc0\xdd\x35\x02\x03\x01" + \
b"\x00\x01"
private_key = b"\x30\x82\x02\x76\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x04\x82\x02\x60\x30\x82\x02\x5c\x02\x01" + \
b"\x00\x02\x81\x81\x00\xb7\x20\x1e\xdb\xbf\x26\x5b\xb2\x53\xc2\x99\x53\x37\x04\xdf\x2c\x99\x09\x78\xc1\x6e\x97\xa0\x4b\x95\x56\xa6" + \
b"\xaf\x1d\xf1\x1e\x60\xe5\xe1\x38\x50\x2f\xc1\x33\x78\x79\xdf\xdd\x44\x61\xed\xe4\xf0\x8b\x33\x03\xfd\x1b\x80\xbe\xfb\x1b\xe0\x9d" + \
b"\x9c\x3f\xcf\xfc\x2c\x1c\xae\xb9\xc8\x3d\x01\x42\xda\xc3\x9d\x7c\x34\x1b\xd4\xbc\x07\xb7\xfe\xe2\x3c\x16\x29\x41\xf4\xb4\xfb\x22" + \
b"\x1d\x9f\x93\x38\x8c\xce\x2b\x21\xbe\xff\xdd\x45\x8b\xe9\x24\x4b\xab\xf3\x4e\x28\xf2\x5a\xe6\x20\xb4\xb8\x83\x61\x7b\xb5\xe9\x85" + \
b"\x13\x64\xc0\xdd\x35\x02\x03\x01\x00\x01\x02\x81\x80\x1b\x35\x19\x80\x9a\x98\xcf\x5c\xe2\x56\xf2\x42\xcb\xd4\xdc\x77\x9a\xd1\x2e" + \
b"\x70\xc6\xc6\x9e\x59\x3c\x79\xe0\x4f\xfa\x49\xdd\xb2\x95\xd4\xed\x71\xf4\x76\x66\xf8\x4a\x5b\x39\x5b\xb4\x95\x0c\xc3\xb4\x5a\xc3" + \
b"\x07\xf2\xf3\x20\xec\xdc\x0a\x76\x40\x45\xb6\xd8\x07\x50\x92\x8d\xd4\x33\xad\x7d\x5b\x4d\xb6\xb2\xda\x42\x17\xac\xfd\x80\x9e\x65" + \
b"\x53\xec\x75\x97\xc4\x22\xf1\xf7\xdd\x61\x0b\x8e\x1d\x94\xcd\x79\x01\x34\xde\x44\x47\xc3\x8d\x87\x0a\x04\x91\xd8\xc9\x13\x34\x0d" + \
b"\xf8\x2f\xf0\x73\xe2\xdb\x75\xe6\x69\x5a\xfb\xc1\x4d\x02\x41\x00\xff\x26\x4c\x32\xc9\xdd\x05\xa4\xe6\x4c\x57\x7b\x27\x66\xfb\x72" + \
b"\x9d\x76\x4b\x19\xf3\x38\xcf\x12\x71\xae\xcc\x61\x38\xa1\x45\x08\xcd\x79\x02\xd4\xf6\x81\x1f\x9e\x7a\x29\x9a\x1a\xb6\xcf\xe9\x64" + \
b"\xfc\x35\x10\xf3\x8d\x42\x82\x93\x94\xe0\xfb\x29\xa1\xfb\x58\xeb\x02\x41\x00\xb7\xbc\x5e\x93\xe9\x5b\x29\x3b\xfd\x13\xb6\x31\xbd" + \
b"\x3e\xa2\xc3\x63\x0c\x4f\xe0\x04\xdf\xe1\x14\x17\xc1\xbb\x36\x3d\x38\xe8\x5d\x4d\xc7\xeb\xe4\x12\x5d\x90\x61\x7d\x09\x9c\x92\x88" + \
b"\xed\x0e\xb4\x69\xbb\x0c\x39\xa3\x8b\xdb\xcb\xd4\x8e\xd0\x06\x97\x99\x1a\x5f\x02\x41\x00\xec\xa7\x26\xf1\xcc\x8d\xb6\x18\x76\x8a" + \
b"\xc4\x71\x33\xe0\xae\xc4\x66\x0c\x3c\x28\x3b\x91\xab\x6e\x9e\x06\xd5\x06\x2f\xb2\x55\x07\x8f\x1c\xf8\x65\x72\xa5\xdd\x1f\xee\x8a" + \
b"\xfb\xa6\x5f\x7d\x84\x2a\xe8\x4a\x88\x08\x7e\x32\xfc\x4d\xca\x3e\xca\x76\x99\x09\x3a\x35\x02\x40\x64\x70\x92\xfb\x24\x8b\xf3\x04" + \
b"\x9a\x1a\x56\x74\xe6\x51\x01\x44\xf2\x36\x5e\xcb\xc5\x9e\x65\x3a\x48\xaa\x5d\x1f\x0f\x64\xb6\x91\x9a\xdd\x79\x34\x5a\x5d\xcf\x79" + \
b"\x9b\x92\xcf\x86\xc3\x57\x63\xbc\x78\x38\x0a\x3c\xd0\x0c\xba\x80\xb8\x97\xc1\x5c\x79\x2e\xf6\xdd\x02\x40\x05\x88\x5c\xb5\x84\xac" + \
b"\x6b\x1e\xd3\x4e\xe1\xf5\xc1\xb9\x84\xee\x44\x90\x95\xe9\x62\xf8\xd7\xbc\x85\xe4\x7b\x6d\x51\x91\x76\x8e\x54\xe7\x1d\x09\x53\xc3" + \
b"\xc8\x26\x96\x80\xde\x73\xa4\x92\xdf\xf0\xb5\xdb\x46\xb3\x7f\x94\xd1\xad\xc2\x6d\x4e\x1e\x08\xef\x76\x88"
certs = [b"\x30\x82\x01\x98\x30\x82\x01\x01\xa0\x03\x02\x01\x02\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30" + \
b"\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x52\x53\x41\x31\x30\x32\x34\x30\x1e\x17\x0d\x31\x36\x30\x35\x31\x35\x32\x33\x34" + \
b"\x30\x30\x38\x5a\x17\x0d\x31\x38\x30\x35\x31\x35\x32\x33\x34\x30\x30\x38\x5a\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07" + \
b"\x52\x53\x41\x31\x30\x32\x34\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89" + \
b"\x02\x81\x81\x00\xb7\x20\x1e\xdb\xbf\x26\x5b\xb2\x53\xc2\x99\x53\x37\x04\xdf\x2c\x99\x09\x78\xc1\x6e\x97\xa0\x4b\x95\x56\xa6\xaf" + \
b"\x1d\xf1\x1e\x60\xe5\xe1\x38\x50\x2f\xc1\x33\x78\x79\xdf\xdd\x44\x61\xed\xe4\xf0\x8b\x33\x03\xfd\x1b\x80\xbe\xfb\x1b\xe0\x9d\x9c" + \
b"\x3f\xcf\xfc\x2c\x1c\xae\xb9\xc8\x3d\x01\x42\xda\xc3\x9d\x7c\x34\x1b\xd4\xbc\x07\xb7\xfe\xe2\x3c\x16\x29\x41\xf4\xb4\xfb\x22\x1d" + \
b"\x9f\x93\x38\x8c\xce\x2b\x21\xbe\xff\xdd\x45\x8b\xe9\x24\x4b\xab\xf3\x4e\x28\xf2\x5a\xe6\x20\xb4\xb8\x83\x61\x7b\xb5\xe9\x85\x13" + \
b"\x64\xc0\xdd\x35\x02\x03\x01\x00\x01\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x81\x81\x00\x97\xc7\xfb\x59" + \
b"\x97\x21\x2b\x8f\xf7\xaf\xa8\x63\xbe\x88\x6d\x1d\x1a\x09\x47\xb7\x39\x2d\x83\xa6\x30\x4b\x60\xcb\xb7\x6a\x9f\x71\x72\x09\x5f\x12" + \
b"\x32\x54\xaa\xfd\xd3\x15\xd9\x33\x65\x09\x93\xdf\x35\x4f\x82\xac\x85\xd9\x46\x71\x78\xba\x8e\xda\x39\x71\x49\xcf\x0d\xf4\xdb\xba" + \
b"\x9d\x7b\xdb\xdf\xfd\x83\xb7\x10\xc2\xc8\xc8\xbf\x25\xef\x4d\xda\x3d\x49\xcc\xa8\x20\x15\x9e\xeb\x97\xb1\x33\xc5\xf3\x24\x21\x9b" + \
b"\x4d\x12\x94\xd5\x24\xa8\x5d\x5e\x6b\x77\xe3\x8f\x42\x81\x40\x52\xf5\x13\x4a\x93\x8d\x29\x34\x2b\x21\xbe\xd1\xa4"]
pyjks-20.0.0/tests/expected/bks_custom_entry_passwords.py 0000664 0000000 0000000 00000013165 13646725476 0023777 0 ustar 00root root 0000000 0000000 public_key = b"\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd9\x7a\xcd" + \
b"\x72\x88\xaa\x98\x10\xee\x43\x50\x98\x95\x42\x98\x2d\x4d\xd7\x2c\xd6\x49\x9d\x4e\x37\x97\x53\x7a\xd3\x94\x8c\x93\x70\x22\xf1\x00" + \
b"\x4b\x4a\x46\xca\xfc\x9c\xa5\x87\xa1\x90\x68\xb9\x04\x79\x1d\x6a\x20\x31\xa2\xe9\x2c\xb1\x51\xb9\x53\xce\x58\x5f\x9c\xd2\xfc\x41" + \
b"\x24\x98\xed\x9e\x0c\x37\xc2\xab\x45\xfc\xbe\x11\x8b\x68\xc0\x4d\xb0\x0c\xb3\xea\x72\x19\xb7\x81\xa8\x3d\x4e\xb0\x59\xb2\xa7\xab" + \
b"\x2d\xac\xd1\xaf\xae\x77\x12\xd3\x30\x97\x28\xd5\xe7\x88\x34\x35\x10\x66\x45\x52\x5f\xea\xfb\x02\x9b\x75\x5c\x77\x67\x02\x03\x01" + \
b"\x00\x01"
private_key = b"\x30\x82\x02\x75\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x04\x82\x02\x5f\x30\x82\x02\x5b\x02\x01" + \
b"\x00\x02\x81\x81\x00\xd9\x7a\xcd\x72\x88\xaa\x98\x10\xee\x43\x50\x98\x95\x42\x98\x2d\x4d\xd7\x2c\xd6\x49\x9d\x4e\x37\x97\x53\x7a" + \
b"\xd3\x94\x8c\x93\x70\x22\xf1\x00\x4b\x4a\x46\xca\xfc\x9c\xa5\x87\xa1\x90\x68\xb9\x04\x79\x1d\x6a\x20\x31\xa2\xe9\x2c\xb1\x51\xb9" + \
b"\x53\xce\x58\x5f\x9c\xd2\xfc\x41\x24\x98\xed\x9e\x0c\x37\xc2\xab\x45\xfc\xbe\x11\x8b\x68\xc0\x4d\xb0\x0c\xb3\xea\x72\x19\xb7\x81" + \
b"\xa8\x3d\x4e\xb0\x59\xb2\xa7\xab\x2d\xac\xd1\xaf\xae\x77\x12\xd3\x30\x97\x28\xd5\xe7\x88\x34\x35\x10\x66\x45\x52\x5f\xea\xfb\x02" + \
b"\x9b\x75\x5c\x77\x67\x02\x03\x01\x00\x01\x02\x81\x80\x29\xf4\xde\x98\xe7\x93\xdd\xd5\x7a\x5a\x03\x3d\x04\xa2\xbd\xe0\x13\xa1\xdd" + \
b"\x15\x14\x4b\xa4\x50\xe6\x41\x65\x33\x57\x77\xcd\x63\xf7\x61\xbe\x58\x48\x22\xa3\x3b\x9b\xee\xf5\x5d\x2e\x92\x7d\x8b\x46\xe0\x6d" + \
b"\x5e\x7b\xa4\xfd\xce\x31\x01\x5e\xbb\x33\xd6\x69\xcf\x68\xc0\x15\x60\x99\xb6\x33\x6a\x59\x86\xe0\xd2\x62\x11\x76\x0c\xe9\x0c\x53" + \
b"\xa4\xa0\x24\x98\xeb\xfd\x05\xa7\x4f\xb1\xbc\xc9\x11\xc2\xdb\xbf\x55\xcd\x4e\x8a\x3f\x46\xb8\xf9\x55\x8c\xe8\x22\xda\xb2\x67\xf0" + \
b"\x71\xe2\xe0\x77\xa8\x00\x9d\x4e\x34\xcd\xdd\x77\x99\x02\x41\x00\xfd\x78\x72\x13\x37\x8d\xcb\x6b\x92\x52\x82\x12\x65\x60\x7b\x8a" + \
b"\xc4\x7a\x5e\xd4\xb8\x1a\xfa\x7f\x5e\xf8\x8a\x84\xf8\x1b\x7c\x28\xfc\x38\x74\xa0\x2e\x44\xc2\x13\x72\x05\xbd\x31\x49\xb9\xb1\x2d" + \
b"\x7a\xf1\x76\x25\x11\x09\xf9\x19\xdd\x02\xfa\xeb\x66\x06\x85\x59\x02\x41\x00\xdb\xa6\x68\xcb\x33\x55\xdb\xbf\x7e\x9b\xdb\xb5\x76" + \
b"\x1a\x1b\xca\x75\x0d\x12\xf2\xf1\x85\x87\x58\xf8\x7c\xee\x6c\x9d\xb9\x06\x1d\xb3\x9e\xad\xf8\xc8\x84\x9d\x8c\xe8\x65\x50\x42\x56" + \
b"\x56\xdc\x81\xd1\xad\xf5\xa9\x7d\xd2\x2e\xa7\x34\xfd\x63\x9e\x9a\xe5\x8a\xbf\x02\x40\x1d\x56\xed\xcd\x6f\xa6\xc8\x1f\x21\x86\xcf" + \
b"\x6b\x95\xb4\x7f\x58\x66\xb9\xcb\x74\x50\x03\x3f\x6f\xb2\xec\x8e\x0c\x2a\x33\xf4\x41\x42\x40\xbe\xaf\x33\xeb\xdd\x93\x26\xa5\xa7" + \
b"\x6a\xa7\x20\x09\x74\x3c\x40\xea\xee\x0b\x74\xde\x12\xb2\x54\x7f\xfa\xf3\x8a\x59\xb1\x02\x40\x5a\xaa\x7a\x1f\x46\x75\x6e\x5b\xc1" + \
b"\x3b\x3c\x99\xce\xc2\x40\x2e\x75\xda\x8b\xb3\xd4\x96\x35\xa4\x38\x0d\xf9\xac\xc3\xfe\x17\xd4\x32\xcc\x91\x2b\x5c\x39\xc1\x7e\xe4" + \
b"\x7e\xcd\x7e\x54\x7d\x4e\x50\x17\xe9\x22\xba\x6f\xc1\x4e\x98\x9e\x7a\xe9\xa0\x12\x78\x25\xa9\x02\x40\x0b\xff\x66\xb9\xf1\x6d\x18" + \
b"\xd9\x92\x64\x60\x16\x04\xdc\x39\x06\x56\xd5\xc9\x9c\x0c\x9b\x66\x06\x35\xf8\xd8\xa0\xa4\xff\xb4\x02\x9c\xaf\xb6\xab\x9a\xc0\x29" + \
b"\xb2\x17\x33\xac\x83\x10\x8c\x4c\x89\x44\x3e\xd0\x1e\x11\x61\x4a\xf5\xe3\xca\x26\x28\x38\x43\x7d\xeb"
certs = [b"\x30\x82\x01\xb8\x30\x82\x01\x21\xa0\x03\x02\x01\x02\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30" + \
b"\x22\x31\x20\x30\x1e\x06\x03\x55\x04\x03\x0c\x17\x63\x75\x73\x74\x6f\x6d\x5f\x65\x6e\x74\x72\x79\x5f\x70\x61\x73\x73\x77\x6f\x72" + \
b"\x64\x73\x31\x30\x1e\x17\x0d\x31\x36\x30\x35\x31\x38\x32\x32\x35\x33\x30\x36\x5a\x17\x0d\x31\x38\x30\x35\x31\x38\x32\x32\x35\x33" + \
b"\x30\x36\x5a\x30\x22\x31\x20\x30\x1e\x06\x03\x55\x04\x03\x0c\x17\x63\x75\x73\x74\x6f\x6d\x5f\x65\x6e\x74\x72\x79\x5f\x70\x61\x73" + \
b"\x73\x77\x6f\x72\x64\x73\x31\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89" + \
b"\x02\x81\x81\x00\xd9\x7a\xcd\x72\x88\xaa\x98\x10\xee\x43\x50\x98\x95\x42\x98\x2d\x4d\xd7\x2c\xd6\x49\x9d\x4e\x37\x97\x53\x7a\xd3" + \
b"\x94\x8c\x93\x70\x22\xf1\x00\x4b\x4a\x46\xca\xfc\x9c\xa5\x87\xa1\x90\x68\xb9\x04\x79\x1d\x6a\x20\x31\xa2\xe9\x2c\xb1\x51\xb9\x53" + \
b"\xce\x58\x5f\x9c\xd2\xfc\x41\x24\x98\xed\x9e\x0c\x37\xc2\xab\x45\xfc\xbe\x11\x8b\x68\xc0\x4d\xb0\x0c\xb3\xea\x72\x19\xb7\x81\xa8" + \
b"\x3d\x4e\xb0\x59\xb2\xa7\xab\x2d\xac\xd1\xaf\xae\x77\x12\xd3\x30\x97\x28\xd5\xe7\x88\x34\x35\x10\x66\x45\x52\x5f\xea\xfb\x02\x9b" + \
b"\x75\x5c\x77\x67\x02\x03\x01\x00\x01\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x81\x81\x00\xd6\x2c\x88\x43" + \
b"\x42\x2a\x0c\x8b\x1c\xd4\xe9\xd1\x16\xd5\x4b\xd3\x00\x5e\xeb\xa3\xdb\x21\x2c\x35\xb5\xbb\xa7\xc8\xb9\x58\x51\x34\x5b\x91\xf7\xc3" + \
b"\x17\x20\x6b\x18\x4d\x5f\x13\x42\x60\x17\x6e\x09\x82\x50\x55\x45\xd9\x6a\x74\xf5\xa9\x10\x3d\x2e\xf1\x16\xad\xa5\x0d\x25\xe7\x06" + \
b"\x22\x0e\x82\xae\x76\x80\x85\x3d\x0a\x3b\x20\x01\xc9\x42\xdd\x19\xfe\x1b\xda\x5d\x6d\x1b\xd7\x0d\x10\x39\x74\x97\xd7\x36\x8c\x1a" + \
b"\x56\x28\x98\x13\xaa\x35\xe7\xa4\xa0\xdd\x60\xba\xed\xca\x4e\xda\x57\x3b\xdf\xd7\xb8\xa7\x9f\xf1\x75\xa6\xbf\x0a"]
pyjks-20.0.0/tests/expected/custom_entry_passwords.py 0000664 0000000 0000000 00000024057 13646725476 0023142 0 ustar 00root root 0000000 0000000 public_key = b"\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01" + \
b"\x00\x96\x9d\xdc\xa9\xb9\xda\x39\xd3\x1e\x82\x24\xa8\x77\x15\xa6\x52\xb1\x67\x7a\x11\x8a\xe2\x07\xbf\xa8\x00\xcf\x7f\x2f\x8b\x06" + \
b"\x2d\x2b\xb4\x8b\xa4\x9c\x6e\x0c\xde\x5f\x2c\xe1\x65\x17\xb1\x20\xe5\x01\x41\x09\x36\x84\xd9\x11\x0e\x3b\x35\x25\xcd\x91\x84\xbd" + \
b"\x4c\xd9\x19\x0d\xd7\xa2\x2d\x8c\x6a\xcb\xb7\x25\xd5\xd2\xb2\x22\xc9\xb9\x07\x1b\xb1\xea\x48\x6a\x29\x9b\x46\x61\x3a\xf1\x19\xf4" + \
b"\x79\x82\x48\xb9\xb1\x52\xa9\xdd\x9a\xd6\xc9\x7a\x2d\x98\x34\x77\x18\x8e\xea\xc5\xd8\xad\x0e\xad\x50\xa8\x4f\xb5\x50\xb8\xee\xa0" + \
b"\xcd\x28\xc2\x8f\x43\x0f\xd6\xef\x82\xcf\x0b\xff\xa8\x3e\x42\xf7\x58\x79\xee\x50\x41\xe7\x8c\x94\x4c\xaf\xb4\x53\xe2\x7b\x46\x81" + \
b"\x2e\xaf\x4b\x09\x21\x82\xf2\xab\x9b\x72\x13\xc4\x55\x66\x8c\x69\x43\xc3\x2c\x52\xdd\x95\xd3\x06\xc2\x2c\xa6\x5e\x1d\x55\xed\x36" + \
b"\x3b\x52\x39\x06\x4a\x25\x60\xec\x4f\x8f\xa2\x3c\xc6\x08\x87\x59\x30\x88\x68\xb5\x8a\xdd\x30\xdf\xda\x78\x07\x5c\xb8\x9f\x5f\xd7" + \
b"\xc8\x50\xf4\x09\x86\x08\x79\x33\x90\xbc\x59\x8f\x40\xba\x13\xcb\xbf\x11\x89\xcd\x8d\x0c\xa3\x28\x47\xb7\x66\xb8\xfb\xcc\x84\xc3" + \
b"\x05\x02\x03\x01\x00\x01"
private_key = b"\x30\x82\x04\xbd\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x04\x82\x04\xa7\x30\x82\x04\xa3\x02\x01" + \
b"\x00\x02\x82\x01\x01\x00\x96\x9d\xdc\xa9\xb9\xda\x39\xd3\x1e\x82\x24\xa8\x77\x15\xa6\x52\xb1\x67\x7a\x11\x8a\xe2\x07\xbf\xa8\x00" + \
b"\xcf\x7f\x2f\x8b\x06\x2d\x2b\xb4\x8b\xa4\x9c\x6e\x0c\xde\x5f\x2c\xe1\x65\x17\xb1\x20\xe5\x01\x41\x09\x36\x84\xd9\x11\x0e\x3b\x35" + \
b"\x25\xcd\x91\x84\xbd\x4c\xd9\x19\x0d\xd7\xa2\x2d\x8c\x6a\xcb\xb7\x25\xd5\xd2\xb2\x22\xc9\xb9\x07\x1b\xb1\xea\x48\x6a\x29\x9b\x46" + \
b"\x61\x3a\xf1\x19\xf4\x79\x82\x48\xb9\xb1\x52\xa9\xdd\x9a\xd6\xc9\x7a\x2d\x98\x34\x77\x18\x8e\xea\xc5\xd8\xad\x0e\xad\x50\xa8\x4f" + \
b"\xb5\x50\xb8\xee\xa0\xcd\x28\xc2\x8f\x43\x0f\xd6\xef\x82\xcf\x0b\xff\xa8\x3e\x42\xf7\x58\x79\xee\x50\x41\xe7\x8c\x94\x4c\xaf\xb4" + \
b"\x53\xe2\x7b\x46\x81\x2e\xaf\x4b\x09\x21\x82\xf2\xab\x9b\x72\x13\xc4\x55\x66\x8c\x69\x43\xc3\x2c\x52\xdd\x95\xd3\x06\xc2\x2c\xa6" + \
b"\x5e\x1d\x55\xed\x36\x3b\x52\x39\x06\x4a\x25\x60\xec\x4f\x8f\xa2\x3c\xc6\x08\x87\x59\x30\x88\x68\xb5\x8a\xdd\x30\xdf\xda\x78\x07" + \
b"\x5c\xb8\x9f\x5f\xd7\xc8\x50\xf4\x09\x86\x08\x79\x33\x90\xbc\x59\x8f\x40\xba\x13\xcb\xbf\x11\x89\xcd\x8d\x0c\xa3\x28\x47\xb7\x66" + \
b"\xb8\xfb\xcc\x84\xc3\x05\x02\x03\x01\x00\x01\x02\x82\x01\x00\x10\x0b\xc5\x64\x77\x9e\x5e\x26\xb5\xcb\x5b\xa8\x6e\xf7\x69\x7e\xc9" + \
b"\xd3\xa2\x57\x98\x1a\x38\x85\x1c\x9a\xe9\x80\x3d\x4a\x6c\x60\x07\x95\xf6\x82\x94\xff\xcc\x73\x2c\x64\x95\xa6\xb5\x7d\x73\x69\xb2" + \
b"\x56\x81\x6a\xc3\x80\x74\xa6\xb5\x7c\x16\x08\xee\x85\xdb\xbd\x02\x2b\xff\x23\x87\xed\x9e\x56\x0a\x59\xfa\xb7\xea\xf8\x7b\x68\x4e" + \
b"\x44\x09\x99\x4c\xc2\x66\x3d\x04\x83\xdc\xfb\xf0\x8f\xb9\x51\xf0\xa5\x5a\xd3\x1f\x61\x65\x70\x87\x8d\x73\x6c\xc4\x18\x62\xcf\xc4" + \
b"\xfa\x12\x15\xe1\x69\xfc\xe0\xe8\xed\x84\xfd\x92\x96\x29\x9f\x47\x72\xc7\x7d\x41\x86\x3a\xbe\xf8\x29\x1f\x34\x8e\x3f\x9e\x37\x84" + \
b"\x0d\x25\xe8\x76\xde\x21\xad\x13\x9c\xea\xe3\x34\xb2\xcf\x67\xcd\x60\xb2\xfa\xba\xd6\x0e\x98\x99\xdf\x9a\xf8\x76\xfa\x77\xf9\xe0" + \
b"\xd1\xe4\xee\x7e\x32\x4e\x1e\x94\x1c\x8b\xcd\x02\xa3\x77\xa9\xeb\x6d\x28\xd4\x55\xce\xfb\xbc\x22\xff\x47\x02\xdf\xee\xd2\x37\x7f" + \
b"\x8d\x61\xa4\x5c\x6e\x01\x34\x1d\x08\x4e\xad\xce\x61\xb3\xe3\x1a\xa9\xca\x6b\x75\x2e\x3a\x0d\xaf\xec\xdb\x9e\xdf\x30\xc1\x70\x30" + \
b"\x91\xf1\x5d\x83\x11\x8a\x14\x97\x07\xd0\x82\x8d\xde\x69\x09\x02\x81\x81\x00\xe4\xb2\xa5\x0a\xa2\x21\xc3\x47\xf3\xaf\x02\x6c\xb3" + \
b"\x84\x96\xc9\x78\x69\x59\x31\x6f\xc3\x14\xdc\x37\xd7\x66\x21\xe7\x21\x99\x4c\x4f\x09\xc3\xf0\x02\x2b\xd4\x68\x7a\x58\xb3\xc3\x6c" + \
b"\x9d\x7e\x24\x2c\x62\x15\xe7\xef\x1f\x91\xf2\x70\x1f\xdf\x13\x6f\x34\x28\xd7\xb7\xf1\xbe\xee\x08\x4f\xbb\x86\x99\x4d\x2d\xed\x48" + \
b"\x82\xd4\x11\xc3\xca\x0e\xef\x49\x2a\x21\x36\x00\xa1\x2b\x17\x96\x7d\x13\x0f\x84\x80\x5c\x3c\xd2\x8a\x0d\x2e\xdd\x9a\x38\xd1\xbc" + \
b"\x77\x9e\x61\xfd\xe8\x57\xe1\x50\xda\xee\xdd\x96\x07\x13\x97\x60\xe0\xc6\xc3\x02\x81\x81\x00\xa8\x98\xef\xdd\x9b\x69\x7c\x1e\x01" + \
b"\x20\x25\xb9\x2d\xc0\xc7\xb1\xc1\x54\x7e\x98\x52\x96\x12\x92\x3a\x0b\x79\xcf\x8d\xb0\xbe\xb9\x0f\xca\xc0\xec\xa6\xce\x8f\xad\x44" + \
b"\x6d\x85\xc0\x52\xd9\x9e\x0a\x12\xe0\x06\xd3\xa8\x48\xf7\xac\xf8\x07\x7d\x4b\x84\x4b\x69\x26\x2f\x2c\xcb\xf6\x28\xd3\x3e\x04\x55" + \
b"\x76\xef\xd2\x68\xb0\xea\xac\x28\xd4\xdf\x4d\x67\x80\x31\xea\x44\xee\x87\x0e\x55\x3d\xe8\xd8\x3c\xd6\x0c\x47\x19\x45\xed\xd8\xd0" + \
b"\x80\x4a\x0c\xea\xb4\x47\xba\x6d\x62\x2d\x50\x0d\xa7\x9f\x7c\xb7\x3f\x8b\x33\x0d\xf6\x02\x97\x02\x81\x81\x00\x8e\x68\x33\x5f\x18" + \
b"\xb0\x5f\x72\x69\x6a\x3b\xdf\x46\x35\xb4\xd3\x45\x98\x8c\x02\xbd\xae\x43\x4a\x11\xb7\x9e\x10\x54\x65\x56\x98\xee\xca\x8c\xe8\xe1" + \
b"\xe1\x3b\x05\xc7\xd1\x7e\x36\x9d\x66\xc0\x8a\x73\xdc\xab\xf9\x5d\xac\x51\xec\x1e\x27\xaa\x77\xe6\x92\x1b\x30\xa0\x88\xf7\x34\x2f" + \
b"\x96\xbe\x95\xc4\xd6\x0b\x58\x2b\x03\xd4\x5c\x2f\x87\x9d\x9f\x20\xf7\x0b\xf1\x1d\x99\x3e\x45\x14\xdf\x53\x44\x21\x64\xf0\x8a\xab" + \
b"\x2b\x6d\xa4\x16\x37\x97\x53\xfa\xc0\x9d\xae\x35\x36\x3d\xaa\xbe\xf7\x65\x30\x33\xe0\xba\x31\x54\x11\x03\x93\x02\x81\x80\x04\xb4" + \
b"\x87\xf5\x32\x52\x26\x51\x9c\x0c\x6e\xa1\x15\x62\xcc\xef\xec\x0a\x54\xa2\x21\xa3\xe8\x8e\xc6\x29\xed\xd3\x5a\x0b\xf5\xb6\xaa\x77" + \
b"\x29\x1d\x31\xa6\x90\x21\xf0\xc7\xf1\xb4\xa5\x5b\x47\x6a\xd6\x8e\x04\x02\xc2\x2e\x8f\x22\xf7\xa5\x15\xdd\x16\xab\x18\x1b\x25\xb3" + \
b"\xe5\x59\x50\x58\x5c\xe1\xb8\x14\xe2\xaa\x87\x9b\x70\x61\x2f\x9e\x89\x40\xda\xc3\x9c\x21\x02\x9d\x79\x8d\x6f\xd9\x93\x76\xfd\x73" + \
b"\xa7\xd2\x10\x25\x80\x76\xcd\x92\xd6\xfe\x37\x4a\xa2\xb9\xd4\x35\xaa\x38\x12\xb9\x7b\xdc\xfa\x2c\x3e\xd1\x44\xf1\x79\xaf\x02\x81" + \
b"\x80\x0d\xd1\xf3\x93\x36\x9f\xdf\xf7\x73\xa4\x02\x97\xe1\x45\x6e\xc0\xfd\xa2\x52\x70\x2c\xff\x87\xdd\x7a\xfa\x25\x93\x94\x0c\x8b" + \
b"\xee\x78\xb9\x51\x2b\x99\x69\x96\x86\x69\x6e\x78\x84\x26\x43\xda\x98\x76\x98\x64\x9d\xda\xf7\x45\x1a\xb1\x36\x03\x8f\xac\x55\xbe" + \
b"\x4a\x83\x77\xce\x28\x53\x57\x93\x83\x1d\xbb\x7b\x4e\x2e\x46\xc2\xd7\x82\x77\x48\xa2\xba\x26\xe6\x01\x73\x1c\x54\xbf\x4e\xef\xc4" + \
b"\x3f\xf0\xc6\xe0\x9b\xaa\x8a\x97\x56\x23\xc5\x93\xe0\x6c\xc4\x65\xfd\xe6\xd3\xc4\x1f\x60\xd6\x47\xf5\x0b\x44\x61\xbf\xb8\xf1\xe9" + \
b"\xc7"
certs = [b"\x30\x82\x02\xbb\x30\x82\x01\xa3\xa0\x03\x02\x01\x02\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30" + \
b"\x21\x31\x1f\x30\x1d\x06\x03\x55\x04\x03\x0c\x16\x63\x75\x73\x74\x6f\x6d\x5f\x65\x6e\x74\x72\x79\x5f\x70\x61\x73\x73\x77\x6f\x72" + \
b"\x64\x73\x30\x1e\x17\x0d\x31\x36\x30\x35\x31\x35\x31\x38\x35\x31\x30\x31\x5a\x17\x0d\x31\x38\x30\x35\x31\x35\x31\x38\x35\x31\x30" + \
b"\x31\x5a\x30\x21\x31\x1f\x30\x1d\x06\x03\x55\x04\x03\x0c\x16\x63\x75\x73\x74\x6f\x6d\x5f\x65\x6e\x74\x72\x79\x5f\x70\x61\x73\x73" + \
b"\x77\x6f\x72\x64\x73\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01" + \
b"\x0a\x02\x82\x01\x01\x00\x96\x9d\xdc\xa9\xb9\xda\x39\xd3\x1e\x82\x24\xa8\x77\x15\xa6\x52\xb1\x67\x7a\x11\x8a\xe2\x07\xbf\xa8\x00" + \
b"\xcf\x7f\x2f\x8b\x06\x2d\x2b\xb4\x8b\xa4\x9c\x6e\x0c\xde\x5f\x2c\xe1\x65\x17\xb1\x20\xe5\x01\x41\x09\x36\x84\xd9\x11\x0e\x3b\x35" + \
b"\x25\xcd\x91\x84\xbd\x4c\xd9\x19\x0d\xd7\xa2\x2d\x8c\x6a\xcb\xb7\x25\xd5\xd2\xb2\x22\xc9\xb9\x07\x1b\xb1\xea\x48\x6a\x29\x9b\x46" + \
b"\x61\x3a\xf1\x19\xf4\x79\x82\x48\xb9\xb1\x52\xa9\xdd\x9a\xd6\xc9\x7a\x2d\x98\x34\x77\x18\x8e\xea\xc5\xd8\xad\x0e\xad\x50\xa8\x4f" + \
b"\xb5\x50\xb8\xee\xa0\xcd\x28\xc2\x8f\x43\x0f\xd6\xef\x82\xcf\x0b\xff\xa8\x3e\x42\xf7\x58\x79\xee\x50\x41\xe7\x8c\x94\x4c\xaf\xb4" + \
b"\x53\xe2\x7b\x46\x81\x2e\xaf\x4b\x09\x21\x82\xf2\xab\x9b\x72\x13\xc4\x55\x66\x8c\x69\x43\xc3\x2c\x52\xdd\x95\xd3\x06\xc2\x2c\xa6" + \
b"\x5e\x1d\x55\xed\x36\x3b\x52\x39\x06\x4a\x25\x60\xec\x4f\x8f\xa2\x3c\xc6\x08\x87\x59\x30\x88\x68\xb5\x8a\xdd\x30\xdf\xda\x78\x07" + \
b"\x5c\xb8\x9f\x5f\xd7\xc8\x50\xf4\x09\x86\x08\x79\x33\x90\xbc\x59\x8f\x40\xba\x13\xcb\xbf\x11\x89\xcd\x8d\x0c\xa3\x28\x47\xb7\x66" + \
b"\xb8\xfb\xcc\x84\xc3\x05\x02\x03\x01\x00\x01\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x63" + \
b"\xed\x2b\xee\x8e\xbc\x6d\x25\xe2\x89\x60\x67\xfb\x32\x91\x5c\x4b\xe7\x62\xbc\x9b\xf9\x7b\x74\x0a\x7f\x60\x62\x56\x73\x11\xfc\x14" + \
b"\xd6\x6f\xfc\xe4\x69\x88\x41\xc7\x6c\x83\x9a\xd4\xda\xa0\x45\x86\xc1\x46\xc1\x02\x28\x63\x4f\x08\x65\x8e\x1c\x3a\x72\xaf\xb0\x40" + \
b"\x67\xfa\x1a\xa3\x39\xab\xcc\x1d\x05\x92\x3a\xe8\x15\x24\xfb\x4f\xd3\xc5\x39\xc4\xe7\xc7\xc9\x0a\xc6\x2c\xdd\xcd\x4e\x55\x0c\x64" + \
b"\xf7\xf1\x82\x75\x41\x22\xa6\x6e\x15\x8a\x34\x84\x34\x01\x78\x8d\xc6\x14\x39\x69\xaa\x1b\xd2\x60\xf7\xd8\x21\x1c\x4b\x7c\xc6\x9e" + \
b"\x67\x92\x12\xca\xe2\xcb\x36\x0b\xc0\x95\xf2\xcf\xb3\x01\x28\x36\x21\xf8\xf6\x98\x52\xb9\x4b\xc7\x11\x10\xe5\x1d\xf0\x53\xf2\xd4" + \
b"\xab\xc7\x3f\x01\xde\x10\xb0\xbe\xea\x24\x08\x78\x55\x3b\xa2\xe8\x27\x1c\xad\x48\x54\xd0\xae\x06\x57\xdd\x4d\xe7\x50\xfe\xb3\x0f" + \
b"\x2d\x3c\xc9\x20\xb7\x28\x76\xa8\x00\x1e\xde\x91\xad\x1b\x3e\xbc\x8f\x03\x94\xed\xb2\x53\x98\xe2\x32\x51\x9f\xf0\xd2\xc5\x6e\x8b" + \
b"\xa3\x45\x2d\xde\xba\x26\x3a\x03\x74\x2f\x8c\x9a\xe4\x34\x82\x59\x93\xec\xc1\x2f\xc4\xad\xb3\x07\x25\xc2\x33\xa8\x22\x05\x72"]
pyjks-20.0.0/tests/expected/jks_non_ascii_password.py 0000664 0000000 0000000 00000024023 13646725476 0023026 0 ustar 00root root 0000000 0000000 public_key = b"\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01" + \
b"\x00\x8a\xf3\x6e\x34\xf0\xfa\xfd\xec\xad\x27\x63\xe1\x66\x8a\x66\xcf\xfa\x1b\xcf\x83\x75\xe3\xb5\xcf\x23\x52\xe1\x37\x29\x77\xe5" + \
b"\x9a\x30\xa5\xf3\xdc\x42\x3a\x62\x00\x7c\x87\xea\xcf\x81\xdb\x40\x4f\x36\x92\x8a\xff\x79\x25\x55\x2e\x55\xdb\xac\x09\x7d\xf1\x5c" + \
b"\x72\xd4\x60\x0f\x81\x65\x11\x89\x06\x89\x58\x37\x05\xbc\x4a\x57\x0b\xce\xe7\x80\xe6\xc9\x3f\xf2\xd5\xc6\x7b\x38\x3a\xf2\x4e\x3d" + \
b"\xff\x1a\xb9\xd7\xfe\x8f\x9f\x08\x4c\xf0\x5b\xe4\xe2\x3e\x67\x9d\x3a\x04\x15\x04\xc4\xbd\x3f\x85\xb8\x3d\x7c\xde\x3b\xcc\x24\x44" + \
b"\x51\x77\xee\xf0\x99\xf1\x65\x0b\x8f\xda\xda\xa2\xc8\x6e\x4c\xa1\xd9\x1c\x7c\x97\xc8\x3b\x37\xc0\x83\x5e\x16\x84\x58\x25\x37\xa7" + \
b"\x17\x01\xd3\xb3\xb8\xd3\xdc\x66\x7f\x8d\x87\x2e\x80\x03\x19\xf5\x01\xb6\x8c\xf9\xab\xdf\xe1\x22\x99\x96\x2d\x97\x33\xcc\x95\x07" + \
b"\x4b\xa2\xdf\x65\x93\xf6\x86\x41\xea\xfd\x65\xbf\xeb\x46\xf2\xe3\x9d\x78\x32\xf5\xd7\xad\x4d\x6b\x5a\xa1\xcd\x01\xf0\x8e\x3d\x26" + \
b"\x52\xf5\x40\xc4\xd5\xfb\x10\x9b\xa1\x74\xa9\x78\xc1\x1a\x3e\x0a\x42\x4f\x37\xa9\x59\x83\x70\xd2\xe0\x33\x26\xfa\xd9\x24\xfb\xdc" + \
b"\x5d\x02\x03\x01\x00\x01"
private_key = b"\x30\x82\x04\xbe\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x04\x82\x04\xa8\x30\x82\x04\xa4\x02\x01" + \
b"\x00\x02\x82\x01\x01\x00\x8a\xf3\x6e\x34\xf0\xfa\xfd\xec\xad\x27\x63\xe1\x66\x8a\x66\xcf\xfa\x1b\xcf\x83\x75\xe3\xb5\xcf\x23\x52" + \
b"\xe1\x37\x29\x77\xe5\x9a\x30\xa5\xf3\xdc\x42\x3a\x62\x00\x7c\x87\xea\xcf\x81\xdb\x40\x4f\x36\x92\x8a\xff\x79\x25\x55\x2e\x55\xdb" + \
b"\xac\x09\x7d\xf1\x5c\x72\xd4\x60\x0f\x81\x65\x11\x89\x06\x89\x58\x37\x05\xbc\x4a\x57\x0b\xce\xe7\x80\xe6\xc9\x3f\xf2\xd5\xc6\x7b" + \
b"\x38\x3a\xf2\x4e\x3d\xff\x1a\xb9\xd7\xfe\x8f\x9f\x08\x4c\xf0\x5b\xe4\xe2\x3e\x67\x9d\x3a\x04\x15\x04\xc4\xbd\x3f\x85\xb8\x3d\x7c" + \
b"\xde\x3b\xcc\x24\x44\x51\x77\xee\xf0\x99\xf1\x65\x0b\x8f\xda\xda\xa2\xc8\x6e\x4c\xa1\xd9\x1c\x7c\x97\xc8\x3b\x37\xc0\x83\x5e\x16" + \
b"\x84\x58\x25\x37\xa7\x17\x01\xd3\xb3\xb8\xd3\xdc\x66\x7f\x8d\x87\x2e\x80\x03\x19\xf5\x01\xb6\x8c\xf9\xab\xdf\xe1\x22\x99\x96\x2d" + \
b"\x97\x33\xcc\x95\x07\x4b\xa2\xdf\x65\x93\xf6\x86\x41\xea\xfd\x65\xbf\xeb\x46\xf2\xe3\x9d\x78\x32\xf5\xd7\xad\x4d\x6b\x5a\xa1\xcd" + \
b"\x01\xf0\x8e\x3d\x26\x52\xf5\x40\xc4\xd5\xfb\x10\x9b\xa1\x74\xa9\x78\xc1\x1a\x3e\x0a\x42\x4f\x37\xa9\x59\x83\x70\xd2\xe0\x33\x26" + \
b"\xfa\xd9\x24\xfb\xdc\x5d\x02\x03\x01\x00\x01\x02\x82\x01\x01\x00\x88\x0a\x35\x88\x17\xb7\x65\x0c\x67\xaa\xfd\x1b\x1d\x6d\xbb\xa3" + \
b"\x3b\xd8\x26\x9f\x2a\xb8\xba\xb4\x06\x7d\x8a\x8a\x9b\x4b\x0b\xbc\x9e\x8c\x9b\xe8\xb5\xde\xbb\x3c\x86\xaf\xfb\xb1\x16\x0e\x37\x34" + \
b"\x56\xd5\xba\xc8\xcd\x2f\x43\xea\x8d\x9c\xf9\x1e\x81\xf3\xe0\xf3\x7c\x02\x60\xab\xea\xeb\xf4\x20\x36\x2f\xec\x0e\xed\x7e\x4b\x23" + \
b"\x00\x7b\x9f\xb4\x54\xad\x0b\x6f\x49\x58\x32\x81\x63\xe2\x3e\xc8\x98\xbe\x03\x3e\xf4\x16\x5b\xe6\x18\x1d\xa0\x45\xf1\x9b\x38\x50" + \
b"\x28\xd6\xfb\x48\x33\x91\x11\xb6\x84\x5c\xd0\xa7\xf3\x02\x3e\xef\x9f\xcc\xa0\xe5\x6b\xfb\xfd\x21\xa1\xa1\xfa\xbc\xb0\x40\xb0\x24" + \
b"\xd4\xc7\x17\x65\x69\x1c\xc7\x36\xf9\xf5\x1c\x5b\x27\x27\x26\x7b\x7b\xb5\x2d\xc2\xe7\x74\x5e\x2b\x88\xde\x62\xe9\x2b\x49\xaf\xf8" + \
b"\xae\x27\x33\x19\x66\x0b\xe9\x61\x1a\x91\xc1\x52\x39\x04\xe2\xfc\xdd\x6b\xcf\x05\xe0\xe0\x4c\xc0\x9b\x71\xab\x40\xf6\xda\xec\x6e" + \
b"\x53\xc1\x96\x2c\xc1\xa8\x50\x53\x4d\xd8\x73\xc3\x9a\xa6\xfa\x31\xe1\xce\x38\x08\xfc\xfc\xf5\xfa\xb3\xb2\x91\x7e\x83\xb6\xc8\xdc" + \
b"\xf0\x50\x85\xeb\xc6\x30\xb1\xa6\x2c\x56\xf9\x9e\xdd\xf9\x1b\x71\x02\x81\x81\x00\xd7\x69\xf9\x23\xc1\xb6\xfa\x1e\x2a\xe1\x31\x7c" + \
b"\xfb\xcb\x3f\x02\x88\x5a\x8f\xb3\xca\xd9\x2c\xd8\x78\x00\x5d\xff\x4f\x0e\xc9\x39\xef\xf6\xd8\x25\x4b\xb2\x88\xed\x0b\xee\x3b\x22" + \
b"\xbd\xbe\xee\x6d\x40\x9b\x66\xa7\x8e\xe7\x53\xf6\x3d\xfe\x95\x2b\xf2\x64\xa3\xf1\xd5\x6d\xdf\x5e\x55\xfc\x0c\x75\xf8\x71\xf7\x9b" + \
b"\xdb\x27\xdc\xcb\xce\xee\xe6\x4e\xb8\xbd\x6c\xce\x39\xeb\xf1\xf3\xb6\x17\xd1\x81\x45\x4a\x2d\xb5\x92\xc5\xdf\x82\xca\x52\x92\xaf" + \
b"\x11\x7e\xd4\xf6\x06\xfa\x60\x7b\x87\xc7\x2d\x7a\xaa\xb1\xf3\xde\x48\x6e\x21\x8f\x02\x81\x81\x00\xa5\x21\x6d\x4b\xa6\xd7\x49\x3b" + \
b"\x0c\x61\x10\x44\xc7\xc9\x0e\x8d\x56\xb0\xd1\x25\x87\x74\x1a\xb6\xe9\x4b\x40\xa1\x6f\xbd\xa9\xae\xc7\x96\x1f\x00\xd0\xa3\x01\x2d" + \
b"\x6c\xd3\xb6\x83\x98\x8e\x10\xdd\x35\xb5\x6f\x75\x24\x82\x2e\x15\xde\x01\x59\x70\x53\xc4\xdf\x1f\x63\xfb\xa6\x06\x73\xd0\x53\x9d" + \
b"\xbf\x06\xbf\x40\x3c\x4c\x99\xf9\x10\x40\x14\x43\xf6\xfb\xe6\xe1\xf4\x31\x4c\x64\x85\x5f\x21\xbf\x1c\x67\xab\x51\x69\xe9\x77\xdd" + \
b"\x31\xc7\x80\x5b\xd7\x17\xcb\x7d\x7e\xd0\x4e\x94\x4c\xa7\x3c\xa5\x44\x09\x2f\x0b\xbf\x75\xd5\x53\x02\x81\x80\x7b\x94\xc2\x67\xaf" + \
b"\x14\xa8\x3d\x72\x60\x2b\x22\x06\xda\x3d\x55\xd8\xa0\x0b\xdd\x1b\xbc\xa8\x2b\xaf\xfc\x95\xf0\x88\x8c\x75\x09\x16\x0f\xc9\x44\xec" + \
b"\x3e\x8a\xab\x63\xb2\xd2\x9e\x45\xb9\x29\xd3\xe7\xc2\xbf\x8b\xd5\x42\x05\x3b\x39\xa9\xba\x2e\xb2\x2a\xe6\x9b\x30\xd4\x8d\xd6\xf3" + \
b"\x01\x5c\xac\xb9\x51\xb8\xb9\xe3\x6a\xe5\x12\xcf\xae\xe2\xd5\xba\xca\x81\x87\x76\x57\x54\x41\x7a\xf0\x03\x33\x64\x6a\xff\xfa\x31" + \
b"\x2f\xef\xe0\x7a\xee\x10\x54\xfb\x76\x85\xfa\x77\x5a\x60\xcb\xbd\xc3\x98\xe5\xcc\xd3\xb6\x92\x89\x7f\x15\x25\x02\x81\x80\x11\x0b" + \
b"\xa3\x49\x94\xde\x52\x3b\xdb\x2a\x45\xd0\xa3\xc2\xd8\x52\xb4\xa9\x29\xdd\xb2\xde\xc4\x47\x2f\x4b\xca\x4f\x1f\xc6\xb7\x36\x48\x79" + \
b"\xf1\x97\x56\xf2\x0c\x94\x10\xd0\xc5\xdd\x4d\xa9\x14\x8a\x91\x19\xba\x78\xa0\x1d\x23\xfe\xb3\xdc\xf5\xcb\x87\x8a\x21\xf2\x0e\x93" + \
b"\x12\x6f\x46\x13\x32\x1e\x6d\x72\x97\xd9\x5c\xa0\x17\xa0\xc3\x11\xaa\x45\xdd\xbf\xe0\xf4\x60\x0d\x9a\xb2\x21\xa6\x51\x48\x75\xf9" + \
b"\x29\x91\x0c\xda\xc1\xb9\xbe\x82\xb6\x78\x44\x7a\xbb\x51\xa7\xfe\x7e\xae\x06\x3f\x99\x1e\x02\x28\xe4\x87\x54\xeb\x27\xeb\x02\x81" + \
b"\x81\x00\x8f\xdd\x55\x22\xe9\x41\x4c\x7e\x22\x5a\x4a\x24\x82\xdf\x4c\x08\xc2\x8e\xd2\x29\xf7\xc3\x02\xa1\x1f\xcf\xa4\x19\x57\xb8" + \
b"\xcf\xa4\xa8\xec\x19\x3b\x73\x29\xe9\x16\x2c\x2a\x03\x5b\xf7\x6d\xcf\x21\x08\x01\x41\xe6\xdd\xe5\x80\xd8\x2f\xce\x3d\x3e\x95\xa3" + \
b"\x32\x6e\x9a\xa5\x61\xfa\x21\x40\xed\x44\x6b\xf9\x24\x65\x43\xf3\xe2\xab\xfe\x52\x4b\x6b\x52\xd9\x63\x00\x9f\x6d\x35\x19\xcd\x09" + \
b"\x08\xd8\x51\xef\x9b\x5f\x34\x1b\x42\xa8\xf7\x7a\x65\x05\x60\x19\xa9\xcb\xbf\x28\x6b\x4b\xff\xea\x58\xe1\xe6\xfc\xed\x0c\x3d\x9f" + \
b"\x83\x31"
certs = [b"\x30\x82\x02\xb3\x30\x82\x01\x9b\xa0\x03\x02\x01\x02\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30" + \
b"\x1d\x31\x1b\x30\x19\x06\x03\x55\x04\x03\x0c\x12\x6e\x6f\x6e\x5f\x61\x73\x63\x69\x69\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x30\x1e" + \
b"\x17\x0d\x31\x36\x30\x35\x31\x35\x31\x38\x35\x31\x30\x30\x5a\x17\x0d\x31\x38\x30\x35\x31\x35\x31\x38\x35\x31\x30\x30\x5a\x30\x1d" + \
b"\x31\x1b\x30\x19\x06\x03\x55\x04\x03\x0c\x12\x6e\x6f\x6e\x5f\x61\x73\x63\x69\x69\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x30\x82\x01" + \
b"\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\x8a\xf3" + \
b"\x6e\x34\xf0\xfa\xfd\xec\xad\x27\x63\xe1\x66\x8a\x66\xcf\xfa\x1b\xcf\x83\x75\xe3\xb5\xcf\x23\x52\xe1\x37\x29\x77\xe5\x9a\x30\xa5" + \
b"\xf3\xdc\x42\x3a\x62\x00\x7c\x87\xea\xcf\x81\xdb\x40\x4f\x36\x92\x8a\xff\x79\x25\x55\x2e\x55\xdb\xac\x09\x7d\xf1\x5c\x72\xd4\x60" + \
b"\x0f\x81\x65\x11\x89\x06\x89\x58\x37\x05\xbc\x4a\x57\x0b\xce\xe7\x80\xe6\xc9\x3f\xf2\xd5\xc6\x7b\x38\x3a\xf2\x4e\x3d\xff\x1a\xb9" + \
b"\xd7\xfe\x8f\x9f\x08\x4c\xf0\x5b\xe4\xe2\x3e\x67\x9d\x3a\x04\x15\x04\xc4\xbd\x3f\x85\xb8\x3d\x7c\xde\x3b\xcc\x24\x44\x51\x77\xee" + \
b"\xf0\x99\xf1\x65\x0b\x8f\xda\xda\xa2\xc8\x6e\x4c\xa1\xd9\x1c\x7c\x97\xc8\x3b\x37\xc0\x83\x5e\x16\x84\x58\x25\x37\xa7\x17\x01\xd3" + \
b"\xb3\xb8\xd3\xdc\x66\x7f\x8d\x87\x2e\x80\x03\x19\xf5\x01\xb6\x8c\xf9\xab\xdf\xe1\x22\x99\x96\x2d\x97\x33\xcc\x95\x07\x4b\xa2\xdf" + \
b"\x65\x93\xf6\x86\x41\xea\xfd\x65\xbf\xeb\x46\xf2\xe3\x9d\x78\x32\xf5\xd7\xad\x4d\x6b\x5a\xa1\xcd\x01\xf0\x8e\x3d\x26\x52\xf5\x40" + \
b"\xc4\xd5\xfb\x10\x9b\xa1\x74\xa9\x78\xc1\x1a\x3e\x0a\x42\x4f\x37\xa9\x59\x83\x70\xd2\xe0\x33\x26\xfa\xd9\x24\xfb\xdc\x5d\x02\x03" + \
b"\x01\x00\x01\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x3a\x72\xed\xf2\x21\x28\x67\xed\xfa" + \
b"\xeb\xc0\xf3\xa4\xd4\x0a\x49\xca\x26\xfb\x04\x20\xbc\x3a\x87\xea\xa2\x71\x45\xe2\x24\xaf\x9d\xc5\x1d\x1a\xdf\xf0\xa9\x95\x81\xe6" + \
b"\xd2\x8f\x0f\x3f\xaa\xcf\xb3\x97\x72\x9c\xf2\x80\x52\x8a\xf5\xf0\x23\xb2\x88\x88\xaa\xc4\xe9\xc3\xc8\x69\xb4\xca\x90\x8c\x9b\x15" + \
b"\x00\x10\x2f\x31\x09\x49\x8a\x31\x60\x1f\x49\xf4\x4e\x33\x2a\xc4\xa0\x78\xcd\x0c\x56\x41\xce\xbb\xb7\x8f\x30\x4a\xdb\x83\xaf\xa2" + \
b"\xe3\xc4\xe0\x2a\xb2\xa2\x7b\xac\xde\x0f\x8a\x97\x2b\x24\x4a\xfe\x08\x21\x4a\x36\xbe\x9b\x3b\xa7\xa3\x66\x29\x9f\x7e\xd9\x34\x69" + \
b"\xac\x3f\x7f\x33\xe3\x3c\x86\x5b\x9a\x6b\x27\xa4\x23\x2a\xc4\x2e\x0f\x1f\x15\x15\xe7\xb3\x7e\xad\x5a\xcc\x5c\xb1\xf4\x3e\x9a\x85" + \
b"\x82\x02\xd1\x82\x29\x64\xcc\xe1\x24\x21\xf2\x72\xdd\x9b\xdb\x72\x94\x4c\x2a\x61\x91\x51\xe3\xde\x72\x34\x3a\xe7\xea\xf7\x26\x3b" + \
b"\xa0\x32\x24\x39\x53\xad\x2e\xca\x8d\x5c\x82\x49\x09\x5e\x31\x90\xe1\x2a\xe3\xfa\x1d\x00\x8e\x51\xb6\x30\x19\x33\x0d\x00\x1b\x80" + \
b"\x93\xd6\x95\xea\x1c\x44\xb1\x9b\x08\x6a\x7e\x82\x9e\xb0\xc4\x72\x7c\x76\xf9\x9f\x9d\x04\xd9"]
pyjks-20.0.0/tests/java/ 0000775 0000000 0000000 00000000000 13646725476 0015040 5 ustar 00root root 0000000 0000000 pyjks-20.0.0/tests/java/.gitignore 0000664 0000000 0000000 00000000047 13646725476 0017031 0 ustar 00root root 0000000 0000000 .classpath
.project
.settings
/target/
pyjks-20.0.0/tests/java/pom.xml 0000664 0000000 0000000 00000002264 13646725476 0016361 0 ustar 00root root 0000000 0000000 4.0.0org.pyjkspyjks1.0.0UTF-8junitjunit4.9testcommons-codeccommons-codec1.10testcommons-iocommons-io2.4testorg.apache.commonscommons-lang33.4org.bouncycastlebcpkix-jdk15on1.54