pax_global_header00006660000000000000000000000064136467254760014536gustar00rootroot0000000000000052 comment=07e5932fbc7b5c10f28b0d2461e2ec4f581c6f62 pyjks-20.0.0/000077500000000000000000000000001364672547600127555ustar00rootroot00000000000000pyjks-20.0.0/.gitignore000066400000000000000000000005301364672547600147430ustar00rootroot00000000000000*.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.yml000066400000000000000000000003321364672547600150640ustar00rootroot00000000000000language: 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.md000066400000000000000000000053641364672547600145760ustar00rootroot00000000000000# 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/LICENSE000066400000000000000000000020641364672547600137640ustar00rootroot00000000000000The 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.in000066400000000000000000000001551364672547600145140ustar00rootroot00000000000000include *.md include *.txt include LICENSE exclude *.yml exclude tox.ini prune docs prune tests prune tools pyjks-20.0.0/README.md000066400000000000000000000067441364672547600142470ustar00rootroot00000000000000pyjks ===== 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.yml000066400000000000000000000012741364672547600153510ustar00rootroot00000000000000version: 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/000077500000000000000000000000001364672547600137055ustar00rootroot00000000000000pyjks-20.0.0/docs/Makefile000066400000000000000000000166621364672547600153600ustar00rootroot00000000000000# 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.rst000066400000000000000000000163371364672547600152300ustar00rootroot00000000000000BouncyCastle 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.rst000066400000000000000000000041671364672547600162650ustar00rootroot00000000000000Concepts ======== 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.py000066400000000000000000000272671364672547600152220ustar00rootroot00000000000000# -*- 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.rst000066400000000000000000000071351364672547600162630ustar00rootroot00000000000000Examples ======== 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.rst000066400000000000000000000013651364672547600166250ustar00rootroot00000000000000Exceptions ========== 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.rst000066400000000000000000000032401364672547600155450ustar00rootroot00000000000000.. 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.rst000066400000000000000000000102341364672547600152260ustar00rootroot00000000000000JKS 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.bat000066400000000000000000000170621364672547600153200ustar00rootroot00000000000000@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/000077500000000000000000000000001364672547600135445ustar00rootroot00000000000000pyjks-20.0.0/jks/__init__.py000066400000000000000000000001251364672547600156530ustar00rootroot00000000000000from .jks import * from .jks import __version__, __version_info__ from .bks import * pyjks-20.0.0/jks/bks.py000066400000000000000000000554131364672547600147050ustar00rootroot00000000000000# 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.py000066400000000000000000001026511364672547600147120ustar00rootroot00000000000000# 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.py000066400000000000000000000004251364672547600152240ustar00rootroot00000000000000# 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.py000066400000000000000000000140061364672547600152150ustar00rootroot00000000000000# 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.py000066400000000000000000000133501364672547600163250ustar00rootroot00000000000000# 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.py000066400000000000000000000230451364672547600150770ustar00rootroot00000000000000# 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.txt000066400000000000000000000001531364672547600172150ustar00rootroot00000000000000-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.txt000066400000000000000000000001331364672547600162360ustar00rootroot00000000000000pyasn1==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.cfg000066400000000000000000000000711364672547600145740ustar00rootroot00000000000000[wheel] universal = 1 [metadata] license_file = LICENSE pyjks-20.0.0/setup.py000066400000000000000000000043751364672547600145000ustar00rootroot00000000000000"""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/000077500000000000000000000000001364672547600141175ustar00rootroot00000000000000pyjks-20.0.0/tests/__init__.py000066400000000000000000000000651364672547600162310ustar00rootroot00000000000000#!/usr/bin/env python """ Test package for pyjks """ pyjks-20.0.0/tests/expected/000077500000000000000000000000001364672547600157205ustar00rootroot00000000000000pyjks-20.0.0/tests/expected/DSA2048.py000066400000000000000000000261361364672547600172270ustar00rootroot00000000000000public_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.py000066400000000000000000000127501364672547600172330ustar00rootroot00000000000000public_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.py000066400000000000000000000534711364672547600205320ustar00rootroot00000000000000public_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__.py000066400000000000000000000002571364672547600200350ustar00rootroot00000000000000from . 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.py000066400000000000000000000127501364672547600211330ustar00rootroot00000000000000public_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.py000066400000000000000000000131651364672547600237770ustar00rootroot00000000000000public_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.py000066400000000000000000000240571364672547600231420ustar00rootroot00000000000000public_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.py000066400000000000000000000240231364672547600230260ustar00rootroot00000000000000public_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/000077500000000000000000000000001364672547600150405ustar00rootroot00000000000000pyjks-20.0.0/tests/java/.gitignore000066400000000000000000000000471364672547600170310ustar00rootroot00000000000000.classpath .project .settings /target/ pyjks-20.0.0/tests/java/pom.xml000066400000000000000000000022641364672547600163610ustar00rootroot00000000000000 4.0.0 org.pyjks pyjks 1.0.0 UTF-8 junit junit 4.9 test commons-codec commons-codec 1.10 test commons-io commons-io 2.4 test org.apache.commons commons-lang3 3.4 org.bouncycastle bcpkix-jdk15on 1.54 pyjks-20.0.0/tests/java/src/000077500000000000000000000000001364672547600156275ustar00rootroot00000000000000pyjks-20.0.0/tests/java/src/test/000077500000000000000000000000001364672547600166065ustar00rootroot00000000000000pyjks-20.0.0/tests/java/src/test/java/000077500000000000000000000000001364672547600175275ustar00rootroot00000000000000pyjks-20.0.0/tests/java/src/test/java/org/000077500000000000000000000000001364672547600203165ustar00rootroot00000000000000pyjks-20.0.0/tests/java/src/test/java/org/pyjks/000077500000000000000000000000001364672547600214565ustar00rootroot00000000000000pyjks-20.0.0/tests/java/src/test/java/org/pyjks/BksKeystoreGeneratorTest.java000066400000000000000000000320001364672547600272700ustar00rootroot00000000000000package org.pyjks; import java.io.File; import java.lang.reflect.Constructor; import java.security.Key; import java.security.KeyPair; import java.security.KeyStore; import java.security.KeyStoreSpi; import java.security.Provider; import java.security.Security; import java.security.cert.Certificate; import java.util.Date; import java.util.Hashtable; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.binary.StringUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.reflect.FieldUtils; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.PBEParametersGenerator; import org.bouncycastle.crypto.digests.SHA1Digest; import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.ParametersWithIV; import org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.Test; public class BksKeystoreGeneratorTest extends PyJksTestCase { protected byte[] generatePkcs12DerivedKey(Digest digest, int purpose, String password, byte[] salt, int iterationCount, int numDesiredBytes) throws Exception { PKCS12ParametersGenerator gen = new PKCS12ParametersGenerator(digest); byte[] password_bytes = PBEParametersGenerator.PKCS12PasswordToBytes(password.toCharArray()); gen.init(password_bytes, salt, iterationCount); if (purpose == PKCS12ParametersGenerator.MAC_MATERIAL) { KeyParameter params = (KeyParameter) gen.generateDerivedMacParameters(numDesiredBytes*8); return params.getKey(); } else { ParametersWithIV params = (ParametersWithIV) gen.generateDerivedParameters(numDesiredBytes*8, numDesiredBytes*8); if (purpose == PKCS12ParametersGenerator.KEY_MATERIAL) { return ((KeyParameter) params.getParameters()).getKey(); } else if (purpose == PKCS12ParametersGenerator.IV_MATERIAL) { return params.getIV(); } } throw new RuntimeException("No such purpose byte"); } @Test public void generatePkcs12KDFTestVectors() throws Exception { int MAC = PKCS12ParametersGenerator.MAC_MATERIAL; int KEY = PKCS12ParametersGenerator.KEY_MATERIAL; int IV = PKCS12ParametersGenerator.IV_MATERIAL; System.out.println(toPythonString(generatePkcs12DerivedKey(new SHA1Digest(), MAC, "", new byte[]{1,2,3,4,5,6,7,8}, 1000, 16))); System.out.println(toPythonString(generatePkcs12DerivedKey(new SHA1Digest(), MAC, "", new byte[]{1,2,3,4,5,6,7,8}, 1000, 17))); System.out.println(toPythonString(generatePkcs12DerivedKey(new SHA1Digest(), KEY, "", new byte[]{-65,10,-86,79,-124,-76,78,65,22,10,17,-73,-19,-104,88,-96,-107,59,75,-8}, 2010, 2))); System.out.println(toPythonString(generatePkcs12DerivedKey(new SHA1Digest(), MAC, "password", new byte[]{1,2,3,4,5,6,7,8}, 1000, 16))); System.out.println(toPythonString(generatePkcs12DerivedKey(new SHA1Digest(), MAC, "password", new byte[]{1,2,3,4,5,6,7,8}, 1000, 17))); System.out.println(toPythonString(generatePkcs12DerivedKey(new SHA1Digest(), KEY, "password", new byte[]{1,2,3,4,5,6,7,8}, 1000, 17))); System.out.println(toPythonString(generatePkcs12DerivedKey(new SHA1Digest(), IV, "password", new byte[]{1,2,3,4,5,6,7,8}, 1000, 17))); String fancyPassword = StringUtils.newStringUtf16Be(new byte[]{ (byte) 0x10, (byte) 0xDA, (byte) 0x00, (byte) 0x28, (byte) 0x0C, (byte) 0xA0, (byte) 0x76, (byte) 0xCA, (byte) 0x0C, (byte) 0xA0, (byte) 0x10, (byte) 0xDA, (byte) 0x00, (byte) 0x29, }); System.out.println(toPythonString(generatePkcs12DerivedKey(new SHA1Digest(), KEY, fancyPassword, new byte[]{1,2,3,4,5,6,7,8}, 1000, 129))); } protected byte[] encryptPBEWithSHAAndTwofishCBC(byte[] input, String password, byte[] salt, int iterationCount) throws Exception { Provider bcProv = Security.getProvider("BC"); if (bcProv == null) Security.addProvider(new BouncyCastleProvider()); try { Cipher c = makePBECipher("PBEWithSHAAndTwofish-CBC", Cipher.ENCRYPT_MODE, password, salt, iterationCount); return c.doFinal(input); } finally { if (bcProv == null) Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); } } protected byte[] encryptPBEWithSHAAnd3KeyTripleDESCBC(byte[] input, String password, byte[] salt, int iterationCount) throws Exception { Provider bcProv = Security.getProvider("BC"); if (bcProv == null) Security.addProvider(new BouncyCastleProvider()); try { Cipher c = makePBECipher("PBEWithSHAAnd3-KeyTripleDES-CBC", Cipher.ENCRYPT_MODE, password, salt, iterationCount); return c.doFinal(input); } finally { if (bcProv == null) Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); } } @Test public void generatePBEWithSHAAndTwofishCBCTestVectors() throws Exception { String fancyPassword = StringUtils.newStringUtf16Be(new byte[]{ (byte) 0x10, (byte) 0xDA, (byte) 0x00, (byte) 0x28, (byte) 0x0C, (byte) 0xA0, (byte) 0x76, (byte) 0xCA, (byte) 0x0C, (byte) 0xA0, (byte) 0x10, (byte) 0xDA, (byte) 0x00, (byte) 0x29, }); System.out.println(toPythonString(encryptPBEWithSHAAndTwofishCBC("sample".getBytes("UTF-8"), "mypassword", new byte[]{1,2,3,4,5,6,7,8}, 1000))); System.out.println(toPythonString(encryptPBEWithSHAAndTwofishCBC("sample".getBytes("UTF-8"), fancyPassword, new byte[]{1,2,3,4,5,6,7,8}, 1000))); System.out.println(toPythonString(encryptPBEWithSHAAndTwofishCBC("-------16-------".getBytes("UTF-8"), "mypassword", new byte[]{1,2,3,4,5,6,7,8}, 1000))); System.out.println(toPythonString(encryptPBEWithSHAAndTwofishCBC("-------16-------".getBytes("UTF-8"), fancyPassword, new byte[]{1,2,3,4,5,6,7,8}, 1000))); } @Test public void generatePBEWithSHAAnd3KeyTripleDESCBCTestVectors() throws Exception { String fancyPassword = StringUtils.newStringUtf16Be(new byte[]{ (byte) 0x10, (byte) 0xDA, (byte) 0x00, (byte) 0x28, (byte) 0x0C, (byte) 0xA0, (byte) 0x76, (byte) 0xCA, (byte) 0x0C, (byte) 0xA0, (byte) 0x10, (byte) 0xDA, (byte) 0x00, (byte) 0x29, }); System.out.println(toPythonString(encryptPBEWithSHAAnd3KeyTripleDESCBC("sample".getBytes("UTF-8"), "mypassword", new byte[]{1,2,3,4,5,6,7,8}, 1000))); System.out.println(toPythonString(encryptPBEWithSHAAnd3KeyTripleDESCBC("sample".getBytes("UTF-8"), fancyPassword, new byte[]{1,2,3,4,5,6,7,8}, 1000))); System.out.println(toPythonString(encryptPBEWithSHAAnd3KeyTripleDESCBC("-------16-------".getBytes("UTF-8"), "mypassword", new byte[]{1,2,3,4,5,6,7,8}, 1000))); System.out.println(toPythonString(encryptPBEWithSHAAnd3KeyTripleDESCBC("-------16-------".getBytes("UTF-8"), fancyPassword, new byte[]{1,2,3,4,5,6,7,8}, 1000))); } protected void populateChristmasStore(KeyStore ks, char[] passwordChars, KeyPair keyPair, SecretKey secretKey, SecretKey plainKey, Certificate cert, byte[] storedValue) throws Exception { Certificate[] certs = new Certificate[]{cert}; ks.setCertificateEntry("cert", cert); ks.setKeyEntry("sealed_private_key", keyPair.getPrivate(), passwordChars, certs); ks.setKeyEntry("sealed_public_key", keyPair.getPublic(), passwordChars, null); ks.setKeyEntry("sealed_secret_key", secretKey, passwordChars, null); ks.setKeyEntry("stored_value", storedValue, null); addPlainKeyEntry(ks, "plain_key", plainKey, new Certificate[]{}); } protected void populateCustomEntryPasswordsStore(KeyStore ks, KeyPair keyPair, SecretKey secretKey, Certificate[] certs) throws Exception { ks.setKeyEntry("sealed_private_key", keyPair.getPrivate(), "private_password".toCharArray(), certs); ks.setKeyEntry("sealed_public_key", keyPair.getPublic(), "public_password".toCharArray(), certs); ks.setKeyEntry("sealed_secret_key", secretKey, "secret_password".toCharArray(), certs); } /** * Adds an entry of type KEY to the given (BKS) keystore. */ @SuppressWarnings({"unchecked", "rawtypes"}) protected void addPlainKeyEntry(KeyStore store, String alias, Key key, Certificate[] chain) throws Exception { // it's no longer possible to create BKS entries of type 'KEY', but we still want to able to handle them on the python side, // so we'll have to dig into the internals of BKS keystores a bit to get such an entry created KeyStoreSpi spi = (KeyStoreSpi) FieldUtils.readField(store, "keyStoreSpi", true); if (!(spi instanceof BcKeyStoreSpi)) throw new RuntimeException("Wrong keystore supplied, must be a BC keystore"); BcKeyStoreSpi bcSpi = (BcKeyStoreSpi) spi; Class storeEntryClazz = Class.forName(BcKeyStoreSpi.class.getName() + "$StoreEntry"); Constructor constr = storeEntryClazz.getDeclaredConstructor(BcKeyStoreSpi.class, String.class, Date.class, int.class, Object.class, Certificate[].class); constr.setAccessible(true); Object storeEntry = constr.newInstance(bcSpi, alias, new Date(), 2 /*BcKeyStoreSpi.KEY*/, key, chain); Hashtable entryTable = (Hashtable) FieldUtils.readField(bcSpi, "table", true); entryTable.put(alias, storeEntry); } @Test public void bks_empty() throws Exception { Security.insertProviderAt(new BouncyCastleProvider(), 1); try { generateKeyStore("BKS-V1", "../keystores/bks/empty.bksv1", null, null, ""); generateKeyStore("BKS", "../keystores/bks/empty.bksv2", null, null, ""); generateKeyStore("UBER", "../keystores/uber/empty.uber", null, null, ""); } catch (Exception e) { System.err.println(e.getMessage()); throw e; } finally { Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); } } /** * Generates BKS and UBER keystores containing all possible entry types (past and present). */ @Test public void bks_christmas_store() throws Exception { Security.insertProviderAt(new BouncyCastleProvider(), 1); try { // generate a bunch of key material of all different supported types SecretKey aesKey = new SecretKeySpec(Hex.decodeHex("3f680504c66cc25aae65d0fa49c526ec".toCharArray()), "AES"); SecretKey desKey = SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec(Hex.decodeHex("4cf2fe915d082a43".toCharArray()))); KeyPair keyPair = generateKeyPair("RSA", 1024); Certificate cert = createSelfSignedCertificate(keyPair, "CN=RSA1024"); char[] passwordChars = "12345678".toCharArray(); // generate one of each store type KeyStore ks = KeyStore.getInstance("BKS-V1"); ks.load(null, null); populateChristmasStore(ks, passwordChars, keyPair, aesKey, desKey, cert, new byte[]{2,3,5,7,11,13,17,19,23}); ks.store(FileUtils.openOutputStream(new File("../keystores/bks/christmas.bksv1")), passwordChars); ks = KeyStore.getInstance("BKS"); ks.load(null, null); populateChristmasStore(ks, passwordChars, keyPair, aesKey, desKey, cert, new byte[]{2,3,5,7,11,13,17,19,23}); ks.store(FileUtils.openOutputStream(new File("../keystores/bks/christmas.bksv2")), passwordChars); ks = KeyStore.getInstance("UBER"); ks.load(null, null); populateChristmasStore(ks, passwordChars, keyPair, aesKey, desKey, cert, new byte[]{2,3,5,7,11,13,17,19,23}); ks.store(FileUtils.openOutputStream(new File("../keystores/uber/christmas.uber")), passwordChars); writePythonDataFile("../expected/bks_christmas.py", keyPair, new Certificate[]{cert}); } catch (Exception e) { System.err.println(e.getMessage()); throw e; } finally { Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); } } /** * Generates BKS and UBER keystores containing keys encrypted with passwords that are different from * the store password. */ @Test public void bks_custom_entry_passwords() throws Exception { Security.insertProviderAt(new BouncyCastleProvider(), 1); try { KeyPair keyPair = generateKeyPair("RSA", 1024); Certificate cert = createSelfSignedCertificate(keyPair, "CN=custom_entry_passwords1"); Certificate[] certs = new Certificate[]{cert}; SecretKey aesKey = new SecretKeySpec(Hex.decodeHex("05e2aaa5e7cdf50be459ea6c64f01b21".toCharArray()), "AES"); char[] store_password = "store_password".toCharArray(); // generate one of each store type KeyStore ks = KeyStore.getInstance("BKS-V1"); ks.load(null, null); populateCustomEntryPasswordsStore(ks, keyPair, aesKey, certs); ks.store(FileUtils.openOutputStream(new File("../keystores/bks/custom_entry_passwords.bksv1")), store_password); ks = KeyStore.getInstance("BKS"); ks.load(null, null); populateCustomEntryPasswordsStore(ks, keyPair, aesKey, certs); ks.store(FileUtils.openOutputStream(new File("../keystores/bks/custom_entry_passwords.bksv2")), store_password); ks = KeyStore.getInstance("UBER"); ks.load(null, null); populateCustomEntryPasswordsStore(ks, keyPair, aesKey, certs); ks.store(FileUtils.openOutputStream(new File("../keystores/uber/custom_entry_passwords.uber")), store_password); writePythonDataFile("../expected/bks_custom_entry_passwords.py", keyPair, new Certificate[]{cert}); } catch (Exception e) { System.err.println(e.getMessage()); throw e; } finally { Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); } } } pyjks-20.0.0/tests/java/src/test/java/org/pyjks/DummyObject.java000066400000000000000000000002531364672547600245430ustar00rootroot00000000000000package org.pyjks; import java.io.Serializable; public class DummyObject implements Serializable { private static final long serialVersionUID = 7291595530223433260L; } pyjks-20.0.0/tests/java/src/test/java/org/pyjks/JceKeystoreGeneratorTest.java000066400000000000000000000111061364672547600272560ustar00rootroot00000000000000package org.pyjks; import javax.crypto.Cipher; import javax.crypto.SealedObject; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import javax.crypto.spec.DESedeKeySpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.reflect.FieldUtils; import org.junit.Test; /** * Prepares JCEKS keystores that use features that are specific to JCE and are not supported by JKS keystores. */ public class JceKeystoreGeneratorTest extends PyJksTestCase { @Test public void jceks_DES() throws Exception { // Note: SunJCE's DESKeyFactory will construct a DESKey from the input bytes given by the DESKeySpec after correcting for parity bits, // so to ensure that the resulting key that gets stored is the exact same as our input bytes given here, we have to choose our // input key bytes in such a way that the parity bit corrections don't affect it. DESKeySpec keySpec = new DESKeySpec(Hex.decodeHex("4cf2fe915d082a43".toCharArray())); SecretKey key = SecretKeyFactory.getInstance("DES").generateSecret(keySpec); generateSecretKeyStore("../keystores/jceks/DES.jceks", key); } @Test public void jceks_DESede() throws Exception { // Same comments as for the DES case DESedeKeySpec keySpec = new DESedeKeySpec(Hex.decodeHex("675e5245e9673b4c8fc194ceec433b318c45c2e0675e5245".toCharArray())); SecretKey key = SecretKeyFactory.getInstance("DESede").generateSecret(keySpec); generateSecretKeyStore("../keystores/jceks/DESede.jceks", key); } @Test public void jceks_PBKDF2() throws Exception { PBEKeySpec keySpec = new PBEKeySpec("fibonacci".toCharArray(), new byte[]{1,1,2,3,5,8,13,21}, 999, 256); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); SecretKey key = factory.generateSecret(keySpec); generateSecretKeyStore("../keystores/jceks/PBKDF2WithHmacSHA1.jceks", key); } @Test public void jceks_AES() throws Exception { generateSecretKeyStore("../keystores/jceks/AES128.jceks", new SecretKeySpec(Hex.decodeHex("666e0221cc44c1fc4aabf458f9dfdd3c".toCharArray()), "AES")); generateSecretKeyStore("../keystores/jceks/AES256.jceks", new SecretKeySpec(Hex.decodeHex("e7d7c262668221787b6b5a0f687712fde4be52e9e7d7c262668221787b6b5a0f".toCharArray()), "AES")); } @Test public void jceks_unknown_type_of_sealed_object() throws Exception { // create a keystore with a SecretKeyEntry that has a serialized object inside of it that is *not* of type javax.crypto.SealedObject String filename = "../keystores/jceks/unknown_type_of_sealed_object.jceks"; String alias = "mykey"; String password = "12345678"; generateManualSealedObjectStore(filename, password, alias, new DummyObject()); } @Test public void jceks_unknown_type_inside_sealed_object() throws Exception { // create a keystore with a SecretKeyEntry with a proper SealedObject instance, but with an unexpected Java type encrypted inside the SealedObject String filename = "../keystores/jceks/unknown_type_inside_sealed_object.jceks"; String alias = "mykey"; String password = "12345678"; // encrypt the enclosed serialized object with PBEWithMD5AndTripleDES, as the Sun JCE key store implementation does Cipher cipher = makePBECipher("PBEWithMD5AndTripleDES", Cipher.ENCRYPT_MODE, password, new byte[]{83, 79, 95, 83, 65, 76, 84, 89}, 42); SealedObject so = new SealedObject(new DummyObject(), cipher); generateManualSealedObjectStore(filename, password, alias, so); } @Test public void jceks_unknown_sealed_object_sealAlg() throws Exception { // create a keystore with a SecretKeyEntry with a proper SealedObject instance, but with an unexpected sealing algorithm String filename = "../keystores/jceks/unknown_sealed_object_sealAlg.jceks"; String alias = "mykey"; String password = "12345678"; // encrypt the enclosed serialized object with PBEWithMD5AndTripleDES, as the Sun JCE key store implementation does PBEParameterSpec pbeParamSpec = new PBEParameterSpec(new byte[]{83, 79, 95, 83, 65, 76, 84, 89}, 42); PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); SecretKey pbeKey = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES").generateSecret(pbeKeySpec); Cipher cipher = Cipher.getInstance("PBEWithMD5AndTripleDES"); cipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec); SealedObject so = new SealedObject(new DummyObject(), cipher); FieldUtils.writeField(so, "sealAlg", "nonsense", true); generateManualSealedObjectStore(filename, password, alias, so); } } pyjks-20.0.0/tests/java/src/test/java/org/pyjks/KeystoreGeneratorTest.java000066400000000000000000000142011364672547600266330ustar00rootroot00000000000000package org.pyjks; import java.security.KeyPair; import java.security.KeyStore; import java.security.cert.Certificate; import java.util.HashMap; import java.util.Map; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.binary.StringUtils; import org.junit.Test; /** * Generates JKS/JCEKS keystores that use features available to either format. */ public class KeystoreGeneratorTest extends PyJksTestCase { @Test public void generate_empty() throws Exception { generateKeyStore("JKS", "../keystores/jks/empty.jks", null, null, ""); generateKeyStore("JCEKS", "../keystores/jceks/empty.jceks", null, null, ""); } @Test public void generate_RSA1024() throws Exception { KeyPair keyPair = generateKeyPair("RSA", 1024); Certificate cert = createSelfSignedCertificate(keyPair, "CN=RSA1024"); Certificate[] certs = new Certificate[]{cert}; generatePrivateKeyStore("JKS", "../keystores/jks/RSA1024.jks", keyPair.getPrivate(), certs); generatePrivateKeyStore("JCEKS", "../keystores/jceks/RSA1024.jceks", keyPair.getPrivate(), certs); writePythonDataFile("../expected/RSA1024.py", keyPair, certs); } @Test public void generate_RSA2048_3certs() throws Exception { KeyPair keyPair = generateKeyPair("RSA", 2048); // these do not form a chain, but that doesn't really matter for our purposes Certificate cert1 = createSelfSignedCertificate(keyPair, "CN=RSA2048, O=1"); Certificate cert2 = createSelfSignedCertificate(keyPair, "CN=RSA2048, O=2"); Certificate cert3 = createSelfSignedCertificate(keyPair, "CN=RSA2048, O=3"); Certificate[] certs = new Certificate[]{ cert1, cert2, cert3 }; generatePrivateKeyStore("JKS", "../keystores/jks/RSA2048_3certs.jks", keyPair.getPrivate(), certs); generatePrivateKeyStore("JCEKS", "../keystores/jceks/RSA2048_3certs.jceks", keyPair.getPrivate(), certs); // and while we have some certificates here anyway, we might as well produce some stores with those in them too String[] certAliases = new String[]{"cert1", "cert2", "cert3"}; generateCertsKeyStore("JKS", "../keystores/jks/3certs.jks", certs, certAliases); generateCertsKeyStore("JCEKS", "../keystores/jceks/3certs.jceks", certs, certAliases); writePythonDataFile("../expected/RSA2048_3certs.py", keyPair, certs); } @Test public void generate_DSA2048() throws Exception { KeyPair keyPair = generateKeyPair("DSA", 2048); Certificate cert = createSelfSignedCertificate(keyPair, "CN=DSA2048"); Certificate[] certs = new Certificate[]{ cert }; generatePrivateKeyStore("JKS", "../keystores/jks/DSA2048.jks", keyPair.getPrivate(), certs); generatePrivateKeyStore("JCEKS", "../keystores/jceks/DSA2048.jceks", keyPair.getPrivate(), certs); writePythonDataFile("../expected/DSA2048.py", keyPair, certs); } @Test public void generate_custom_entry_passwords() throws Exception { // create JKS and JCEKS keystores containing entries of each type, each with a different entry password Map entriesByAlias = new HashMap(); Map passwordsByAlias = new HashMap(); // produce some key material KeyPair keyPair = generateKeyPair("RSA", 2048); Certificate cert = createSelfSignedCertificate(keyPair, "CN=custom_entry_passwords"); Certificate[] certs = new Certificate[]{ cert }; SecretKey secretKey = new SecretKeySpec(Hex.decodeHex("3f680504c66cc25aae65d0fa49c526ec".toCharArray()), "AES"); // write JKS keystore entriesByAlias.put("cert", new KeyStore.TrustedCertificateEntry(cert)); entriesByAlias.put("private", new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), certs)); passwordsByAlias.put("private", "private_password"); generateKeyStore("JKS", "../keystores/jks/custom_entry_passwords.jks", entriesByAlias, passwordsByAlias, "store_password"); // add secret key entries and write JCEKS keystore entriesByAlias.put("secret", new KeyStore.SecretKeyEntry(secretKey)); passwordsByAlias.put("secret", "secret_password"); generateKeyStore("JCEKS", "../keystores/jceks/custom_entry_passwords.jceks", entriesByAlias, passwordsByAlias, "store_password"); writePythonDataFile("../expected/custom_entry_passwords.py", keyPair, certs); } @Test public void generate_duplicate_aliases() throws Exception { KeyPair keyPair = generateKeyPair("RSA", 1024); Certificate cert1 = createSelfSignedCertificate(keyPair, "CN=duplicate_aliases, O=1"); Certificate cert2 = createSelfSignedCertificate(keyPair, "CN=duplicate_aliases, O=2"); String[] aliases = new String[]{"my_alias", "my_alias"}; int[] tags = new int[]{TAG_TRUSTED_CERT, TAG_TRUSTED_CERT}; byte[][] entriesData = new byte[][]{ encodeTrustedCert(cert1), encodeTrustedCert(cert2) }; generateManualStore("JKS", "../keystores/jks/duplicate_aliases.jks", aliases, tags, entriesData, "12345678"); generateManualStore("JCEKS", "../keystores/jceks/duplicate_aliases.jceks", aliases, tags, entriesData, "12345678"); } @Test public void generate_non_ascii_jks_password() throws Exception { // The JKS keystore protector algorithm says that the password is expected to be ASCII but it doesn't enforce that, // so there's nothing stopping you from using non-ASCII passwords anyway. Let's generate one and see if we can parse it. KeyPair keyPair = generateKeyPair("RSA", 2048); Certificate cert = createSelfSignedCertificate(keyPair, "CN=non_ascii_password"); Certificate[] certs = new Certificate[]{cert}; // Note: prefer not to use the \\uXXXX syntax here because the Java compiler interprets them *prior* to lexing (!) // causing them to interfere with syntax ... String non_ascii_password = StringUtils.newStringUtf16Be(new byte[]{ (byte) 0x10, (byte) 0xDA, (byte) 0x00, (byte) 0x28, (byte) 0x0C, (byte) 0xA0, (byte) 0x76, (byte) 0xCA, (byte) 0x0C, (byte) 0xA0, (byte) 0x10, (byte) 0xDA, (byte) 0x00, (byte) 0x29, }); generatePrivateKeyStore("JKS", "../keystores/jks/non_ascii_password.jks", keyPair.getPrivate(), certs, non_ascii_password, non_ascii_password, "mykey"); writePythonDataFile("../expected/jks_non_ascii_password.py", keyPair, certs); } } pyjks-20.0.0/tests/java/src/test/java/org/pyjks/MiscTest.java000066400000000000000000000020421364672547600240520ustar00rootroot00000000000000package org.pyjks; import org.junit.Test; public class MiscTest extends PyJksTestCase { /** * Encrypt some known data with PBEWithMD5AndTripleDES to verify correct decryption in python. * In particular, exercise the edge case where the two salt halves are equal, because there's a bug in the JCE lurking there * (see the python side for details) */ @Test public void generate_PBEWithMD5AndTripleDES_samples() throws Exception { byte[] output1 = encryptPBEWithMD5AndTripleDES("sample".getBytes(), "my_password", new byte[]{1,2,3,4,5,6,7,8}, 42); byte[] output2 = encryptPBEWithMD5AndTripleDES("sample".getBytes(), "my_password", new byte[]{1,2,3,4,1,2,3,4}, 42); // special case for SunJCE's PBEWithMD5AndTripleDES: identical salt halves byte[] output3 = encryptPBEWithMD5AndTripleDES("sample".getBytes(), "my_password", new byte[]{1,2,3,4,1,2,3,5}, 42); // control case for the previous one System.out.println(toPythonString(output1)); System.out.println(toPythonString(output2)); System.out.println(toPythonString(output3)); } } pyjks-20.0.0/tests/java/src/test/java/org/pyjks/PyJksTestCase.java000066400000000000000000000357071364672547600250310ustar00rootroot00000000000000package org.pyjks; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.DigestOutputStream; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.Security; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.RSAPrivateKey; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; /** * Superclass for pyjks test case generators; mostly here to provide utility functions. */ public class PyJksTestCase { public static int TAG_PRIVATE_KEY = 1; public static int TAG_TRUSTED_CERT = 2; public static int TAG_SECRET_KEY = 3; public static String toPythonString(byte[] data, int bytesPerLine, String leftPadding) { char[] hexChars = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e','f'}; leftPadding = (leftPadding == null ? "" : leftPadding); StringBuffer sb = new StringBuffer(); sb.append(leftPadding); sb.append("b\""); for (int i=0; i < data.length; i++) { sb.append("\\x"); // Note: Java promotes bytes to ints when doing any arithmetic on them (including bitwise operators), // so it doesn't matter whether we use >> or >>> as long as we null out extended sign bits with the bitwise AND first. sb.append(hexChars[(data[i] & 0xF0) >> 4]); sb.append(hexChars[(data[i] & 0x0F)]); if ((i+1) % bytesPerLine == 0 && i < data.length - 1) { // reached max. bytes for this line and there are remaining bytes left sb.append("\" + \\\n"+leftPadding+"b\""); } } sb.append('"'); return sb.toString(); } public static String toPythonString(byte[] data) { return toPythonString(data, 32, ""); } /** * Creates a Python source file at the given location which defines three variables 'public_key', 'private_key' * and 'certs', containing byte strings with the encoded form of the given keypair and certificates. * * The 'public_key' variable contains a byte string with the X.509 SubjectPublicKeyInfo representation of the public key. * The 'private_key' variable contains a byte string with the PKCS#8 representation of the private key. * The 'certs' variable contains a list with the X.509 DER representation of each given certificate in order. * * The main purpose of this method is to be able to avoid having to include these large byte strings into the test * case files, and instead be able to reference them through Python's module system. * * E.g. outputting expected/mytestcase.py using this method allows the Python test cases to reference * expected.mytestcase.private_key and expected.mytestcase.certs to access the expected values that should be found * after decoding that test's keystore. */ protected void writePythonDataFile(String filename, KeyPair keyPair, Certificate[] certs) throws Exception { String privateKeyPadding = StringUtils.repeat(" ", "private_key = ".length()); String publicKeyPadding = StringUtils.repeat(" ", "public_key = ".length()); String certsPadding = StringUtils.repeat(" ", "certs = [".length()); StringBuffer sb = new StringBuffer(); sb.append("public_key = "); sb.append(StringUtils.stripStart(toPythonString(keyPair.getPublic().getEncoded(), 32, publicKeyPadding), null)); sb.append("\n"); sb.append("private_key = "); sb.append(StringUtils.stripStart(toPythonString(keyPair.getPrivate().getEncoded(), 32, privateKeyPadding), null)); sb.append("\n"); sb.append("certs = ["); for (int i = 0; i < certs.length; i++) { String toAppend = toPythonString(certs[i].getEncoded(), 32, certsPadding); toAppend = (i == 0 ? StringUtils.stripStart(toAppend, null) : toAppend); sb.append(toAppend); sb.append(i < certs.length - 1 ? ",\n" : ""); } sb.append("]\n"); FileUtils.writeStringToFile(new File(filename), sb.toString()); } protected KeyPair generateKeyPair(String algorithm, int size) throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm); keyPairGenerator.initialize(size); return keyPairGenerator.generateKeyPair(); } protected void generateKeyStore(String storeType, String filepath, Map entriesByAlias, Map entryPasswordsByAlias, String storePassword) throws Exception { // avoid the need for some null checks in the remainder of the code below if (entriesByAlias == null) entriesByAlias = new HashMap(); if (entryPasswordsByAlias == null) entryPasswordsByAlias = new HashMap(); KeyStore ks = KeyStore.getInstance(storeType); char[] storePasswordChars = storePassword.toCharArray(); ks.load(null, storePasswordChars); for (String alias : entriesByAlias.keySet()) { KeyStore.Entry entry = entriesByAlias.get(alias); if (entry == null) continue; // trusted certificates don't need password protection (and will complain if you provide it) KeyStore.PasswordProtection passwordProtection = null; if (!(entry instanceof KeyStore.TrustedCertificateEntry)) { String entryPassword = entryPasswordsByAlias.get(alias); if (entryPassword == null) entryPassword = storePassword; // Note: there's no point specifying a protection algorithm/parameters to the KeyStore.PasswordProtection instance, // the default KeyStoreSpi.setEntry implementation uses it only to grab the password and nothing else. // Only the PKCS12 keystore SPI appears to honor custom protection algorithm/parameters. passwordProtection = new KeyStore.PasswordProtection(entryPassword.toCharArray()); } ks.setEntry(alias, entry, passwordProtection); } // use FileUtils.openOutputStream so we don't have to manually create any intermediate directories in filepath FileOutputStream fos = FileUtils.openOutputStream(new File(filepath)); ks.store(fos, storePasswordChars); fos.close(); } protected void generatePrivateKeyStore(String storeType, String filepath, PrivateKey privateKey, Certificate[] chain) throws Exception { generatePrivateKeyStore(storeType, filepath, privateKey, chain, "12345678", "12345678", "mykey"); } protected void generatePrivateKeyStore(String storeType, String filepath, PrivateKey privateKey, Certificate[] chain, String storePassword, String keyPassword, String alias) throws Exception { Map entriesByAlias = new HashMap(); Map passwordsByAlias = new HashMap(); if (privateKey != null) { entriesByAlias.put(alias, new KeyStore.PrivateKeyEntry(privateKey, chain)); passwordsByAlias.put(alias, keyPassword); } generateKeyStore(storeType, filepath, entriesByAlias, passwordsByAlias, storePassword); } protected void generateCertKeyStore(String storeType, String filepath, Certificate cert) throws Exception { generateCertsKeyStore(storeType, filepath, new Certificate[]{cert}, new String[]{"mycert"}, "12345678"); } protected void generateCertsKeyStore(String storeType, String filepath, Certificate[] certs, String[] aliases) throws Exception { generateCertsKeyStore(storeType, filepath, certs, aliases, "12345678"); } protected void generateCertsKeyStore(String storeType, String filepath, Certificate[] certs, String[] aliases, String storePassword) throws Exception { Map entriesByAlias = new HashMap(); if (certs != null && aliases != null) { for (int i=0; i < certs.length; i++) entriesByAlias.put(aliases[i], new KeyStore.TrustedCertificateEntry(certs[i])); } generateKeyStore(storeType, filepath, entriesByAlias, null, storePassword); } protected void generateSecretKeyStore(String filepath, SecretKey secretKey) throws Exception { generateSecretKeyStore(filepath, secretKey, "12345678", "12345678", "mykey"); } protected void generateSecretKeyStore(String filepath, SecretKey secretKey, String storePassword, String keyPassword, String alias) throws Exception { Map entriesByAlias = new HashMap(); Map passwordsByAlias = new HashMap(); if (secretKey != null) { entriesByAlias.put(alias, new KeyStore.SecretKeyEntry(secretKey)); passwordsByAlias.put(alias, keyPassword); } generateKeyStore("JCEKS", filepath, entriesByAlias, passwordsByAlias, storePassword); } protected MessageDigest getJceStoreDigest(String keystorePassword) { char[] password = keystorePassword.toCharArray(); MessageDigest md = null; try { md = MessageDigest.getInstance("SHA-1"); byte[] passwdBytes = new byte[password.length * 2]; for (int i = 0, j = 0; i < password.length; i++) { passwdBytes[j++] = (byte) (password[i] >> 8); passwdBytes[j++] = (byte) password[i]; } md.update(passwdBytes); for (int i = 0; i < passwdBytes.length; i++) passwdBytes[i] = 0; md.update("Mighty Aphrodite".getBytes("UTF8")); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } return md; } /** * Generates a JCEKS keystore with a single SecretKey entry whose serialized Java object is provided manually * (i.e. replacing the SealedObject that the JCE key store implementation would otherwise automatically generate). * * Main purpose is to test the behaviour of pyjks when given unepxected serialized java objects either at or inside the SealedObject level. */ protected void generateManualSealedObjectStore(String filename, String storePassword, String alias, Object sealedObject) throws Exception { MessageDigest md = getJceStoreDigest(storePassword); DataOutputStream dos = new DataOutputStream(new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(filename)), md)); dos.writeInt(0xCECECECE); // JCE magic bytes dos.writeInt(2); // keystore version dos.writeInt(1); // number of entries dos.writeInt(TAG_SECRET_KEY); dos.writeShort(alias.length()); dos.write(alias.getBytes("UTF-8")); dos.writeLong(System.currentTimeMillis()); ObjectOutputStream oos = new ObjectOutputStream(dos); oos.writeObject(sealedObject); oos.flush(); dos.write(md.digest()); dos.flush(); oos.close(); } protected void generateManualStore(String storeType, String filename, String[] aliases, int[] tags, byte[][] entriesData, String storePassword) throws Exception { MessageDigest md = getJceStoreDigest(storePassword); DataOutputStream dos = new DataOutputStream(new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(filename)), md)); dos.writeInt("JCEKS".equals(storeType) ? 0xCECECECE : 0xFEEDFEED); dos.writeInt(2); // keystore version dos.writeInt(aliases.length); // number of entries for (int i=0; i < aliases.length; i++) { String alias = aliases[i]; int tag = tags[i]; byte[] data = entriesData[i]; dos.writeInt(tag); dos.writeShort(alias.length()); dos.write(alias.getBytes("UTF-8")); dos.writeLong(System.currentTimeMillis()); dos.write(data); } dos.write(md.digest()); dos.flush(); dos.close(); } protected Certificate createSelfSignedCertificate(KeyPair keyPair, String dn) throws Exception { // Note: producing a self-signed certificate can be done through the JRE implementation as well, // but not in any portable or documented way (see sun.security.tools.keytool.CertAndKeyGen) Provider bcProv = Security.getProvider("BC"); if (bcProv == null) Security.addProvider(new BouncyCastleProvider()); try { PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); String sigAlgorithmName = ""; if (keyPair.getPrivate() instanceof RSAPrivateKey) { sigAlgorithmName = "SHA256WithRSAEncryption"; } else if (keyPair.getPrivate() instanceof DSAPrivateKey) { sigAlgorithmName = "SHA1withDSA"; } else { throw new RuntimeException("Don't know which signing algorithm to use for private keys of type " + keyPair.getPrivate().getAlgorithm()); } X500Name subject = new X500Name(dn); X500Name issuer = subject; BigInteger serial = BigInteger.valueOf(0); Date notBefore = new Date(System.currentTimeMillis()); Date notAfter = new Date(System.currentTimeMillis() + 2*365*86400000L); JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuer, serial, notBefore, notAfter, subject, publicKey); ContentSigner signer = new JcaContentSignerBuilder(sigAlgorithmName).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(privateKey); X509CertificateHolder holder = certBuilder.build(signer); X509Certificate cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(holder); return cert; } finally { if (bcProv == null) Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); } } protected byte[] encodeTrustedCert(Certificate cert) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); byte[] encoded = cert.getEncoded(); String type = cert.getType(); dos.writeShort(type.length()); dos.write(type.getBytes("UTF-8")); dos.writeInt(encoded.length); dos.write(encoded); dos.flush(); dos.close(); return bos.toByteArray(); } protected Cipher makePBECipher(String algorithm, int mode, String password, byte[] salt, int iterationCount) throws Exception { PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, iterationCount); SecretKey pbeKey = SecretKeyFactory.getInstance(algorithm).generateSecret(pbeKeySpec); Cipher cipher = Cipher.getInstance(algorithm); cipher.init(mode, pbeKey, pbeParamSpec); return cipher; } protected byte[] encryptPBEWithMD5AndTripleDES(byte[] input, String password, byte[] salt, int iterationCount) throws Exception { Cipher c = makePBECipher("PBEWithMD5AndTripleDES", Cipher.ENCRYPT_MODE, password, salt, iterationCount); return c.doFinal(input); } } pyjks-20.0.0/tests/keystores/000077500000000000000000000000001364672547600161475ustar00rootroot00000000000000pyjks-20.0.0/tests/keystores/bks/000077500000000000000000000000001364672547600167265ustar00rootroot00000000000000pyjks-20.0.0/tests/keystores/bks/christmas.bksv1000066400000000000000000000040261364672547600216750ustar00rootroot00000000000000IK,=H$B plain_keyTʏRAWDESL]*Csealed_private_keyTʏnX.509000  *H  010U RSA10240 160515234008Z 180515234008Z010U RSA102400  *H 0 ۿ&[S™S7, xnKV`8P/3xyDa3?,=BÝ|4Լ<)A"8+!E$KN(Z a{d50  *H  Y!+cm G9-0K`˷jqr _2T3e 5OFqx9qI ۺ{ȿ%M=Į 뗱3$!M$]^kwB@RJ)4+!Ѥc' 3h"_zE$\[j^E¾utm\Y /{`8Hz6̬V UC"QA i-#2!=ƹ|uY6/Q6 ؼMY4Aʜ?8ῄ >K1)`{~h)|^[0nŒU)z)~$M8?5\sInO,%RNOC?ֽ*P N_ YaJFc?^4;p=O&"vN#B,β '?[יYJJsṠt5I~ҏ#*oGR  S`f5.|bbtoCąBt}*h YH1ѧl7mg!&f 0(i\(%,5!4`UNEh Yp0Dt"Oz@ۚ0dY9M‚bZ<J-jRX%T[|NᩙI澭3=k|l1щvGOGrc 2`: tMoONde':>P]e)H ""+6Y򄑆(C6Efs}"%v–"esealed_public_keyTʏ!FAݳukmm%?fyB%dnTB TeM .k^69ԮY TeZJ[3j}Gy$]6ј=¯ǎq [MV|Pn\?kk:Xu plain_keyTʏRAWDESL]*Csealed_private_keyTʏX.509000  *H  010U RSA10240 160515234008Z 180515234008Z010U RSA102400  *H 0 ۿ&[S™S7, xnKV`8P/3xyDa3?,=BÝ|4Լ<)A"8+!E$KN(Z a{d50  *H  Y!+cm G9-0K`˷jqr _2T3e 5OFqx9qI ۺ{ȿ%M=Į 뗱3$!M$]^kwB@RJ)4+!ѤuT6 ~=MZuT D\U-1J A~AnVxBT/k@'h,V[ءiĦA95DlY[?E@͓WЩ;0@7bkϨt=qU xek79B (;qz |@aKC݇߼_ztMf+rˎ`Wiw<$Vo\<ju1L;X<./Nps h*F 3z]Œ%lN92j:nRB쉞.t2LS)!=&!::E3+ G%Ygv®DtC#7kqtWzs|.XTnq7F-S{aC!P#ve ڻmx0WY^df5A84W4C9qw+)lW*N2Y&mML}I\6!UC+5wTu|̀+f/'}UzwS| N FZ,k }yԃm;:I=Dݨ똕2rF )&"-sff.isealed_public_keyTʏr)Kn4Q4cֻV9wqg U5 ݏ} h_(%e hq DaMKP}g4)+IHv?+!R^ŀzBr!/ ;Οɣ'P35cR+j15/ÍJ =;<`ۍ1.0ivvŐ*%certTʏX.509000  *H  010U RSA10240 160515234008Z 180515234008Z010U RSA102400  *H 0 ۿ&[S™S7, xnKV`8P/3xyDa3?,=BÝ|4Լ<)A"8+!E$KN(Z a{d50  *H  Y!+cm G9-0K`˷jqr _2T3e 5OFqx9qI ۺ{ȿ%M=Į 뗱3$!M$]^kwB@RJ)4+!Ѥ stored_valueTʏ  sealed_secret_keyTʏ<lv}kء9O_ U%e{%do>K- *N;uZ6AK[7Qpyjks-20.0.0/tests/keystores/bks/custom_entry_passwords.bksv1000066400000000000000000000046631364672547600245470ustar00rootroot00000000000000t,бF 면d8sealed_private_keyTBX.50900!0  *H  0"1 0U custom_entry_passwords10 160518225306Z 180518225306Z0"1 0U custom_entry_passwords100  *H 0zrCPB-M,IN7SzӔp"KJFhyj 1,QSX_A$ 7«EhM r=NY-ѯw0(45fER_u\wg0  *H  ,CB* K^!,5ȹXQ4[ kM_B`n PUEjt=. %"v= ; B]m 9t6V(5礠`NW;׸u bXn}:V3*B9@腥YhH~ï!( HXqWO0]U<%-UG8,a%]0ī!uW[Sѽ[oRf?9>" !ބf1*p>/Æ;ܠZ4QlR%' t`?bOH@'g m '\W?˭ʬBʧ+V6amtXSC[Ƶfd$b$s04 , R(y喠Krg2)/Q2 Hj)!_I4#-*-[S蹿gݸړ|-5㵻tYŤK1 4gϋ>4a݌mW[<]9NSM #mL4ËaXZ HF!p Asealed_public_keyTX.50900!0  *H  0"1 0U custom_entry_passwords10 160518225306Z 180518225306Z0"1 0U custom_entry_passwords100  *H 0zrCPB-M,IN7SzӔp"KJFhyj 1,QSX_A$ 7«EhM r=NY-ѯw0(45fER_u\wg0  *H  ,CB* K^!,5ȹXQ4[ kM_B`n PUEjt=. %"v= ; B]m 9t6V(5礠`NW;׸u 0ð;j/h((Xνɘc/z!E*u'GXh ҵ!UͷQA5z7H*Lh+yМǴx'aLv}YiD C10w%,xV7,!g@M %@Vw Āၤ{/x-*+6SѨqsealed_secret_keyTX.50900!0  *H  0"1 0U custom_entry_passwords10 160518225306Z 180518225306Z0"1 0U custom_entry_passwords100  *H 0zrCPB-M,IN7SzӔp"KJFhyj 1,QSX_A$ 7«EhM r=NY-ѯw0(45fER_u\wg0  *H  ,CB* K^!,5ȹXQ4[ kM_B`n PUEjt=. %"v= ; B]m 9t6V(5礠`NW;׸u <QӭZyk?l5P'<yp. jqVE]Tb`)FOǻc%pyjks-20.0.0/tests/keystores/bks/custom_entry_passwords.bksv2000066400000000000000000000046631364672547600245500ustar00rootroot00000000000000W߄9A{P`o}sealed_private_keyTX.50900!0  *H  0"1 0U custom_entry_passwords10 160518225306Z 180518225306Z0"1 0U custom_entry_passwords100  *H 0zrCPB-M,IN7SzӔp"KJFhyj 1,QSX_A$ 7«EhM r=NY-ѯw0(45fER_u\wg0  *H  ,CB* K^!,5ȹXQ4[ kM_B`n PUEjt=. %"v= ; B]m 9t6V(5礠`NW;׸u \w2sV `\xa<פqpR5&4h䠆FW[ZYǫE,=,m͛mg?_*#]kzq,՚)GIc\NfH@@u Xv'e;9tݐži_pOjN^tu9׫w^DfmƴߠFwS FHdT0]ԩ rวA *S AΠO#܎aVz LY^r(*>P.fCͭ).1U؃ۺ,  FZ8H:Vg7 {gF?,&UhQʨD xނ_oT; lo/tsA9{HZTͽ)}DU^moO0H]<6RiuO0ȼx-{vf Xw6,6: -CmۭK[I0 E/[}>uXc$|7ϝ[}Ti)xoftb7F5 Mr xDI )RDScHܔE^V>u,Kb'k[8z$Du Pc*0Otysealed_secret_keyTX.50900!0  *H  0"1 0U custom_entry_passwords10 160518225306Z 180518225306Z0"1 0U custom_entry_passwords100  *H 0zrCPB-M,IN7SzӔp"KJFhyj 1,QSX_A$ 7«EhM r=NY-ѯw0(45fER_u\wg0  *H  ,CB* K^!,5ȹXQ4[ kM_B`n PUEjt=. %"v= ; B]m 9t6V(5礠`NW;׸u <d̾h*׮8/&xKxܐU~Ztj1nnz*84u T4׉Us̴pyjks-20.0.0/tests/keystores/bks/empty.bksv1000066400000000000000000000000651364672547600210350ustar00rootroot00000000000000αsȥb{^ڦb)prx;.aM)pyjks-20.0.0/tests/keystores/bks/empty.bksv2000066400000000000000000000000651364672547600210360ustar00rootroot00000000000000̻h|2[CỶxU͝[},\B,md>[pyjks-20.0.0/tests/keystores/jceks/000077500000000000000000000000001364672547600172465ustar00rootroot00000000000000pyjks-20.0.0/tests/keystores/jceks/3certs.jceks000066400000000000000000000042451364672547600214770ustar00rootroot00000000000000cert3TP)X.509000  *H  010U RSA20481 0U 30 160515185804Z 180515185804Z010U RSA20481 0U 30"0  *H 0 vtH~gÐQͳkANN 0WG*g&l(pҲDzoVRC&L>QkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  d.w˸lDvѩ֟QQ;| Je*% ,v ;97##D|,giӬ[C-HLϡSPJwNx`-q8He_FU$Yh8Fi-*m%<n(FT~I&*byM OgC0p\gL\F{GUAӅ|06z}ijR[TH;,Kyrcert2TP)X.509000  *H  010U RSA20481 0U 20 160515185804Z 180515185804Z010U RSA20481 0U 20"0  *H 0 vtH~gÐQͳkANN 0WG*g&l(pҲDzoVRC&L>QkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  kx/y+j6?M-[\q=&x[ؗLι)0\h"ԀQkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  ʏ+J0LE0r^<rDeIx:b]9l@Piz>h~3n@_nBBE'o:Ia".÷w5cGnJZ ~*4r6 L=]tbԷv r#,;гgb*#ѕ׬QaDZR&vH.ӍUsNuڪi|B뜔t:ښ4+tCa/Q˹k!$hJ1/.98HIOqY$47pyjks-20.0.0/tests/keystores/jceks/AES128.jceks000066400000000000000000000007621364672547600211370ustar00rootroot00000000000000mykeyT8T sr3com.sun.crypto.provider.SealedObjectForKeyProtectorWY0Sxrjavax.crypto.SealedObject>6=÷Tp[ encodedParamst[B[encryptedContentq~L paramsAlgtLjava/lang/String;LsealAlgq~xpur[BTxp0 [dIuq~,v5Z>U;$7D%E?"[ڏz6ֵR5!HӅq>4Ec Sk[; ( E+i U/j@_b=19F&|;. BVtonntb IA !Y]O\tPBEWithMD5AndTripleDEStPBEWithMD5AndTripleDES"uZ տHpyjks-20.0.0/tests/keystores/jceks/AES256.jceks000066400000000000000000000010021364672547600211250ustar00rootroot00000000000000mykeyT8Tsr3com.sun.crypto.provider.SealedObjectForKeyProtectorWY0Sxrjavax.crypto.SealedObject>6=÷Tp[ encodedParamst[B[encryptedContentq~L paramsAlgtLjava/lang/String;LsealAlgq~xpur[BTxp0 ?"#uq~%hOy6}2C`y)^1WϸxQZlo;0g#xU5|ݛC㤹uE[0$h2SBZ:#(OD;͍瑲)u1QQN8-K]BM*j򭇊tR0r/%ǫqA\MiuKtPBEWithMD5AndTripleDEStPBEWithMD5AndTripleDES.D [dD3.ʷl-pyjks-20.0.0/tests/keystores/jceks/DES.jceks000066400000000000000000000011521364672547600207010ustar00rootroot00000000000000mykeyT8Ssr3com.sun.crypto.provider.SealedObjectForKeyProtectorWY0Sxrjavax.crypto.SealedObject>6=÷Tp[ encodedParamst[B[encryptedContentq~L paramsAlgtLjava/lang/String;LsealAlgq~xpur[BTxp0 K(% @Zuq~PlL@IW :wE\w`n<w]d kg*o zŨݕBP5xTݕE~}ɱ@_ԣ;$U=3ɹ_x)v.IJEaOmQYF/-<\VuY:,: ~K\LSWY8N0i[ţFy ) ދVe"S~J*Uϫ88Ä9Y0!øgo釜Hm5e^1Y6tPBEWithMD5AndTripleDEStPBEWithMD5AndTripleDES]$X[$p ɶupyjks-20.0.0/tests/keystores/jceks/DESede.jceks000066400000000000000000000012021364672547600213530ustar00rootroot00000000000000mykeyT8SӬsr3com.sun.crypto.provider.SealedObjectForKeyProtectorWY0Sxrjavax.crypto.SealedObject>6=÷Tp[ encodedParamst[B[encryptedContentq~L paramsAlgtLjava/lang/String;LsealAlgq~xpur[BTxp0  uq~ L aĭ*Ͻ'O!GvS*"&gFxRX.Z1Gg>y姂勣[JsbSND)>%ڙ,rtRF+F/;Jq&#/uWe~zIHl\{)LVqFҴ>$dY4|̋2x,0n -=qF%͠q"S1r}7Y2IC o2C3Hߘ,1Jng}'PԷVeOhxNBmBk4tPBEWithMD5AndTripleDEStPBEWithMD5AndTripleDESr3˸eUtN󫙧jpyjks-20.0.0/tests/keystores/jceks/DSA2048.jceks000066400000000000000000000033101364672547600212110ustar00rootroot00000000000000mykeyT00 +*0 h٫hZZqw)_D> YlWȆtG7zHALx`r?M}J$XXLjcf7ʛ!3y9slbxU;EDž-7CRK#gG$VwǼ3u Qt 6KDhA7.8ĸ;ڐhb0W?/HJX'Pt* ̑~қ.AIv;LLDcx/Hqnw&j8ڤaPk&Wcˈuh&5y #X^:={iK#㰲.8%cɣG fh{@ש.5n.cN=2X!\]6CQ39 ®SG[yuIi\6(/' =bE4 ilFXK 7 W#=T)dDw4.u]=)/30׹IY/h r9=kFS3E;2r%nN?;8P)}^CKf>#Xԇ!#n8e"BHLv99`gxgwDž23i]\X HPpNuW 4:4xT-$.OKАJ71D TL[,6>ԉNh2B\xxƍ': ,Dh9te+Lh&>/KUK/|餖ӥ]S[E?=9%$w1)mGH~_޷T1k"2Sݯ+ 02$'ڋK)"ٺ=Zh*f(/Hpyjks-20.0.0/tests/keystores/jceks/PBKDF2WithHmacSHA1.jceks000066400000000000000000000012221364672547600232360ustar00rootroot00000000000000mykeyT?'tsr3com.sun.crypto.provider.SealedObjectForKeyProtectorWY0Sxrjavax.crypto.SealedObject>6=÷Tp[ encodedParamst[B[encryptedContentq~L paramsAlgtLjava/lang/String;LsealAlgq~xpur[BTxp0 {sNuq~0hp%ܚY ;HDҹ78GB}zBagnVDkD֘mKf =͌@?} ?d}'#T =YsyAadKeY(M@^=/J!f(¸t,9ӌZ9q1x0'ԌItPBEWithMD5AndTripleDEStPBEWithMD5AndTripleDES4"ZD=p|w~epyjks-20.0.0/tests/keystores/jceks/RSA1024.jceks000066400000000000000000000022061364672547600212230ustar00rootroot00000000000000mykeyT00 +*0 /' oUhkJ-'c-̼ X:sP:Pcv=Qɨ NkOC@,J}P\2~1!cGs=J)e2,ѶjS}YNQ+эᬻ~Qpb>r)^QiӢ&z-פ= 8gFI{YT~;>]I Սlm:ӬѼ@zIXl{GI}+ո:z;(ɾbf󃫛Ò7'irC#IlF}"#8ťIU] 5vT>S~1cOy+@c>]-̷ "lS8_[D'@@8XRiw2aM zz O ٛԸo}I`]m촆%/Ak"ev˧>WZrq#cB~۪ 6 H8JZT6U]9"n]OZ痝͈X.509000  *H  010U RSA10240 160515185101Z 180515185101Z010U RSA102400  *H 0x 8=Rf(HZhyYIk <)r^'*Oc-WY={sdǮNۑ`ywrp;C U9FE48ۇ>3u0G0  *H  wOK9'^A˗F?wƧeߓ j')?|,=ʱű=Ktܬڣ.W$^- ` z-f3a%`dfBa l?$|Ul8,pyjks-20.0.0/tests/keystores/jceks/RSA2048_3certs.jceks000066400000000000000000000065631364672547600225270ustar00rootroot00000000000000mykeyTP&00 +*0 gM ^ ~ˊ,C|RQ8)oxњ[qaLMsn?G1h͈1F@k|/SxGڛ }2'֥4b:bt&qUWǡPup|;9Z%gm^%yI~LFHEP#by( !)"|_ 9_ CV;ݓK^|& GZ\)~Ua&k _$S$#d['*ꔦ tZnvx" !e|A_j2/Gq*{BK yYʼnNSk`LSq #SAS`,֏0˩30 b"^J-Ȧ%Jx (ˀp])NgMӈ*\ǐdd?^gQz\g2qQrwJn I;+"Zr wdEXs-p Wr5&&-Be$|B|42!X)=vD1 (wt9 $algTTx`>n"?o|}z#+b2t螵 (PO$Sih Ɓ2dNAr1lQkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  ʏ+J0LE0r^<rDeIx:b]9l@Piz>h~3n@_nBBE'o:Ia".÷w5cGnJZ ~*4r6 L=]tbԷv r#,;гgb*#ѕ׬QaDZR&vH.ӍUsNuڪi|B뜔t:ښ4+tCa/Q˹k!$hJ1/.X.509000  *H  010U RSA20481 0U 20 160515185804Z 180515185804Z010U RSA20481 0U 20"0  *H 0 vtH~gÐQͳkANN 0WG*g&l(pҲDzoVRC&L>QkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  kx/y+j6?M-[\q=&x[ؗLι)0\h"ԀQkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  d.w˸lDvѩ֟QQ;| Je*% ,v ;97##D|,giӬ[C-HLϡSPJwNx`-q8He_FU$Yh8Fi-*m%<n(FT~I&*byM OgC0p\gL\F{GUAӅ|06z}ijR[TH;,Kyry.霝8`R1+Bpyjks-20.0.0/tests/keystores/jceks/custom_entry_passwords.jceks000066400000000000000000000062421364672547600251330ustar00rootroot00000000000000secretTsr3com.sun.crypto.provider.SealedObjectForKeyProtectorWY0Sxrjavax.crypto.SealedObject>6=÷Tp[ encodedParamst[B[encryptedContentq~L paramsAlgtLjava/lang/String;LsealAlgq~xpur[BTxp0 t6zfBuq~Uɛ_ҡR~D.WA>ʽ;ձ^+;X]?C2S N$z[-g.IZ̬oqW+2$C3T.VtMՍTS*}ӗXxt ,ǣ0[4utPBEWithMD5AndTripleDEStPBEWithMD5AndTripleDESprivateT000 +*0 W*!iJ)1>œ,>+f mEeh0 Zke%M;H¯#Gdu.1~e[)Bh7H=wAysO xqd@ֻh&([%Y;wdQ@dEKXPQ0uq@-Oj2=2vDP?՜UZѳ4ֻ{g(I+PLfC 6VHh ڪ)M'E7MBE0'vus|mM/1ttMiCL֣<r>Oڜ2 \/F}"SɧH/g@b[Xu2 AӞċ6uH{SQXY҂ K %g$D:kÔ7O{ߊ囁C2n'OCRI~ފ; l[FG ;󿪔͢xI)xhaU*15혛l_^Z/w_(H3Ag^N~xlƏ59jB~)À*R"9hn߶"SPBxSw'y(%#g+x eM̲%:ݰ;NIfq@:s>^3Nd\Q_S:Ll[ z,hiMIO&m* ţ)Һn$$"> Lޠiuc"xZZ$ȁZ_ErCY#d,:-p̍~(ܦ( P=8lfh_-k:VEVc7Zj3#^ц Y2H)82r751y @3p41GBmhP (ROW.I] UZ&]#< }D)l :ƈOh= \ M~k/ < ^[Uu3]G6Q`ߵA8"AK'@; .Ƞ8] 9_AuƚA, Ydf{2BW"{K)eiVIW< _‹i&tlS4D EH_.N1sņ>`)-EHnF.U"mB][A\k Qf'Ǿ5? @gu~)D%]/\9?Wޗ 6z'捗}p`~{.0\>u(~6X.509000  *H  0!10U custom_entry_passwords0 160515185101Z 180515185101Z0!10U custom_entry_passwords0"0  *H 0 ܩ9$wRgz/-+n _,e A 6;5%͑L ע-j˷%Ҳ"ɹHj)Fa:yHRݚz-4wحPOP(C >BXyPA猔LS{F.K !rUfiC,Rݕ,^U6;R9J%`O<Y0h0x\_P y3Y@˿͍ (Gf̄0  *H  c+m%`g2\Kb{t `bVsoiAlڠEF(cOe:r@g9:$O9 ,NU duA"n44x9i`!K|ƞg6 ϳ(6!RKSԫ?$xU;'HTЮWMP-< (vޑ>S2QnE-޺&:t/4Y/ĭ%3"rcertTX.509000  *H  0!10U custom_entry_passwords0 160515185101Z 180515185101Z0!10U custom_entry_passwords0"0  *H 0 ܩ9$wRgz/-+n _,e A 6;5%͑L ע-j˷%Ҳ"ɹHj)Fa:yHRݚz-4wحPOP(C >BXyPA猔LS{F.K !rUfiC,Rݕ,^U6;R9J%`O<Y0h0x\_P y3Y@˿͍ (Gf̄0  *H  c+m%`g2\Kb{t `bVsoiAlڠEF(cOe:r@g9:$O9 ,NU duA"n44x9i`!K|ƞg6 ϳ(6!RKSԫ?$xU;'HTЮWMP-< (vޑ>S2QnE-޺&:t/4Y/ĭ%3"rz0wyܟo<[Qpyjks-20.0.0/tests/keystores/jceks/duplicate_aliases.jceks000066400000000000000000000017621364672547600237500ustar00rootroot00000000000000my_aliasT^X.50900-0  *H  0(10U duplicate_aliases1 0U 10 160428214908Z 180428214908Z0(10U duplicate_aliases1 0U 100  *H 0R$6c3ت@@t^I)\=KIr{ErVF *RÀΘ9D%ܯVr m,[ yt9^#JNn uNwg%U 0  *H  y-Z,t|Xf0xc#0כl pI3l^==T|m=0-VJ<na"DA IxZةX׼&$j *[4bi hm6Umy_aliasT^X.50900-0  *H  0(10U duplicate_aliases1 0U 20 160428214908Z 180428214908Z0(10U duplicate_aliases1 0U 200  *H 0R$6c3ت@@t^I)\=KIr{ErVF *RÀΘ9D%ܯVr m,[ yt9^#JNn uNwg%U 0  *H  2C;ӿ̡>̝D3d?Π1e-X1fvftT~!9p\~9jnݕ #ԀMYIU #wv%KB4! 6*yr@zUP pyjks-20.0.0/tests/keystores/jceks/empty.jceks000066400000000000000000000000401364672547600214170ustar00rootroot00000000000000D;˞s Y$|pyjks-20.0.0/tests/keystores/jceks/unknown_sealed_object_sealAlg.jceks000066400000000000000000000005021364672547600262560ustar00rootroot00000000000000mykeyTEȗ3srjavax.crypto.SealedObject>6=÷Tp[ encodedParamst[B[encryptedContentq~L paramsAlgtLjava/lang/String;LsealAlgq~xpur[BTxp0 SO_SALTY*uq~0jYeEHS]\(ƃ❁k)lވ۟{&tPBEWithMD5AndTripleDEStnonsense9:\-N.Xpyjks-20.0.0/tests/keystores/jceks/unknown_type_inside_sealed_object.jceks000066400000000000000000000005201364672547600272220ustar00rootroot00000000000000mykeyTEHRqsrjavax.crypto.SealedObject>6=÷Tp[ encodedParamst[B[encryptedContentq~L paramsAlgtLjava/lang/String;LsealAlgq~xpur[BTxp0 SO_SALTY*uq~0jYeEHS]\(ƃ❁k)lވ۟{&tPBEWithMD5AndTripleDEStPBEWithMD5AndTripleDES?0JY $Cq`S 2 pyjks-20.0.0/tests/keystores/jceks/unknown_type_of_sealed_object.jceks000066400000000000000000000001351364672547600263550ustar00rootroot00000000000000mykeyTEHdpsrorg.pyjks.DummyObjecte06,xpf(H3$pyjks-20.0.0/tests/keystores/jks/000077500000000000000000000000001364672547600167365ustar00rootroot00000000000000pyjks-20.0.0/tests/keystores/jks/3certs.jks000066400000000000000000000042451364672547600206570ustar00rootroot00000000000000cert3TP(X.509000  *H  010U RSA20481 0U 30 160515185804Z 180515185804Z010U RSA20481 0U 30"0  *H 0 vtH~gÐQͳkANN 0WG*g&l(pҲDzoVRC&L>QkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  d.w˸lDvѩ֟QQ;| Je*% ,v ;97##D|,giӬ[C-HLϡSPJwNx`-q8He_FU$Yh8Fi-*m%<n(FT~I&*byM OgC0p\gL\F{GUAӅ|06z}ijR[TH;,Kyrcert2TP(X.509000  *H  010U RSA20481 0U 20 160515185804Z 180515185804Z010U RSA20481 0U 20"0  *H 0 vtH~gÐQͳkANN 0WG*g&l(pҲDzoVRC&L>QkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  kx/y+j6?M-[\q=&x[ؗLι)0\h"ԀQkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  ʏ+J0LE0r^<rDeIx:b]9l@Piz>h~3n@_nBBE'o:Ia".÷w5cGnJZ ~*4r6 L=]tbԷv r#,;гgb*#ѕ׬QaDZR&vH.ӍUsNuڪi|B뜔t:ښ4+tCa/Q˹k!$hJ1/. $/ֶw.GN!3opyjks-20.0.0/tests/keystores/jks/DSA2048.jks000066400000000000000000000033341364672547600203770ustar00rootroot00000000000000mykeyT00 +*>fի2'Ws\}zUWsV0d􂅭Yda`.YH 7U2&0IkiD+y5` %FVn\oO"3S(r&љ G(zo}}{.ǀ< cl $uv\_2Lc0{r GPw!ȨB[ld&1Tž7vᏐN駡|7Ʊ2hOZW!ŀaHX.509000 *H8010U DSA20480 160515185102Z 180515185102Z010U DSA20480B05*H80(y5ٹ鿫zIQ.Ş;7Ė>6CQ39 ®SG[yuIi\6(/' =bE4 ilFXK 7 W#=T)dDw4.u]=)/30׹IY/h r9=kFS3E;2r%nN?;8P)}^CKf>#Xԇ!#n8e"BHLv99`gxgwDž23i]\X HPpNuW 4:4xT-$.OKАJ71D TL[,6>ԉNh2B\xxƍ': ,Dh9te+Lh&>/KUK/|餖ӥ]S[E?=9%$w1)mGH~_޷T1k"2Sݯ+ 02$'ڋK)"ٺ.Hb9GXkl- b^ 2R!1Ik=}{<Ɠ1yB丆AYec񕲁 K{OjWܘ 6(ʈZ-&@ a8 ׬4ꔙ^J2䙿{b9cOQ s}&;鮭ymV#gu* x&c}TTA 5#ӠP곇rk(RhYw^ͱ{n#`Ot-$'-q,7<)@@h^挏p4X!>Hh[ A%:ƣ> L`4(՝/lcDn1UX.509000  *H  010U RSA10240 160515185101Z 180515185101Z010U RSA102400  *H 0x 8=Rf(HZhyYIk <)r^'*Oc-WY={sdǮNۑ`ywrp;C U9FE48ۇ>3u0G0  *H  wOK9'^A˗F?wƧeߓ j')?|,=ʱű=Ktܬڣ.W$^- ` z-f3a%`dfBa;̘~ru{2eR<pyjks-20.0.0/tests/keystores/jks/RSA2048_3certs.jks000066400000000000000000000066101364672547600217000ustar00rootroot00000000000000mykeyTP$00 +*msFT J+NVY3ӰUKYN":зh4`j`valy) z͊!gt HP, bE.>Pw%=Uix+!U^g,x>fr!;ӯԃ6\绸\]n$o*7|^6^ Lײm zRk k H֗:um Y2HA @^LCĊ;bɦ6!_Ԛ;K2.ɽZcoɒf;v0[١~\D@vΊm&YK`X2Snx Mu|ICjABzohZсqft劜bs^/m0D=½S ̑?D9i_> EK0x}e˨Jt{?GM+4 RZ?yդngr39I{NOYޑc8׮q8 kX3-Xf/=Uj _X^wZŒ }uB΂H̸@!xhg19E_4O-ܬ` rZn_ ԯ:k0@liDDV]. ^$F(I`ķFJ.:.u^l w..9"lRdN1J]e(-\iNBpX (.">{"Y3fpw! $Q-[lT*mN>IyX-%=}q:6} I4=c<=6qOP32_r? ה0WEu0mQ U4aUW6'2c %pgX.509000  *H  010U RSA20481 0U 10 160515185804Z 180515185804Z010U RSA20481 0U 10"0  *H 0 vtH~gÐQͳkANN 0WG*g&l(pҲDzoVRC&L>QkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  ʏ+J0LE0r^<rDeIx:b]9l@Piz>h~3n@_nBBE'o:Ia".÷w5cGnJZ ~*4r6 L=]tbԷv r#,;гgb*#ѕ׬QaDZR&vH.ӍUsNuڪi|B뜔t:ښ4+tCa/Q˹k!$hJ1/.X.509000  *H  010U RSA20481 0U 20 160515185804Z 180515185804Z010U RSA20481 0U 20"0  *H 0 vtH~gÐQͳkANN 0WG*g&l(pҲDzoVRC&L>QkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  kx/y+j6?M-[\q=&x[ؗLι)0\h"ԀQkӚ}&Zn/fa1b# j i`Ϻl03,It#!RQON>ѨΒ4lMo}ePX!P8n}ɵkڔ^v ~y `̠;BԔм~k `G!\5~ȆJV֒=#<:3HU0  *H  d.w˸lDvѩ֟QQ;| Je*% ,v ;97##D|,giӬ[C-HLϡSPJwNx`-q8He_FU$Yh8Fi-*m%<n(FT~I&*byM OgC0p\gL\F{GUAӅ|06z}ijR[TH;,KyrOˇV'':pyjks-20.0.0/tests/keystores/jks/custom_entry_passwords.jks000066400000000000000000000053441364672547600243150ustar00rootroot00000000000000privateT00 +*.'>\4UkYS32<#05-kx)'+Ĝ6ޣGb [&?gDD'(%1[ Su򪘙X:)W$8>ޛA},B%%PsO EvVW4XdS?ȪaH\3&@&v4=Aj>#P#-U`2p "/l*-@^Zpht!r:_ %.Ɍ+,̄2\wO3_~:$~Tc m`~BYUؿ00ȃNI Ifr~ܷT2/ºdx\zvhk,\%Tu6T["YH-@ yJnj5qԓ ,ڂTVõ 걽cd@G\o?e=[*Rq={;CIb@H7D _Jz\k袡g<3T_|dQ X]j3򲀤-&<+3'ȀM.p%[^Yȅ,jK:_+}oQq^3n_rEym< ,eimO\v6WI(|aĞGi2 X0*|4tdQjl2Xԥ{YhA)C1k( KH!fUĦKy][pBXyPA猔LS{F.K !rUfiC,Rݕ,^U6;R9J%`O<Y0h0x\_P y3Y@˿͍ (Gf̄0  *H  c+m%`g2\Kb{t `bVsoiAlڠEF(cOe:r@g9:$O9 ,NU duA"n44x9i`!K|ƞg6 ϳ(6!RKSԫ?$xU;'HTЮWMP-< (vޑ>S2QnE-޺&:t/4Y/ĭ%3"rcertTX.509000  *H  0!10U custom_entry_passwords0 160515185101Z 180515185101Z0!10U custom_entry_passwords0"0  *H 0 ܩ9$wRgz/-+n _,e A 6;5%͑L ע-j˷%Ҳ"ɹHj)Fa:yHRݚz-4wحPOP(C >BXyPA猔LS{F.K !rUfiC,Rݕ,^U6;R9J%`O<Y0h0x\_P y3Y@˿͍ (Gf̄0  *H  c+m%`g2\Kb{t `bVsoiAlڠEF(cOe:r@g9:$O9 ,NU duA"n44x9i`!K|ƞg6 ϳ(6!RKSԫ?$xU;'HTЮWMP-< (vޑ>S2QnE-޺&:t/4Y/ĭ%3"rI] ]l PRpyjks-20.0.0/tests/keystores/jks/duplicate_aliases.jks000066400000000000000000000017621364672547600231300ustar00rootroot00000000000000my_aliasT^X.50900-0  *H  0(10U duplicate_aliases1 0U 10 160428214908Z 180428214908Z0(10U duplicate_aliases1 0U 100  *H 0R$6c3ت@@t^I)\=KIr{ErVF *RÀΘ9D%ܯVr m,[ yt9^#JNn uNwg%U 0  *H  y-Z,t|Xf0xc#0כl pI3l^==T|m=0-VJ<na"DA IxZةX׼&$j *[4bi hm6Umy_aliasT^X.50900-0  *H  0(10U duplicate_aliases1 0U 20 160428214908Z 180428214908Z0(10U duplicate_aliases1 0U 200  *H 0R$6c3ت@@t^I)\=KIr{ErVF *RÀΘ9D%ܯVr m,[ yt9^#JNn uNwg%U 0  *H  2C;ӿ̡>̝D3d?Π1e-X1fvftT~!9p\~9jnݕ #ԀMYIU #wv%KB4! 6*yql땅^Ai gLpyjks-20.0.0/tests/keystores/jks/empty.jks000066400000000000000000000000401364672547600205770ustar00rootroot00000000000000W'bpYhB:ipyjks-20.0.0/tests/keystores/jks/non_ascii_password.jks000066400000000000000000000037771364672547600233510ustar00rootroot00000000000000mykeyTQ00 +*qgF2y<3kQ(["o]; ` nڗoOl5U(l{뚟/-ua/uwr[b]fƨ3bZ첐4N<2( J}#yV` ^?!6ʾpZ1kNLo]eާE/fޠ1k@]_y~|SDW/\+C-OϤ㳗WuZ~ i2!')U=7;'Q:pIӛqp1Oy2,x琅jM#ئv髻*:) I&͂~4ABQl@Fn'ٝ0KZ8BD,a*ݍv9,|eu\S5QqLUU: 1rOB%q!jn9)_E4kxR?t R4ܖTkRxD(Wq ԏ1/|#wVKخ&vb >7m(OSGm12댎,(<#MWy,Us̢lm}N@e@~C3ԃv(uR<)֟x>ERMJNcHMV!NOTW#IMj-x|{jM`?N0^Dd(DDe3>X}YbRw'Ώar7i2V2\gE."Vh#·UT.aVT<޾A\ `ZsЃťpPpJ'Jneyh2aOsE*=udtT)_O)l(%alq-VΤ[7Pwd@V|4d6:#Qg:Ľ?=|;$DQwe ڢnL|;7^X%7ӳf."-3̕KeAeFx2׭MkZ=&R@tx> BO7Yp3&$]0  *H  :r!(g I& :qE$𩕁ҏ?ϳrR#iʐ/1 I1`IN3*Ġx VAλ0Jۃ*{+$J!J6;f)~4i?3<[k'#*.~Z\>т)d$!rݛrL*aQr4:&;2$9S.ʍ\I ^1*Q03 ֕Dj~r|vq)VgFG?]1I&Zpyjks-20.0.0/tests/keystores/jks/truststore.jks000066400000000000000000000024041364672547600217050ustar00rootroot00000000000000cordaintermediatecaW6X.509N0J0갴.0 *H=0t10U Corda Node Root CA1 0 U R310 U corda10 U London1 0 U UK1 0U10  *H  0 160909000000Z 260907000000Z0|1#0!U Corda Node Intermediate CA1 0 U R310 U corda10 U London1 0 U UK1 0U10  *H  0V0*H=+ B~,d Νh,f7f.6曒oӋmJ6 \}#6 .>M>vz0)g0e0U{j%N}]+n70U00 U0#U%0++U%0 *H=H0E!5|Xm;x~2b%UƏa#5 nS#ZUL"e((WoMDb> cordarootcaW6X.509F0B0:qDK 0 *H=0t10U Corda Node Root CA1 0 U R310 U corda10 U London1 0 U UK1 0U10  *H  0 160909000000Z 260907000000Z0t10U Corda Node Root CA1 0 U R310 U corda10 U London1 0 U UK1 0U10  *H  0V0*H=+ B= E9Dx1s'JS5.0cUyR7mZa]ُ#Di䩴;zg0e0UhC9 \DB0U00 U0#U%0++U%0 *H=H0E!VO'yŐlA+҃I'He. a∋؋.Yj즿*ڗvlKxCH5ǒh8pyjks-20.0.0/tests/keystores/uber/000077500000000000000000000000001364672547600171045ustar00rootroot00000000000000pyjks-20.0.0/tests/keystores/uber/christmas.uber000066400000000000000000000040401364672547600217560ustar00rootroot00000000000000ǰ8>홚f-uasx[F0,̬^SǺd}R}"KsV*ΗP"GwC2[3Y*^Hk&!VʂW?A'l!U٣e;ի@lKsFإCxi&~xbaش:+ *9Z\Ŷۍ0 A< &<5d-io",c a@T2rZE ¾n~'vxDuRX:yf~9ҏR0}\G>\ te'AJe3jC s --ߵacJ8d&bNC~LM|z c ŠKT6Y쁝iT2s`+- srEdՙhG.N{2>ؖP6ٔqSs 6~[gѕQ js<@2$XwK,(Sx/!AuA뤘Zюn?:GJ@yw;8k HOav(rU_46{6NEpѴn=#mȩZ zV_wsC!GH&/:3P}ōߤY{蠒[mPgR_?HXj`/ xB?] @Sgh5PB&|wd#B)vL*Ѕhr&֧qFE# ȽUa/;\DY HanQwYJ*Ap'f[ 0"P֝rt7եśXBm5 ,>\o%PqV$~U`ntp3@2Ee} Ǹ8uQ]E<*o/jۥ l$:tsy#<%?om89#a)&9ͽV0&gǓF=AVm::V_zpP]ekMpby/]"ipR8' (JD05F>2ぐufXl[4jˬ m+*K2Gk<>2 f+>rDo&Ih^q#jCRLD*"0t.xӲb2#Aw==GmT↥MLrc+.kuɕgu&j>$MOTzf7ǏyQ{~2&Ғ`pSͷ1拔և m)S=!YhP۴eAarXj5Nfǵ$4lAG5]BG=~O4?$CsʍHmVF~Wsj۔:<冪\a%k뜸!&CR7?[5 MW3nrb>K!:LyA% ui7IUr'6y,?E'ʂ`̝Sg[ Gdv$fpyjks-20.0.0/tests/keystores/uber/custom_entry_passwords.uber000066400000000000000000000047001364672547600246240ustar00rootroot00000000000000X  hޙ ʄCQ~G7tB9` ;Ҏ[ /e^ʙED/fKɊnI SX=QLM9t6{<uEn҉D},k^ZWIJBt(>Ynbqri$@F;siz!i:L"M_ӐCcŢ#h97VKznl=s6hn1aLQ­t]/ʫ_P~Zycr®νQ N.7^ak2SH5a];p>XT: 7]^DL^/'|'G 2ڟ=LAJe\]mc׾7 pP׉#nەmUzn?7^Mb-kua1u[9`E%r{81.93b0抈\ [+R/;6Wٲ W6.kY- IYb贽K,Dm#q%jA l> 7苃c0^BDDbw ]ؚJNpw8cNymѐ8gUXeg |GeKo6g+c^h+78 lg]bIԼKUJ$jAاrFXy/S֏Cx_a*!V-]8-s,Γ粒U?)bs j\|M(L:3C.,j^&$vVA|bV2߫pv`=#0]3ڕ.կ' ×ɶ{OPK@f=_ ᥂ y?1l>X4tݠ8$'ZG, hE{_¡@[-)%/`SF nh*zuOm_hVt6Э Xs8Z?`qgI >~c HuAx=as^@Qs?0GY_SChzƞ7>Pnk>Oxm#7WKfqe`192e{>XABm_{E/f-\?AHI:-C#,*R@kMC_A8G*r=skq5@ҏ8S+vfDUFk`"7=;[__%^h-EAX3kb"&Z0`VY OĊc1ܑ@Uu%&_eY[:,~L[#vbNԧ˭_i4XJ>yӯS=nQeꌢm@35#d>&_A1m0|*577=i((vbl9־-t@՗98O wtPYyVpG \ϔNGcð)Ɲ -hXq gκ/B_X!pB?1? / 4wBOn # 'L^NQ;PWu @Ы&I藚y̠@^& !o7j$$b }"#iV2C>\,"wW+oH%jFv9[gVZ79J"af"}[8 el2'CMnˏI0v6z-1qKܩhWi2rk֧ =볼 )-2Kkz.ֳr`!(6T3R (Quˌw,k|i[G p\V:`҇Z_K "9K9K):;E r_@A\ HQq:V`B5\c{ZrJ9жEP2aJ%A.) !kFn%N|3?'0\^ ޡ! rFg1GI7OLS}PZrjo&u|[ެsxߠ遷t_W!nS2>NۗRϢV d [@^m Kѣ\/ axjp܀!QYRn{RE-8>eߦ/Gs s0XS(nB(w"7s x5>@F)l1 RؾW xbl6KfY3Qt+7 oc7; +yOi8T,v/EoYMOx&71:-4|QJMHylpËq`L>Ob#7 " ]fGa26¨'-l5a[pyjks-20.0.0/tests/keystores/uber/empty.uber000066400000000000000000000001001364672547600211100ustar00rootroot00000000000000hNOF(.|*EUp !b,Q(A;ǡ_{`pyjks-20.0.0/tests/test_jks.py000066400000000000000000001355441364672547600163330ustar00rootroot00000000000000#!/usr/bin/env python # vim: set ai et ts=4 sw=4 sts=4: """ Tests for pyjks. Note: run 'mvn test' in the tests/java directory to reproduce keystore files (requires a working Maven installation) """ from __future__ import print_function import os import sys import unittest import hashlib import jks from jks.util import py23basestring from . import expected CUR_PATH = os.path.dirname(os.path.abspath(__file__)) KS_PATH = os.path.join(CUR_PATH, 'keystores') try: long except: long = int class AbstractTest(unittest.TestCase): def find_private_key(self, ks, alias): pk = ks.entries[alias] if not isinstance(pk, jks.PrivateKeyEntry): self.fail("Private key entry not found: %s" % alias) if pk.is_decrypted(): self.assertTrue(isinstance(pk.pkey, bytes)) self.assertTrue(isinstance(pk.pkey_pkcs8, bytes)) self.assertTrue(isinstance(pk.cert_chain, list)) self.assertTrue(all(isinstance(c[1], bytes) for c in pk.cert_chain)) return pk def find_secret_key(self, ks, alias): sk = ks.entries[alias] if not isinstance(sk, jks.SecretKeyEntry): self.fail("Secret key entry not found: %s" % alias) if sk.is_decrypted(): self.assertTrue(isinstance(sk.key, bytes)) return sk def find_cert(self, ks, alias): c = ks.entries[alias] if not isinstance(c, jks.TrustedCertEntry): self.fail("Certificate entry not found: %s" % alias) self.assertTrue(isinstance(c.cert, bytes)) self.assertTrue(isinstance(c.type, py23basestring)) return c def check_pkey_and_certs_equal(self, pk, algorithm_oid, pkey_pkcs8, certs): self.assertEqual(pk.algorithm_oid, algorithm_oid) self.assertEqual(pk.pkey_pkcs8, pkey_pkcs8) self.assertEqual(len(pk.cert_chain), len(certs)) for i in range(len(certs)): self.assertEqual(pk.cert_chain[i][1], certs[i]) def check_secret_key_equal(self, sk, algorithm_name, key_size, key_bytes): self.assertEqual(sk.algorithm, algorithm_name) self.assertEqual(sk.key_size, key_size) self.assertEqual(sk.key, key_bytes) class JksTests(AbstractTest): def test_empty_store(self): store = jks.KeyStore.load(KS_PATH + "/jks/empty.jks", "") self.assertEqual(store.store_type, "jks") self.assertEqual(len(store.entries), 0) def test_store_without_passphrase(self): store = jks.KeyStore.load(KS_PATH + "/jks/truststore.jks", None) self.assertEqual(store.store_type, "jks") self.assertEqual(len(store.entries), 2) def test_bad_keystore_format(self): self.assertRaises(jks.util.BadKeystoreFormatException, jks.KeyStore.loads, b"\x00\x00\x00\x00", "") # bad magic bytes self.assertRaises(jks.util.BadKeystoreFormatException, jks.KeyStore.loads, b"\xFE\xED\xFE\xED\x00", "") # insufficient store version bytes self.assertRaises(jks.util.UnsupportedKeystoreVersionException, jks.KeyStore.loads, b"\xFE\xED\xFE\xED\x00\x00\x00\x00", "") # unknown store version self.assertRaises(jks.util.KeystoreSignatureException, jks.KeyStore.loads, b"\xFE\xED\xFE\xED\x00\x00\x00\x02\x00\x00\x00\x00" + b"\x00"*20, "") # bad signature self.assertRaises(jks.util.BadKeystoreFormatException, jks.KeyStore.loads, b"\xFE\xED\xFE\xED\x00\x00\x00\x02\x00\x00\x00\x00" + b"\x00"*19, "") # insufficient signature bytes def test_trailing_data(self): """Issue #21 on github; Portecle is able to load keystores with trailing data after the hash, so we should be as well.""" store_bytes = None with open(KS_PATH + "/jks/RSA1024.jks", "rb") as f: store_bytes = f.read() store = jks.KeyStore.loads(store_bytes + b"\x00"*1, "12345678") store = jks.KeyStore.loads(store_bytes + b"\x00"*1000, "12345678") def test_rsa_1024(self): store = jks.KeyStore.load(KS_PATH + "/jks/RSA1024.jks", "12345678") pk = self.find_private_key(store, "mykey") self.assertEqual(store.store_type, "jks") self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.RSA1024.private_key, expected.RSA1024.certs) def test_rsa_2048_3certs(self): store = jks.KeyStore.load(KS_PATH + "/jks/RSA2048_3certs.jks", "12345678") pk = self.find_private_key(store, "mykey") self.assertEqual(store.store_type, "jks") self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.RSA2048_3certs.private_key, expected.RSA2048_3certs.certs) def test_dsa_2048(self): store = jks.KeyStore.load(KS_PATH + "/jks/DSA2048.jks", "12345678") pk = self.find_private_key(store, "mykey") self.assertEqual(store.store_type, "jks") self.check_pkey_and_certs_equal(pk, jks.util.DSA_OID, expected.DSA2048.private_key, expected.DSA2048.certs) def test_certs(self): store = jks.KeyStore.load(KS_PATH + "/jks/3certs.jks", "12345678") cert1 = self.find_cert(store, "cert1") cert2 = self.find_cert(store, "cert2") cert3 = self.find_cert(store, "cert3") self.assertEqual(cert1.cert, expected.RSA2048_3certs.certs[0]) self.assertEqual(cert2.cert, expected.RSA2048_3certs.certs[1]) self.assertEqual(cert3.cert, expected.RSA2048_3certs.certs[2]) self.assertEqual(store.store_type, "jks") def test_custom_entry_passwords(self): store = jks.KeyStore.load(KS_PATH + "/jks/custom_entry_passwords.jks", "store_password") self.assertEqual(store.store_type, "jks") self.assertEqual(len(store.entries), 2) self.assertEqual(len(store.certs), 1) self.assertEqual(len(store.private_keys), 1) self.assertEqual(len(store.secret_keys), 0) pk = self.find_private_key(store, "private") self.assertRaises(jks.DecryptionFailureException, pk.decrypt, "wrong_password") self.assertTrue(not pk.is_decrypted()) pk.decrypt("private_password") self.assertTrue(pk.is_decrypted()) self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.custom_entry_passwords.private_key, expected.custom_entry_passwords.certs) cert = self.find_cert(store, "cert") self.assertEqual(cert.cert, expected.custom_entry_passwords.certs[0]) def test_duplicate_aliases(self): self.assertRaises(jks.DuplicateAliasException, jks.KeyStore.load, KS_PATH + "/jks/duplicate_aliases.jks", "12345678") def test_non_ascii_jks_password(self): store = jks.KeyStore.load(KS_PATH + "/jks/non_ascii_password.jks", u"\u10DA\u0028\u0CA0\u76CA\u0CA0\u10DA\u0029") pk = self.find_private_key(store, "mykey") self.assertEqual(store.store_type, "jks") self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.jks_non_ascii_password.private_key, expected.jks_non_ascii_password.certs) def test_load_and_save_rsa_keystore(self): with open(KS_PATH + "/jks/RSA2048_3certs.jks", 'rb') as file: keystore_bytes = file.read() store = jks.KeyStore.loads(keystore_bytes, "12345678", False) resaved_keystore_bytes = store.saves('12345678') # since we didn't decrypt the key, the keystores should be identical self.assertEqual(keystore_bytes, resaved_keystore_bytes) def test_load_and_save_dsa_keystore(self): with open(KS_PATH + "/jks/DSA2048.jks", 'rb') as file: keystore_bytes = file.read() store = jks.KeyStore.loads(keystore_bytes, "12345678", False) resaved_keystore_bytes = store.saves('12345678') # since we didn't decrypt the key, the keystores should be identical self.assertEqual(keystore_bytes, resaved_keystore_bytes) def test_load_and_save_keystore_non_ascii_password(self): with open(KS_PATH + "/jks/non_ascii_password.jks", 'rb') as file: keystore_bytes = file.read() store = jks.KeyStore.loads(keystore_bytes, u"\u10DA\u0028\u0CA0\u76CA\u0CA0\u10DA\u0029", False) resaved_keystore_bytes = store.saves(u"\u10DA\u0028\u0CA0\u76CA\u0CA0\u10DA\u0029") # since we didn't decrypt the key, the keystores should be identical self.assertEqual(keystore_bytes, resaved_keystore_bytes) def test_create_and_load_keystore_non_ascii_password(self): cert = jks.PrivateKeyEntry.new('mykey', expected.jks_non_ascii_password.certs, expected.jks_non_ascii_password.private_key) store = jks.KeyStore.new('jks', [cert]) store_bytes = store.saves(u"\u10DA\u0028\u0CA0\u76CA\u0CA0\u10DA\u0029") store2 = jks.KeyStore.loads(store_bytes, u"\u10DA\u0028\u0CA0\u76CA\u0CA0\u10DA\u0029") pk = self.find_private_key(store2, "mykey") self.assertEqual(store2.store_type, "jks") self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.jks_non_ascii_password.private_key, expected.jks_non_ascii_password.certs) def test_create_and_load_non_ascii_alias(self): cert = jks.PrivateKeyEntry.new(u'\xe6\xe6\xe6\xf8\xf8\xf8\xe5\xe5\xf8\xe6', expected.RSA1024.certs, expected.RSA1024.private_key) store = jks.KeyStore.new('jks', [cert]) store_bytes = store.saves('12345678') store2 = jks.KeyStore.loads(store_bytes, '12345678') pk = self.find_private_key(store2, u'\xe6\xe6\xe6\xf8\xf8\xf8\xe5\xe5\xf8\xe6') self.assertEqual(store2.store_type, "jks") self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.RSA1024.private_key, expected.RSA1024.certs) def test_create_and_load_custom_entry_passwords(self): cert = jks.PrivateKeyEntry.new('mykey', expected.custom_entry_passwords.certs, expected.custom_entry_passwords.private_key) store = jks.KeyStore.new('jks', [cert]) pk = self.find_private_key(store, "mykey") self.assertTrue(pk.is_decrypted()) pk.encrypt("private_password") self.assertTrue(not pk.is_decrypted()) store_bytes = store.saves("store_password") store2 = jks.KeyStore.loads(store_bytes, 'store_password') pk2 = self.find_private_key(store2, "mykey") self.assertTrue(not pk2.is_decrypted()) pk2.decrypt("private_password") self.assertTrue(pk2.is_decrypted()) self.assertEqual(store2.store_type, "jks") self.check_pkey_and_certs_equal(pk2, jks.util.RSA_ENCRYPTION_OID, expected.custom_entry_passwords.private_key, expected.custom_entry_passwords.certs) def test_create_and_load_keystore_pkcs8_rsa(self): cert = jks.PrivateKeyEntry.new('mykey', expected.RSA2048_3certs.certs, expected.RSA2048_3certs.private_key) store = jks.KeyStore.new('jks', [cert]) store_bytes = store.saves('12345678') store2 = jks.KeyStore.loads(store_bytes, '12345678') pk = self.find_private_key(store2, "mykey") self.assertEqual(store2.store_type, "jks") self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.RSA2048_3certs.private_key, expected.RSA2048_3certs.certs) def test_create_and_load_keystore_pkcs8_dsa(self): cert = jks.PrivateKeyEntry.new('mykey', expected.DSA2048.certs, expected.DSA2048.private_key) store = jks.KeyStore.new('jks', [cert]) store_bytes = store.saves('12345678') store2 = jks.KeyStore.loads(store_bytes, '12345678') pk = self.find_private_key(store2, "mykey") self.assertEqual(store2.store_type, "jks") self.check_pkey_and_certs_equal(pk, jks.util.DSA_OID, expected.DSA2048.private_key, expected.DSA2048.certs) def test_create_and_load_keystore_raw_rsa(self): cert = jks.PrivateKeyEntry.new('mykey', expected.RSA2048_3certs.certs, expected.RSA2048_3certs.raw_private_key, 'rsa_raw') store = jks.KeyStore.new('jks', [cert]) store_bytes = store.saves('12345678') store2 = jks.KeyStore.loads(store_bytes, '12345678') pk = self.find_private_key(store2, "mykey") self.assertEqual(store2.store_type, "jks") self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.RSA2048_3certs.private_key, expected.RSA2048_3certs.certs) def test_create_and_load_keystore_trusted_certs(self): cert1 = jks.TrustedCertEntry(type='X.509', cert=expected.RSA2048_3certs.certs[0], timestamp=1463338684456, alias='cert1') cert2 = jks.TrustedCertEntry(type='X.509', cert=expected.RSA2048_3certs.certs[1], timestamp=1463338684456, alias='cert2') cert3 = jks.TrustedCertEntry(type='X.509', cert=expected.RSA2048_3certs.certs[2], timestamp=1463338684456, alias='cert3') store = jks.KeyStore.new('jks', [cert1, cert2, cert3]) store_bytes = store.saves('12345678') store2 = jks.KeyStore.loads(store_bytes, '12345678') cert1_2 = self.find_cert(store2, "cert1") cert2_2 = self.find_cert(store2, "cert2") cert3_2 = self.find_cert(store2, "cert3") self.assertEqual(cert1_2.cert, expected.RSA2048_3certs.certs[0]) self.assertEqual(cert2_2.cert, expected.RSA2048_3certs.certs[1]) self.assertEqual(cert3_2.cert, expected.RSA2048_3certs.certs[2]) def test_create_and_load_keystore_both_trusted_and_private(self): pk = jks.PrivateKeyEntry.new('mykey', expected.RSA2048_3certs.certs, expected.RSA2048_3certs.raw_private_key, 'rsa_raw') cert1 = jks.TrustedCertEntry.new('cert1', expected.RSA2048_3certs.certs[0]) cert2 = jks.TrustedCertEntry.new('cert2', expected.RSA2048_3certs.certs[1]) cert3 = jks.TrustedCertEntry.new('cert3', expected.RSA2048_3certs.certs[2]) store = jks.KeyStore.new('jks', [pk, cert1, cert2, cert3]) store_bytes = store.saves('12345678') store2 = jks.KeyStore.loads(store_bytes, '12345678') cert1_2 = self.find_cert(store2, "cert1") cert2_2 = self.find_cert(store2, "cert2") cert3_2 = self.find_cert(store2, "cert3") pk2 = self.find_private_key(store2, "mykey") self.assertEqual(cert1_2.cert, expected.RSA2048_3certs.certs[0]) self.assertEqual(cert2_2.cert, expected.RSA2048_3certs.certs[1]) self.assertEqual(cert3_2.cert, expected.RSA2048_3certs.certs[2]) self.assertEqual(store.store_type, "jks") self.check_pkey_and_certs_equal(pk2, jks.util.RSA_ENCRYPTION_OID, expected.RSA2048_3certs.private_key, expected.RSA2048_3certs.certs) def test_new_keystore_duplicate_alias(self): cert1 = jks.TrustedCertEntry.new('cert1', expected.RSA2048_3certs.certs[0]) cert2 = jks.TrustedCertEntry.new('cert1', expected.RSA2048_3certs.certs[1]) self.assertRaises(jks.util.DuplicateAliasException, jks.KeyStore.new, 'jks', [cert1, cert2]) def test_save_invalid_keystore_format(self): self.assertRaises(jks.util.UnsupportedKeystoreTypeException, jks.KeyStore.new, 'invalid', []) def test_save_invalid_keystore_entry(self): self.assertRaises(jks.util.UnsupportedKeystoreEntryTypeException, jks.KeyStore.new, 'jks', ['string']) def test_create_unknown_key_format(self): self.assertRaises(jks.util.UnsupportedKeyFormatException, jks.PrivateKeyEntry.new, 'alias','cert', 'key', 'ecdsa') def test_save_jks_keystore_with_secret_key(self): store = jks.KeyStore.load(KS_PATH + "/jceks/AES128.jceks", "12345678") store.store_type = 'jks' # changing it to a jks keystore self.assertRaises(jks.util.UnsupportedKeystoreEntryTypeException, store.saves, '12345678') def test_create_jks_keystore_with_secret_key(self): store = jks.KeyStore.load(KS_PATH + "/jceks/AES128.jceks", "12345678") sk = self.find_secret_key(store, "mykey") self.assertRaises(jks.util.UnsupportedKeystoreEntryTypeException, jks.KeyStore.new, 'jks', [sk]) def test_alias_lower_certificate(self): cert = jks.TrustedCertEntry.new('CERT', expected.RSA2048_3certs.certs[0]) keystore = jks.KeyStore.new('jks', [cert]) alias = list(keystore.certs.keys())[0] self.assertTrue(alias.islower()) def test_alias_lower_pkey(self): pkey = jks.PrivateKeyEntry.new('PKEY', expected.RSA2048_3certs.certs, expected.RSA2048_3certs.private_key) keystore = jks.KeyStore.new('jks', [pkey]) alias = list(keystore.private_keys.keys())[0] self.assertTrue(alias.islower()) class JceTests(AbstractTest): def test_empty_store(self): store = jks.KeyStore.load(KS_PATH + "/jceks/empty.jceks", "") self.assertEqual(store.store_type, "jceks") self.assertEqual(len(store.entries), 0) def test_bad_keystore_format(self): self.assertRaises(jks.util.BadKeystoreFormatException, jks.KeyStore.loads, b"\x00\x00\x00\x00", "") # bad magic bytes self.assertRaises(jks.util.BadKeystoreFormatException, jks.KeyStore.loads, b"\xCE\xCE\xCE\xCE\x00", "") # insufficient store version bytes self.assertRaises(jks.util.UnsupportedKeystoreVersionException, jks.KeyStore.loads, b"\xCE\xCE\xCE\xCE\x00\x00\x00\x00", "") # unknown store version self.assertRaises(jks.util.KeystoreSignatureException, jks.KeyStore.loads, b"\xCE\xCE\xCE\xCE\x00\x00\x00\x02\x00\x00\x00\x00" + b"\x00"*20, "") # bad signature self.assertRaises(jks.util.BadKeystoreFormatException, jks.KeyStore.loads, b"\xCE\xCE\xCE\xCE\x00\x00\x00\x02\x00\x00\x00\x00" + b"\x00"*19, "") # insufficient signature bytes def test_trailing_data(self): """Issue #21 on github; Portecle is able to load keystores with trailing data after the hash, so we should be as well.""" store_bytes = None with open(KS_PATH + "/jceks/RSA1024.jceks", "rb") as f: store_bytes = f.read() store = jks.KeyStore.loads(store_bytes + b"\x00"*1, "12345678") store = jks.KeyStore.loads(store_bytes + b"\x00"*1000, "12345678") def test_rsa_1024(self): store = jks.KeyStore.load(KS_PATH + "/jceks/RSA1024.jceks", "12345678") pk = self.find_private_key(store, "mykey") self.assertEqual(store.store_type, "jceks") self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.RSA1024.private_key, expected.RSA1024.certs) def test_rsa_2048_3certs(self): store = jks.KeyStore.load(KS_PATH + "/jceks/RSA2048_3certs.jceks", "12345678") pk = self.find_private_key(store, "mykey") self.assertEqual(store.store_type, "jceks") self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.RSA2048_3certs.private_key, expected.RSA2048_3certs.certs) def test_dsa_2048(self): store = jks.KeyStore.load(KS_PATH + "/jceks/DSA2048.jceks", "12345678") pk = self.find_private_key(store, "mykey") self.assertEqual(store.store_type, "jceks") self.check_pkey_and_certs_equal(pk, jks.util.DSA_OID, expected.DSA2048.private_key, expected.DSA2048.certs) def test_certs(self): store = jks.KeyStore.load(KS_PATH + "/jceks/3certs.jceks", "12345678") cert1 = self.find_cert(store, "cert1") cert2 = self.find_cert(store, "cert2") cert3 = self.find_cert(store, "cert3") self.assertEqual(cert1.cert, expected.RSA2048_3certs.certs[0]) self.assertEqual(cert2.cert, expected.RSA2048_3certs.certs[1]) self.assertEqual(cert3.cert, expected.RSA2048_3certs.certs[2]) self.assertEqual(store.store_type, "jceks") def test_custom_entry_passwords(self): store = jks.KeyStore.load(KS_PATH + "/jceks/custom_entry_passwords.jceks", "store_password") # shouldn't throw, we're not yet trying to decrypt anything at this point self.assertEqual(store.store_type, "jceks") self.assertEqual(len(store.entries), 3) self.assertEqual(len(store.certs), 1) self.assertEqual(len(store.private_keys), 1) self.assertEqual(len(store.secret_keys), 1) pk = self.find_private_key(store, "private") self.assertRaises(jks.DecryptionFailureException, pk.decrypt, "wrong_password") self.assertTrue(not pk.is_decrypted()) pk.decrypt("private_password") self.assertTrue(pk.is_decrypted()) self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.custom_entry_passwords.private_key, expected.custom_entry_passwords.certs) sk = self.find_secret_key(store, "secret") self.assertRaises(jks.DecryptionFailureException, sk.decrypt, "wrong_password") sk.decrypt("secret_password") self.assertTrue(sk.is_decrypted()) self.assertEqual(sk.key, b"\x3f\x68\x05\x04\xc6\x6c\xc2\x5a\xae\x65\xd0\xfa\x49\xc5\x26\xec") self.assertEqual(sk.algorithm, "AES") self.assertEqual(sk.key_size, 128) cert = self.find_cert(store, "cert") self.assertEqual(cert.cert, expected.custom_entry_passwords.certs[0]) def test_duplicate_aliases(self): self.assertRaises(jks.DuplicateAliasException, jks.KeyStore.load, KS_PATH + "/jceks/duplicate_aliases.jceks", "12345678") class JceOnlyTests(AbstractTest): def test_des_secret_key(self): store = jks.KeyStore.load(KS_PATH + "/jceks/DES.jceks", "12345678") sk = self.find_secret_key(store, "mykey") self.assertEqual(store.store_type, "jceks") self.check_secret_key_equal(sk, "DES", 64, b"\x4c\xf2\xfe\x91\x5d\x08\x2a\x43") def test_desede_secret_key(self): store = jks.KeyStore.load(KS_PATH + "/jceks/DESede.jceks", "12345678") sk = self.find_secret_key(store, "mykey") self.assertEqual(store.store_type, "jceks") self.check_secret_key_equal(sk, "DESede", 192, b"\x67\x5e\x52\x45\xe9\x67\x3b\x4c\x8f\xc1\x94\xce\xec\x43\x3b\x31\x8c\x45\xc2\xe0\x67\x5e\x52\x45") def test_aes128_secret_key(self): store = jks.KeyStore.load(KS_PATH + "/jceks/AES128.jceks", "12345678") sk = self.find_secret_key(store, "mykey") self.assertEqual(store.store_type, "jceks") self.check_secret_key_equal(sk, "AES", 128, b"\x66\x6e\x02\x21\xcc\x44\xc1\xfc\x4a\xab\xf4\x58\xf9\xdf\xdd\x3c") def test_aes256_secret_key(self): store = jks.KeyStore.load(KS_PATH + "/jceks/AES256.jceks", "12345678") sk = self.find_secret_key(store, "mykey") self.assertEqual(store.store_type, "jceks") self.check_secret_key_equal(sk, "AES", 256, b"\xe7\xd7\xc2\x62\x66\x82\x21\x78\x7b\x6b\x5a\x0f\x68\x77\x12\xfd\xe4\xbe\x52\xe9\xe7\xd7\xc2\x62\x66\x82\x21\x78\x7b\x6b\x5a\x0f") def test_pbkdf2_hmac_sha1(self): store = jks.KeyStore.load(KS_PATH + "/jceks/PBKDF2WithHmacSHA1.jceks", "12345678") sk = self.find_secret_key(store, "mykey") self.assertEqual(store.store_type, "jceks") self.check_secret_key_equal(sk, "PBKDF2WithHmacSHA1", 256, b"\x57\x95\x36\xd9\xa2\x7f\x7e\x31\x4e\xf4\xe3\xff\xa5\x76\x26\xef\xe6\x70\xe8\xf4\xd2\x96\xcd\x31\xba\x1a\x82\x7d\x9a\x3b\x1e\xe1") def test_unknown_type_of_sealed_object(self): """Verify that an exception is raised when encountering a (serialized) Java object inside a SecretKey entry that is not of type javax.crypto.SealedObject""" self.assertRaises(jks.UnexpectedJavaTypeException, lambda: \ jks.KeyStore.load(KS_PATH + "/jceks/unknown_type_of_sealed_object.jceks", "12345678")) def test_unknown_type_inside_sealed_object(self): """Verify that an exception is raised when encountering a (serialized) Java object inside of a SealedObject in a SecretKey entry (after decryption) that is not of a recognized/supported type""" self.assertRaises(jks.UnexpectedJavaTypeException, lambda: \ jks.KeyStore.load(KS_PATH + "/jceks/unknown_type_inside_sealed_object.jceks", "12345678")) def test_unknown_sealed_object_sealAlg(self): self.assertRaises(jks.UnexpectedAlgorithmException, lambda: \ jks.KeyStore.load(KS_PATH + "/jceks/unknown_sealed_object_sealAlg.jceks", "12345678")) class BksOnlyTests(AbstractTest): def check_bks_entry(self, entry, store_type): """Checks that apply to BKS entries of any type""" self.assertEqual(entry.store_type, store_type) self.assertTrue(isinstance(entry.alias, py23basestring)) self.assertTrue(isinstance(entry.timestamp, (int, long))) self.assertTrue(isinstance(entry.cert_chain, list)) self.assertTrue(all(isinstance(c, jks.bks.BksTrustedCertEntry) for c in entry.cert_chain)) def check_cert_entry(self, entry, store_type): self.check_bks_entry(entry, store_type) self.assertTrue(isinstance(entry.cert, bytes)) self.assertTrue(isinstance(entry.type, py23basestring)) self.assertTrue(entry.is_decrypted()) def check_sealed_key_entry(self, entry, store_type): self.check_bks_entry(entry, store_type) self.assertTrue(isinstance(entry, jks.bks.BksSealedKeyEntry)) if entry.is_decrypted(): # all attributes of the nested entry should also be directly accessible through the parent sealed entry, # so run the same check twice with the two different objects self.check_plain_key_entry(entry.nested, store_type) self.check_plain_key_entry(entry, store_type, check_type=False) def check_secret_key_entry(self, entry, store_type): self.check_bks_entry(entry, store_type) self.assertTrue(isinstance(entry, jks.bks.BksSecretKeyEntry)) self.assertTrue(isinstance(entry.key, bytes)) def check_plain_key_entry(self, key_entry, store_type, check_type=True): self.check_bks_entry(key_entry, store_type) if check_type: self.assertTrue(isinstance(key_entry, jks.bks.BksKeyEntry)) self.assertTrue(isinstance(key_entry.format, py23basestring)) self.assertTrue(isinstance(key_entry.algorithm, py23basestring)) self.assertTrue(isinstance(key_entry.encoded, bytes)) self.assertTrue(key_entry.is_decrypted()) if key_entry.type == jks.bks.KEY_TYPE_PRIVATE: self.assertTrue(isinstance(key_entry.pkey_pkcs8, bytes)) self.assertTrue(isinstance(key_entry.pkey, bytes)) self.assertTrue(isinstance(key_entry.algorithm_oid, tuple)) elif key_entry.type == jks.bks.KEY_TYPE_PUBLIC: self.assertTrue(isinstance(key_entry.public_key_info, bytes)) self.assertTrue(isinstance(key_entry.public_key, bytes)) self.assertTrue(isinstance(key_entry.algorithm_oid, tuple)) elif key_entry.type == jks.bks.KEY_TYPE_SECRET: self.assertTrue(isinstance(key_entry.key, bytes)) else: self.fail("No such key type: %s" % repr(key_entry.type)) # TODO: code duplication with JKS' check_pkey_and_certs_equal; only difference is that in JKS entries # the cert_chain is stored as a tuple instead of a TrustedCertEntry object. # consider changing that so this logic can be reused def check_pkey_and_certs_equal(self, pk, algorithm_oid, pkey_pkcs8, certs): self.assertEqual(pk.algorithm_oid, algorithm_oid) self.assertEqual(pk.pkey_pkcs8, pkey_pkcs8) self.assertEqual(len(pk.cert_chain), len(certs)) for i in range(len(certs)): self.assertEqual(pk.cert_chain[i].cert, certs[i]) # ---------------------------------------------- def test_bad_bks_keystore_format(self): self.assertRaises(jks.util.BadKeystoreFormatException, jks.bks.BksKeyStore.loads, b"\x00\x00\x00", "") # insufficient store version bytes self.assertRaises(jks.util.UnsupportedKeystoreVersionException, jks.bks.BksKeyStore.loads, b"\x00\x00\x00\x00" + b"\x00\x00\x00\x08" + (b"\xFF"*8) + b"\x00\x00\x00\x14" + b"\x00" + (b"\x00"*20), "") # unknown store version self.assertRaises(jks.util.KeystoreSignatureException, jks.bks.BksKeyStore.loads, b"\x00\x00\x00\x02" + b"\x00\x00\x00\x08" + (b"\xFF"*8) + b"\x00\x00\x00\x14" + b"\x00" + (b"\x00"*20), "") # bad HMAC self.assertRaises(jks.util.BadKeystoreFormatException, jks.bks.BksKeyStore.loads, b"\x00\x00\x00\x02" + b"\x00\x00\x00\x08" + (b"\xFF"*8) + b"\x00\x00\x00\x14" + b"\x00" + (b"\x00"*19), "") # insufficient HMAC bytes def test_bad_uber_keystore_format(self): self.assertRaises(jks.util.BadKeystoreFormatException, jks.bks.UberKeyStore.loads, b"\x00\x00\x00", "") # insufficient store version bytes self.assertRaises(jks.util.UnsupportedKeystoreVersionException, jks.bks.UberKeyStore.loads, b"\x00\x00\x00\x00" + b"\x00\x00\x00\x08" + (b"\xFF"*8) + b"\x00\x00\x00\x14", "") # unknown store version password = "" salt = b"\xFF"*8 self.assertRaises(jks.util.KeystoreSignatureException, jks.bks.UberKeyStore.loads, b"\x00\x00\x00\x01" + \ b"\x00\x00\x00\x08" + salt + \ b"\x00\x00\x00\x14" + \ jks.rfc7292.encrypt_PBEWithSHAAndTwofishCBC(b"\x00" + b"\00"*20, password, salt, 0x14), password) # empty embedded BKS entries + bad SHA-1 hash of that 0-byte store self.assertRaises(jks.util.BadKeystoreFormatException, jks.bks.UberKeyStore.loads, b"\x00\x00\x00\x01" + \ b"\x00\x00\x00\x08" + salt + \ b"\x00\x00\x00\x14" + \ jks.rfc7292.encrypt_PBEWithSHAAndTwofishCBC(b"\x00" + b"\00"*10, password, salt, 0x14), password) # insufficient signature bytes def test_empty_store_v1(self): store = jks.bks.BksKeyStore.load(KS_PATH + "/bks/empty.bksv1", "") self.assertEqual(store.version, 1) def test_empty_store_v2(self): store = jks.bks.BksKeyStore.load(KS_PATH + "/bks/empty.bksv2", "") self.assertEqual(store.version, 2) def test_empty_store_uber(self): store = jks.bks.UberKeyStore.load(KS_PATH + "/uber/empty.uber", "") self.assertEqual(store.version, 1) def test_christmas_store_v1(self): store = jks.bks.BksKeyStore.load(KS_PATH + "/bks/christmas.bksv1", "12345678") self.assertEqual(store.version, 1) self._test_christmas_store(store, "bks") def test_christmas_store_v2(self): store = jks.bks.BksKeyStore.load(KS_PATH + "/bks/christmas.bksv2", "12345678") self.assertEqual(store.version, 2) self._test_christmas_store(store, "bks") def test_christmas_store_uber(self): store = jks.bks.UberKeyStore.load(KS_PATH + "/uber/christmas.uber", "12345678") self.assertEqual(store.version, 1) self._test_christmas_store(store, "uber") def test_custom_entry_passwords_v1(self): store = jks.bks.BksKeyStore.load(KS_PATH + "/bks/custom_entry_passwords.bksv1", "store_password") self.assertEqual(store.version, 1) self._test_custom_entry_passwords(store, "bks") def test_custom_entry_passwords_v2(self): store = jks.bks.BksKeyStore.load(KS_PATH + "/bks/custom_entry_passwords.bksv2", "store_password") self.assertEqual(store.version, 2) self._test_custom_entry_passwords(store, "bks") def test_custom_entry_passwords_uber(self): store = jks.bks.UberKeyStore.load(KS_PATH + "/uber/custom_entry_passwords.uber", "store_password") self.assertEqual(store.version, 1) self._test_custom_entry_passwords(store, "uber") def _test_christmas_store(self, store, store_type): self.assertEqual(store.store_type, store_type) self.assertEqual(len(store.entries), 6) self.assertEqual(len(store.certs), 1) self.assertEqual(len(store.sealed_keys), 3) self.assertEqual(len(store.secret_keys), 1) self.assertEqual(len(store.plain_keys), 1) sealed_public = store.entries["sealed_public_key"] self.check_sealed_key_entry(sealed_public, store_type) self.assertTrue(sealed_public.is_decrypted()) self.assertEqual(sealed_public.type, jks.bks.KEY_TYPE_PUBLIC) self.assertEqual(sealed_public.algorithm, "RSA") self.assertEqual(sealed_public.algorithm_oid, jks.util.RSA_ENCRYPTION_OID) self.assertEqual(sealed_public.public_key_info, expected.bks_christmas.public_key) sealed_private = store.entries["sealed_private_key"] self.check_sealed_key_entry(sealed_private, store_type) self.assertEqual(sealed_private.type, jks.bks.KEY_TYPE_PRIVATE) self.assertEqual(sealed_private.algorithm, "RSA") self.assertTrue(sealed_private.is_decrypted()) self.check_pkey_and_certs_equal(sealed_private, jks.util.RSA_ENCRYPTION_OID, expected.bks_christmas.private_key, expected.bks_christmas.certs) sealed_secret = store.entries["sealed_secret_key"] self.check_sealed_key_entry(sealed_secret, store_type) self.assertEqual(sealed_secret.type, jks.bks.KEY_TYPE_SECRET) self.assertEqual(sealed_secret.algorithm, "AES") self.check_secret_key_equal(sealed_secret, "AES", 128, b"\x3f\x68\x05\x04\xc6\x6c\xc2\x5a\xae\x65\xd0\xfa\x49\xc5\x26\xec") plain_key = store.entries["plain_key"] self.check_plain_key_entry(plain_key, store_type) self.assertEqual(plain_key.type, jks.bks.KEY_TYPE_SECRET) self.assertEqual(plain_key.algorithm, "DES") self.check_secret_key_equal(plain_key, "DES", 64, b"\x4c\xf2\xfe\x91\x5d\x08\x2a\x43") cert = store.entries["cert"] self.check_cert_entry(cert, store_type) self.assertEqual(cert.cert, expected.bks_christmas.certs[0]) stored_value = store.entries["stored_value"] self.check_secret_key_entry(stored_value, store_type) self.assertEqual(stored_value.key, b"\x02\x03\x05\x07\x0B\x0D\x11\x13\x17") def _test_custom_entry_passwords(self, store, store_type): self.assertEqual(store.store_type, store_type) self.assertEqual(len(store.entries), 3) self.assertEqual(len(store.certs), 0) self.assertEqual(len(store.sealed_keys), 3) self.assertEqual(len(store.secret_keys), 0) self.assertEqual(len(store.plain_keys), 0) attrs_non_encrypted = ["alias", "timestamp", "store_type", "cert_chain"] attrs_encrypted_common = ["type", "format", "algorithm", "encoded"] attrs_encrypted_public = attrs_encrypted_common + ["public_key_info", "public_key", "algorithm_oid"] attrs_encrypted_private = attrs_encrypted_common + ["pkey", "pkey_pkcs8", "algorithm_oid"] attrs_encrypted_secret = attrs_encrypted_common + ["key", "key_size"] sealed_public = store.entries["sealed_public_key"] self.assertFalse(sealed_public.is_decrypted()) for a in attrs_encrypted_public: self.assertRaises(jks.util.NotYetDecryptedException, getattr, sealed_public, a) for a in attrs_non_encrypted: getattr(sealed_public, a) # shouldn't throw self.assertRaises(jks.util.DecryptionFailureException, sealed_public.decrypt, "wrong_password") sealed_public.decrypt("public_password") self.assertTrue(sealed_public.is_decrypted()) for a in attrs_encrypted_public: getattr(sealed_public, a) # shouldn't throw sealed_private = store.entries["sealed_private_key"] self.assertFalse(sealed_private.is_decrypted()) for a in attrs_encrypted_private: self.assertRaises(jks.util.NotYetDecryptedException, getattr, sealed_private, a) for a in attrs_non_encrypted: getattr(sealed_private, a) # shouldn't throw self.assertRaises(jks.util.DecryptionFailureException, sealed_private.decrypt, "wrong_password") sealed_private.decrypt("private_password") self.assertTrue(sealed_private.is_decrypted()) for a in attrs_encrypted_private: getattr(sealed_private, a) # shouldn't throw sealed_secret = store.entries["sealed_secret_key"] self.assertFalse(sealed_secret.is_decrypted()) for a in attrs_encrypted_secret: self.assertRaises(jks.util.NotYetDecryptedException, getattr, sealed_secret, a) for a in attrs_non_encrypted: getattr(sealed_secret, a) # shouldn't throw self.assertRaises(jks.util.DecryptionFailureException, sealed_secret.decrypt, "wrong_password") sealed_secret.decrypt("secret_password") self.assertTrue(sealed_secret.is_decrypted()) for a in attrs_encrypted_secret: getattr(sealed_secret, a) # shouldn't throw def test_trailing_data_v1(self): """Issue #21 on github; Portecle is able to load keystores with trailing data after the HMAC signature, so we should be as well.""" christmas_store_bytes = None with open(KS_PATH + "/bks/christmas.bksv1", "rb") as f: christmas_store_bytes = f.read() store = jks.bks.BksKeyStore.loads(christmas_store_bytes + b"\x00"*1, "12345678") store = jks.bks.BksKeyStore.loads(christmas_store_bytes + b"\x00"*1000, "12345678") self._test_christmas_store(store, "bks") def test_trailing_data_v2(self): """Issue #21 on github; Portecle is able to load keystores with trailing data after the HMAC signature, so we should be as well.""" christmas_store_bytes = None with open(KS_PATH + "/bks/christmas.bksv2", "rb") as f: christmas_store_bytes = f.read() store = jks.bks.BksKeyStore.loads(christmas_store_bytes + b"\x00"*1, "12345678") store = jks.bks.BksKeyStore.loads(christmas_store_bytes + b"\x00"*1000, "12345678") self._test_christmas_store(store, "bks") def test_trailing_data_uber(self): # Note: trailing data in an UBER keystore should always be a fatal error because there is no way to distinguish # the trailing data from the encrypted store blob in advance. christmas_store_bytes = None with open(KS_PATH + "/uber/christmas.uber", "rb") as f: christmas_store_bytes = f.read() self.assertRaises(jks.util.DecryptionFailureException, jks.bks.UberKeyStore.loads, christmas_store_bytes + b"\x00"*256, "12345678") # maintain multiple of 16B -> decryption failure self.assertRaises(jks.util.BadKeystoreFormatException, jks.bks.UberKeyStore.loads, christmas_store_bytes + b"\x00"*255, "12345678") # break multiple of 16B -> bad format class MiscTests(AbstractTest): def test_bitstring_to_bytes(self): def bs2b(t, _str): bits_tuple = tuple(map(int, _str.replace(" ", ""))) result = jks.util.bitstring_to_bytes(bits_tuple) t.assertTrue(isinstance(result, bytes)) return result self.assertEqual(bs2b(self, ""), b"") self.assertEqual(bs2b(self, " 0"), b"\x00") self.assertEqual(bs2b(self, " 1"), b"\x01") self.assertEqual(bs2b(self, "0110 1010"), b"\x6a") self.assertEqual(bs2b(self, "1111 1111"), b"\xff") self.assertEqual(bs2b(self, " 0 1111 1111"), b"\x00\xff") self.assertEqual(bs2b(self, " 1 1111 1111"), b"\x01\xff") def test_strip_pkcs5_padding(self): self.assertEqual(jks.util.strip_pkcs5_padding(b"\x08\x08\x08\x08\x08\x08\x08\x08"), b"") self.assertEqual(jks.util.strip_pkcs5_padding(b"\x01\x07\x07\x07\x07\x07\x07\x07"), b"\x01") self.assertEqual(jks.util.strip_pkcs5_padding(b"\x01\x02\x03\x04\x05\x06\x07\x01"), b"\x01\x02\x03\x04\x05\x06\x07") self.assertRaises(jks.util.BadPaddingException, jks.util.strip_pkcs5_padding, b"") self.assertRaises(jks.util.BadPaddingException, jks.util.strip_pkcs5_padding, b"\x01") self.assertRaises(jks.util.BadPaddingException, jks.util.strip_pkcs5_padding, b"\x01\x02\x03\x04\x08\x08") self.assertRaises(jks.util.BadPaddingException, jks.util.strip_pkcs5_padding, b"\x07\x07\x07\x07\x07\x07\x07") self.assertRaises(jks.util.BadPaddingException, jks.util.strip_pkcs5_padding, b"\x00\x00\x00\x00\x00\x00\x00\x00") def test_sun_jce_pbe_decrypt(self): self.assertEqual(b"sample", jks.sun_crypto.jce_pbe_decrypt(b"\xc4\x20\x59\xac\x54\x03\xc7\xbf", "my_password", b"\x01\x02\x03\x04\x05\x06\x07\x08", 42)) self.assertEqual(b"sample", jks.sun_crypto.jce_pbe_decrypt(b"\xef\x9f\xbd\xc5\x91\x5f\x49\x50", "my_password", b"\x01\x02\x03\x04\x01\x02\x03\x05", 42)) self.assertEqual(b"sample", jks.sun_crypto.jce_pbe_decrypt(b"\x72\x8f\xd8\xcc\x21\x41\x25\x80", "my_password", b"\x01\x02\x03\x04\x01\x02\x03\x04", 42)) def test_pkcs12_key_derivation(self): self.assertEqual(jks.rfc7292.derive_key(hashlib.sha1, jks.rfc7292.PURPOSE_MAC_MATERIAL, "", b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000, 16), b"\xe7\x76\x85\x01\x6a\x53\x62\x1e\x9a\x2a\x8a\x0f\x80\x00\x2e\x70") self.assertEqual(jks.rfc7292.derive_key(hashlib.sha1, jks.rfc7292.PURPOSE_MAC_MATERIAL, "", b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000, 17), b"\xe7\x76\x85\x01\x6a\x53\x62\x1e\x9a\x2a\x8a\x0f\x80\x00\x2e\x70\xfe") self.assertEqual(jks.rfc7292.derive_key(hashlib.sha1, jks.rfc7292.PURPOSE_KEY_MATERIAL, "", b"\xbf\x0a\xaa\x4f\x84\xb4\x4e\x41\x16\x0a\x11\xb7\xed\x98\x58\xa0\x95\x3b\x4b\xf8", 2010, 2), b"\x1b\xee") self.assertEqual(jks.rfc7292.derive_key(hashlib.sha1, jks.rfc7292.PURPOSE_MAC_MATERIAL, "password", b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000, 0), b"") self.assertEqual(jks.rfc7292.derive_key(hashlib.sha1, jks.rfc7292.PURPOSE_MAC_MATERIAL, "password", b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000, 16), b"\x21\x2b\xab\x71\x42\x2d\x31\xa5\xd3\x93\x4c\x20\xe5\xe7\x7e\xb7") self.assertEqual(jks.rfc7292.derive_key(hashlib.sha1, jks.rfc7292.PURPOSE_MAC_MATERIAL, "password", b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000, 17), b"\x21\x2b\xab\x71\x42\x2d\x31\xa5\xd3\x93\x4c\x20\xe5\xe7\x7e\xb7\xed") self.assertEqual(jks.rfc7292.derive_key(hashlib.sha1, jks.rfc7292.PURPOSE_KEY_MATERIAL, "password", b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000, 17), b"\xe8\x0b\xdd\x02\x01\x55\x31\x7f\x30\xb8\x54\xcb\x9f\x78\x11\x81\x76") self.assertEqual(jks.rfc7292.derive_key(hashlib.sha1, jks.rfc7292.PURPOSE_IV_MATERIAL, "password", b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000, 17), b"\x27\x68\x91\x7c\xf9\xf4\x33\xb0\xa6\x4a\x9f\xcc\xbc\x80\x5f\xd6\x48") fancy_password = u"\u10DA\u0028\u0CA0\u76CA\u0CA0\u10DA\u0029" self.assertEqual(jks.rfc7292.derive_key(hashlib.sha1, jks.rfc7292.PURPOSE_KEY_MATERIAL, fancy_password, b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000, 129), b"\x5e\x3d\xab\x11\xd7\x55\x2c\xaf\x58\x2f\x61\xbd\x95\xdd\x03\xa7\x83\xa4\xf0\x2a\xeb\xdc\x86\x5c\xdb\x1e\xae\x2c\x8f\x91\x82\xa5" + \ b"\x84\xbf\xab\x23\x75\x1c\x83\x96\x34\xcf\x0e\xc1\x6c\x84\xd7\x15\xd1\x7c\x10\x3d\x8b\xa8\xef\x1f\x63\xb4\x71\xdf\x15\x4f\xc2\x86" + \ b"\xf9\x5c\xba\x37\xad\xd3\xe2\xb2\xaa\xb3\x37\x60\x42\x3d\x69\x29\xd1\x96\x47\x32\x6c\x41\x57\xfa\x0e\x20\x87\xd6\xa7\x40\xae\x0f" + \ b"\xe8\x17\xd8\x8e\xda\x12\x53\xac\x7e\x19\x99\xc6\x26\x20\xed\x5d\xcd\x44\xe4\xed\x05\xb9\xdc\x39\x6a\x91\x1b\x00\xbb\x39\x3e\xd8" + \ b"\x9b") def test_decrypt_PBEWithSHAAnd3KeyTripleDESCBC(self): fancy_password = u"\u10DA\u0028\u0CA0\u76CA\u0CA0\u10DA\u0029" self.assertEqual(b"sample", jks.rfc7292.decrypt_PBEWithSHAAnd3KeyTripleDESCBC(b"\x69\xea\xff\x28\x65\x85\x0a\x68", "mypassword", b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000)) self.assertEqual(b"sample", jks.rfc7292.decrypt_PBEWithSHAAnd3KeyTripleDESCBC(b"\x73\xf1\xc7\x14\x74\xa3\x04\x59", fancy_password, b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000)) self.assertEqual(b"-------16-------", jks.rfc7292.decrypt_PBEWithSHAAnd3KeyTripleDESCBC(b"\x4c\xbb\xc8\x03\x09\x35\x27\xcb\xd6\x98\x81\xba\x93\x75\x7a\x96\x60\xf2\x5b\xa9\x1e\x32\xe2\x4d", "mypassword", b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000)) self.assertEqual(b"-------16-------", jks.rfc7292.decrypt_PBEWithSHAAnd3KeyTripleDESCBC(b"\xe1\xce\x6d\xa1\x5b\x81\x0c\xdd\x1c\x7c\xbd\x14\x4a\x64\xc4\xa1\xda\x26\x27\xe3\x50\x87\x9d\xd1", fancy_password, b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000)) self.assertRaises(jks.util.BadDataLengthException, jks.rfc7292.decrypt_PBEWithSHAAnd3KeyTripleDESCBC, b"\x00", "", b"", 20) def test_decrypt_PBEWithSHAAndTwofishCBC(self): fancy_password = u"\u10DA\u0028\u0CA0\u76CA\u0CA0\u10DA\u0029" self.assertEqual(b"sample", jks.rfc7292.decrypt_PBEWithSHAAndTwofishCBC(b"\xc5\x22\x81\xc9\xa2\x24\x4b\x10\xf9\x1c\x6c\xbc\x67\x10\x42\x3e", "mypassword", b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000)) self.assertEqual(b"sample", jks.rfc7292.decrypt_PBEWithSHAAndTwofishCBC(b"\xc8\xc4\x7a\xe6\xa7\xc2\x80\xd7\x05\x5f\xe2\x4f\xf4\x20\x30\x7c", fancy_password, b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000)) self.assertEqual(b"-------16-------", jks.rfc7292.decrypt_PBEWithSHAAndTwofishCBC( b"\xf3\x4e\x3a\xd9\x3c\x48\x42\x53\xec\x07\xef\x00\x82\x56\x30\xee\x4f\xdf\x52\x0b\x5a\xd4\x8c\x9e\xa6\x72\x19\xe4\x90\x0b\xf1\x0c", "mypassword", b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000)) self.assertEqual(b"-------16-------", jks.rfc7292.decrypt_PBEWithSHAAndTwofishCBC( b"\xe0\xc7\x1a\xe8\xf4\x90\xca\x17\xa8\x0c\xc1\x1c\xea\x2e\x96\x38\x9d\x8d\xcc\xa4\x20\x15\x05\xa8\x57\xfa\x47\xa3\x0b\x97\xf5\x00", fancy_password, b"\x01\x02\x03\x04\x05\x06\x07\x08", 1000)) self.assertRaises(jks.util.BadDataLengthException, jks.rfc7292.decrypt_PBEWithSHAAndTwofishCBC, b"\x00", "", b"", 20) def test_filter_attributes(self): ks = jks.KeyStore("jks", {}) self.assertEqual(len(list(ks.private_keys)), 0) self.assertEqual(len(list(ks.secret_keys)), 0) self.assertEqual(len(list(ks.certs)), 0) dummy_entries = { "1": jks.SecretKeyEntry(), "2": jks.SecretKeyEntry(), "3": jks.SecretKeyEntry(), "4": jks.TrustedCertEntry(), "5": jks.TrustedCertEntry(), "6": jks.PrivateKeyEntry() } ks = jks.KeyStore("jks", dummy_entries) self.assertEqual(len(ks.private_keys), 1) self.assertEqual(len(ks.secret_keys), 3) self.assertEqual(len(ks.certs), 2) self.assertTrue(all(a in ks.secret_keys for a in ["1", "2", "3"])) self.assertTrue(all(a in ks.private_keys for a in ["6"])) self.assertTrue(all(a in ks.certs for a in ["4", "5"])) def test_try_decrypt_keys(self): # as applied to secret keys store = jks.KeyStore.load(KS_PATH + "/jceks/AES128.jceks", "12345678", try_decrypt_keys=False) sk = self.find_secret_key(store, "mykey") self.assertTrue(not sk.is_decrypted()) self.assertRaises(jks.NotYetDecryptedException, lambda: sk.key) self.assertRaises(jks.NotYetDecryptedException, lambda: sk.key_size) self.assertRaises(jks.NotYetDecryptedException, lambda: sk.algorithm) store = jks.KeyStore.load(KS_PATH + "/jceks/AES128.jceks", "12345678", try_decrypt_keys=True) sk = self.find_secret_key(store, "mykey") self.assertTrue(sk.is_decrypted()) dummy = sk.key dummy = sk.key_size dummy = sk.algorithm # as applied to private keys store = jks.KeyStore.load(KS_PATH + "/jceks/RSA1024.jceks", "12345678", try_decrypt_keys=False) pk = self.find_private_key(store, "mykey") self.assertTrue(not pk.is_decrypted()) self.assertRaises(jks.NotYetDecryptedException, lambda: pk.pkey) self.assertRaises(jks.NotYetDecryptedException, lambda: pk.pkey_pkcs8) self.assertRaises(jks.NotYetDecryptedException, lambda: pk.algorithm_oid) dummy = pk.cert_chain # not stored in encrypted form in the store, shouldn't require decryption to access store = jks.KeyStore.load(KS_PATH + "/jceks/RSA1024.jceks", "12345678", try_decrypt_keys=True) pk = self.find_private_key(store, "mykey") self.check_pkey_and_certs_equal(pk, jks.util.RSA_ENCRYPTION_OID, expected.RSA1024.private_key, expected.RSA1024.certs) dummy = pk.cert_chain if __name__ == "__main__": unittest.main() pyjks-20.0.0/tools/000077500000000000000000000000001364672547600141155ustar00rootroot00000000000000pyjks-20.0.0/tools/readks.py000077500000000000000000000103401364672547600157410ustar00rootroot00000000000000#!/usr/bin/env python # vim: set et ai ts=4 sts=4 sw=4: from __future__ import print_function import sys, base64, textwrap import logging import jks import datetime import base64 from jks.util import pkey_as_pem, as_pem, as_hex, print_pem from argparse import ArgumentParser def get_entry_metadata(entry): result = "Alias: %s\n" % entry.alias result += " Type: %s\n" % type(entry).__name__ result += " Timestamp: %s\n" % datetime.datetime.utcfromtimestamp(entry.timestamp//1000).strftime('%Y-%m-%dT%H:%M:%SZ') if entry.is_decrypted(): if isinstance(entry, jks.PrivateKeyEntry): result += " Algorithm OID: %s\n" % (entry.algorithm_oid,) result += " Certificate chain: %d certificate(s)\n" % len(entry.cert_chain) if isinstance(entry, jks.SecretKeyEntry): result += " Algorithm: %s\n" % (entry.algorithm,) result += " Key size: %d bits\n" % (entry.key_size,) if isinstance(entry, jks.BksKeyEntry) or \ isinstance(entry, jks.BksSealedKeyEntry): result += " Key type: %s\n" % jks.bks.BksKeyEntry.type2str(entry.type) result += " Key format: %s\n" % (entry.format,) result +=" Key algorithm: %s\n" % (entry.algorithm,) if entry.type in [jks.bks.KEY_TYPE_PRIVATE, jks.bks.KEY_TYPE_PUBLIC]: result += " Key algorithm OID: %s\n" % (entry.algorithm_oid,) elif entry.type == jks.bks.KEY_TYPE_SECRET: result += " Key size: %d bits\n" % (entry.key_size,) if isinstance(entry, jks.TrustedCertEntry) or \ isinstance(entry, jks.bks.TrustedCertEntry): result += " Certificate type: %s\n" % (entry.type,) else: result += " \n" return result def get_entry_bits(entry): if isinstance(entry, jks.PrivateKeyEntry): result = pkey_as_pem(entry) for c in entry.cert_chain: result += "\n" + as_pem(c[1], "CERTIFICATE") return result if isinstance(entry, jks.SecretKeyEntry): return base64.b64encode(entry.key) if isinstance(entry, jks.bks.BksKeyEntry) or \ isinstance(entry, jks.bks.BksSealedKeyEntry): if entry.type == jks.bks.KEY_TYPE_PRIVATE: result = pkey_as_pem(entry) for c in entry.cert_chain: result += "\n" + as_pem(c.cert, "CERTIFICATE") return result elif entry.type == jks.bks.KEY_TYPE_PUBLIC: return as_pem(entry.public_key_info, "PUBLIC KEY") elif entry.type == jks.bks.KEY_TYPE_SECRET: return base64.b64encode(entry.key) if isinstance(entry, jks.bks.BksSecretKeyEntry): return base64.b64encode(entry.key) if isinstance(entry, jks.TrustedCertEntry) or \ isinstance(entry, jks.bks.TrustedCertEntry): return as_pem(entry.cert, "CERTIFICATE") if __name__ == "__main__": parser = ArgumentParser(description="Utility for reading Java keystores.") parser.add_argument("keystore_file") parser.add_argument("keystore_password") parser.add_argument("--type", default="jks", choices=["jks", "jceks", "bks", "uber"], help="The type of input keystore. Defaults to 'jks'.") group = parser.add_mutually_exclusive_group() group.add_argument("-l", "--list", action="store_true", default=True, help="Print a list of entries/aliases in the keystore and some metadata about each one.") group.add_argument("-x", "--extract", metavar="ALIAS", dest="extract_alias", help="Extract the relevant key and/or certificates for the given alias and print them in the PEM format.") args = parser.parse_args() args.type = args.type.lower() ks_class = jks.KeyStore if args.type == "bks": ks_class = jks.BksKeyStore elif args.type == "uber": ks_class = jks.UberKeyStore ks = ks_class.load(args.keystore_file, args.keystore_password) if args.extract_alias: entry = ks.entries[args.extract_alias] if not entry.is_decrypted(): # call entry.decrypt("password") here raise Exception("Entry is still encrypted; password needed") print(get_entry_bits(entry)) elif args.list: for alias, entry in ks.entries.items(): print(get_entry_metadata(entry)) pyjks-20.0.0/tox.ini000066400000000000000000000006051364672547600142710ustar00rootroot00000000000000[tox] envlist = py26,py27,py34,py37,py38,pypy,packaging [testenv] changedir = .tox deps = -rrequirements-test.txt commands = py.test --doctest-modules {envsitepackagesdir}/jks {toxinidir}/tests [testenv:packaging] changedir = {toxinidir} deps = check-manifest==0.35 readme_renderer==17.2 commands = check-manifest python setup.py check --metadata --restructuredtext --strict