././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1614341428.941233 securesystemslib-0.20.0/0000755000076500000240000000000000000000000015275 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/LICENSE0000644000076500000240000000207200000000000016303 0ustar00lukpstaff00000000000000The MIT License (MIT) Copyright (c) 2016 Santiago Torres 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/MANIFEST.in0000644000076500000240000000055100000000000017034 0ustar00lukpstaff00000000000000include README.rst include LICENSE # Add test config files to show how to run tests include tox.ini include requirements*.txt # Include all files under the tests directory (including test data) graft tests # Exclude all files anywhere in the source tree matching any of these patterns global-exclude *.py[cod] .DS_Store .coverage */htmlcov/* */__pycache__/* ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.9394147 securesystemslib-0.20.0/PKG-INFO0000644000076500000240000004130500000000000016375 0ustar00lukpstaff00000000000000Metadata-Version: 2.1 Name: securesystemslib Version: 0.20.0 Summary: A library that provides cryptographic and general-purpose routines for Secure Systems Lab projects at NYU Home-page: https://github.com/secure-systems-lab/securesystemslib Author: https://www.updateframework.com Author-email: theupdateframework@googlegroups.com License: MIT Project-URL: Source, https://github.com/secure-systems-lab/securesystemslib Project-URL: Issues, https://github.com/secure-systems-lab/securesystemslib/issues Description: Secure Systems Library ---------------------- .. image:: https://github.com/secure-systems-lab/securesystemslib/workflows/Run%20Securesystemslib%20tests/badge.svg :target: https://github.com/secure-systems-lab/securesystemslib/actions?query=workflow%3A%22Run+Securesystemslib+tests%22+branch%3Amaster .. image:: https://api.dependabot.com/badges/status?host=github&repo=secure-systems-lab/securesystemslib :target: https://api.dependabot.com/badges/status?host=github&repo=secure-systems-lab/securesystemslib A library that provides cryptographic and general-purpose functions for Secure Systems Lab projects at NYU. The routines are general enough to be usable by other projects. Overview ++++++++ securesystemslib supports public-key and general-purpose cryptography, such as `ECDSA `_, `Ed25519 `_, `RSA `_, SHA256, SHA512, etc. Most of the cryptographic operations are performed by the `cryptography `_ and `PyNaCl `_ libraries, but verification of Ed25519 signatures can be done in pure Python. The `cryptography` library is used to generate keys and signatures with the ECDSA and RSA algorithms, and perform general-purpose cryptography such as encrypting keys. The PyNaCl library is used to generate Ed25519 keys and signatures. PyNaCl is a Python binding to the Networking and Cryptography Library. For key storage, RSA keys may be stored in PEM or JSON format, and Ed25519 keys in JSON format. Generating, importing, and loading cryptographic key files can be done with functions available in securesystemslib. securesystemslib also provides an interface to the `GNU Privacy Guard (GPG) `_ command line tool, with functions to create RSA and DSA signatures using private keys in a local gpg keychain; to export the corresponding public keys in a *pythonic* format; and to verify the created signatures using the exported keys. The latter does not require the gpg command line tool to be installed, instead the `cryptography` library is used. Installation ++++++++++++ :: $ pip install securesystemslib The default installation only supports Ed25519 keys and signatures (in pure Python). Support for RSA, ECDSA, and E25519 via the `cryptography` and `PyNaCl` libraries is available by installing the `crypto` and `pynacl` extras: :: $ pip install securesystemslib[crypto] $ pip install securesystemslib[pynacl] Usage ++++++++++++ Create RSA Keys ~~~~~~~~~~~~~~~ Note: In the instructions below, lines that start with *>>>* denote commands that should be entered by the reader, *#* begins the start of a comment, and text without prepended symbols is the output of a command. :: >>> from securesystemslib.interface import * # The following function creates an RSA key pair, where the private key is # saved to "rsa_key1" and the public key to "rsa_key1.pub" (both saved to # the current working directory). A full directory path may be specified # instead of saving keys to the current working directory. If specified # directories do not exist, they will be created. >>> generate_and_write_rsa_keypair( password="password", filepath="rsa_key1", bits=2048) # If the key length is unspecified, it defaults to 3072 bits. A length of # less than 2048 bits raises an exception. A similar function is available # to supply a password on the prompt. If an empty password is entered, the # private key is saved unencrypted. >>> generate_and_write_rsa_keypair_with_prompt("rsa_key2") enter password to encrypt private key file '/path/to/rsa_key2' (leave empty if key should not be encrypted): Confirm: The following four key files should now exist: 1. rsa_key1 2. rsa_key1.pub 3. rsa_key2 4. rsa_key2.pub Import RSA Keys ~~~~~~~~~~~~~~~ :: # Continuing from the previous section . . . # Import an existing public key. >>> public_rsa_key1 = import_rsa_publickey_from_file("rsa_key1.pub") # Import an existing private key. If your private key is encrypted, # which it should be, you either have to pass a 'password' or enter one # on the prompt. >>> private_rsa_key1 = import_rsa_privatekey_from_file("rsa_key1", password="some passphrase") # OR: >>> private_rsa_key1 = import_rsa_privatekey_from_file("rsa_key1", prompt=True) enter password to decrypt private key file '/path/to/rsa_key1' (leave empty if key not encrypted): **import_rsa_privatekey_from_file()** raises a *securesystemslib.exceptions.CryptoError* exception if the key / password is invalid: :: securesystemslib.exceptions.CryptoError: RSA (public, private) tuple cannot be generated from the encrypted PEM string: Bad decrypt. Incorrect password? Note: The specific message provided by the exception might differ depending on which cryptography library is used. Create and Import Ed25519 Keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # Continuing from the previous section . . . # The same generation and import functions as for rsa keys exist for ed25519 >>> generate_and_write_ed25519_keypair_with_prompt('ed25519_key') enter password to encrypt private key file '/path/to/ed25519_key' (leave empty if key should not be encrypted): Confirm: # Import the Ed25519 public key just created . . . >>> public_ed25519_key = import_ed25519_publickey_from_file('ed25519_key.pub') # and its corresponding private key. >>> private_ed25519_key = import_ed25519_privatekey_from_file('ed25519_key', prompt=True) enter password to decrypt private key file '/path/to/ed25519_key' (leave empty if key should not be encrypted): Create and Import ECDSA Keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # The same generation and import functions as for rsa and ed25519 keys # exist for ecdsa >>> generate_and_write_ecdsa_keypair_with_prompt('ecdsa_key') enter password to decrypt private key file '/path/to/ecdsa_key' (leave empty if key should not be encrypted): >>> public_ecdsa_key = import_ecdsa_publickey_from_file('ecdsa_key.pub') >>> private_ecdsa_key = import_ecdsa_privatekey_from_file('ecdsa_key', prompt=True) enter password to decrypt private key file '/path/to/ecdsa_key' (leave empty if key should not be encrypted): Generate ECDSA, Ed25519, and RSA Signatures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note: Users may also access the crypto functions directly to perform cryptographic operations. :: >>> from securesystemslib.keys import * >>> data = b'The quick brown fox jumps over the lazy dog' >>> ed25519_key = generate_ed25519_key() >>> signature = create_signature(ed25519_key, data) >>> rsa_key = generate_rsa_key(2048) >>> signature = create_signature(rsa_key, data) >>> ecdsa_key = generate_ecdsa_key() >>> signature = create_signature(ecdsa_key, data) Verify ECDSA, Ed25519, and RSA Signatures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # Continuing from the previous sections . . . >>> data = b'The quick brown fox jumps over the lazy dog' >>> ed25519_key = generate_ed25519_key() >>> signature = create_signature(ed25519_key, data) >>> verify_signature(ed25519_key, signature, data) True >>> verify_signature(ed25519_key, signature, 'bad_data') False >>> rsa_key = generate_rsa_key() >>> signature = create_signature(rsa_key, data) >>> verify_signature(rsa_key, signature, data) True >>> ecdsa_key = generate_ecdsa_key() >>> signature = create_signature(ecdsa_key, data) >>> verify_signature(ecdsa_key, signature, data) True Miscellaneous functions ~~~~~~~~~~~~~~~~~~~~~~~ **create_rsa_encrypted_pem()** :: # Continuing from the previous sections . . . >>> rsa_key = generate_rsa_key() >>> private = rsa_key['keyval']['private'] >>> passphrase = 'secret' >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) **import_rsakey_from_public_pem()** :: >>> rsa_key = generate_rsa_key() >>> public = rsa_key['keyval']['public'] >>> rsa_key2 = import_rsakey_from_public_pem(public) **import_rsakey_from_pem()** :: >>> rsa_key = generate_rsa_key() >>> public = rsa_key['keyval']['public'] >>> private = rsa_key['keyval']['private'] >>> rsa_key2 = import_rsakey_from_pem(public) >>> rsa_key3 = import_rsakey_from_pem(private) **extract_pem()** :: >>> rsa_key = generate_rsa_key() >>> private_pem = extract_pem(rsakey['keyval']['private'], private_pem=True) >>> public_pem = extract_pem(rsakey['keyval']['public'], private_pem=False) **encrypt_key()** :: >>> ed25519_key = generate_ed25519_key() >>> password = 'secret' >>> encrypted_key = encrypt_key(ed25519_key, password) **decrypt_key()** :: >>> ed25519_key = generate_ed25519_key() >>> password = 'secret' >>> encrypted_key = encrypt_key(ed25519_key, password) >>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), password) >>> decrypted_key == ed25519_key True **create_rsa_encrypted_pem()** :: >>> rsa_key = generate_rsa_key() >>> private = rsa_key['keyval']['private'] >>> passphrase = 'secret' >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) **is_pem_public()** :: >>> rsa_key = generate_rsa_key() >>> public = rsa_key['keyval']['public'] >>> private = rsa_key['keyval']['private'] >>> is_pem_public(public) True >>> is_pem_public(private) False **is_pem_private()** :: >>> rsa_key = generate_rsa_key() >>> private = rsa_key['keyval']['private'] >>> public = rsa_key['keyval']['public'] >>> is_pem_private(private) True >>> is_pem_private(public) False **import_ecdsakey_from_private_pem()** :: >>> ecdsa_key = generate_ecdsa_key() >>> private_pem = ecdsa_key['keyval']['private'] >>> ecdsa_key2 = import_ecdsakey_from_private_pem(private_pem) **import_ecdsakey_from_public_pem()** :: >>> ecdsa_key = generate_ecdsa_key() >>> public = ecdsa_key['keyval']['public'] >>> ecdsa_key2 = import_ecdsakey_from_public_pem(public) **import_ecdsakey_from_pem()** :: >>> ecdsa_key = generate_ecdsa_key() >>> private_pem = ecdsa_key['keyval']['private'] >>> ecdsa_key2 = import_ecdsakey_from_pem(private_pem) >>> public_pem = ecdsa_key['keyval']['public'] >>> ecdsa_key2 = import_ecdsakey_from_pem(public_pem) GnuPG interface ~~~~~~~~~~~~~~~ Signature creation and public key export requires installation of the `gpg` or `gpg2` command line tool, which may be downloaded from `https://gnupg.org/download `_. It is also needed to generate the supported RSA or DSA signing keys (see `gpg` man pages for detailed instructions). Sample keys are available in a test keyring at `tests/gpg_keyrings/rsa`, which may be passed to the signing and export functions using the `homedir` argument (if not passed the default keyring is used). The GPG client to use can be also specified with the help of environment variable `GNUPG`. :: >>> import securesystemslib.gpg.functions as gpg >>> data = b"The quick brown fox jumps over the lazy dog" >>> signing_key_id = "8465A1E2E0FB2B40ADB2478E18FB3F537E0C8A17" >>> keyring = "tests/gpg_keyrings/rsa" >>> signature = gpg.create_signature(data, signing_key_id, homedir=keyring) >>> public_key = gpg.export_pubkey(non_default_signing_key, homedir=keyring) >>> gpg.verify_signature(signature, public_key, data) True Testing ++++++++++++ Testing is done with `tox `_, which can be installed with pip: :: $ pip install tox Secure Systems Library supports multiple versions of Python. For that reason, the project is tested against multiple virtual environments with different Python versions. If you run :: $ tox this will run all tests creating virtual environments for all python versions described in the *tox.ini* file. If you want to run the tests against specific python version, for example Python 3.7, you will use: :: $ tox -e py37 Keywords: cryptography,keys,signatures,rsa,ed25519,ecdsa Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Security Classifier: Topic :: Software Development Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4 Description-Content-Type: text/x-rst Provides-Extra: colors Provides-Extra: crypto Provides-Extra: pynacl ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/README.rst0000644000076500000240000003000700000000000016764 0ustar00lukpstaff00000000000000Secure Systems Library ---------------------- .. image:: https://github.com/secure-systems-lab/securesystemslib/workflows/Run%20Securesystemslib%20tests/badge.svg :target: https://github.com/secure-systems-lab/securesystemslib/actions?query=workflow%3A%22Run+Securesystemslib+tests%22+branch%3Amaster .. image:: https://api.dependabot.com/badges/status?host=github&repo=secure-systems-lab/securesystemslib :target: https://api.dependabot.com/badges/status?host=github&repo=secure-systems-lab/securesystemslib A library that provides cryptographic and general-purpose functions for Secure Systems Lab projects at NYU. The routines are general enough to be usable by other projects. Overview ++++++++ securesystemslib supports public-key and general-purpose cryptography, such as `ECDSA `_, `Ed25519 `_, `RSA `_, SHA256, SHA512, etc. Most of the cryptographic operations are performed by the `cryptography `_ and `PyNaCl `_ libraries, but verification of Ed25519 signatures can be done in pure Python. The `cryptography` library is used to generate keys and signatures with the ECDSA and RSA algorithms, and perform general-purpose cryptography such as encrypting keys. The PyNaCl library is used to generate Ed25519 keys and signatures. PyNaCl is a Python binding to the Networking and Cryptography Library. For key storage, RSA keys may be stored in PEM or JSON format, and Ed25519 keys in JSON format. Generating, importing, and loading cryptographic key files can be done with functions available in securesystemslib. securesystemslib also provides an interface to the `GNU Privacy Guard (GPG) `_ command line tool, with functions to create RSA and DSA signatures using private keys in a local gpg keychain; to export the corresponding public keys in a *pythonic* format; and to verify the created signatures using the exported keys. The latter does not require the gpg command line tool to be installed, instead the `cryptography` library is used. Installation ++++++++++++ :: $ pip install securesystemslib The default installation only supports Ed25519 keys and signatures (in pure Python). Support for RSA, ECDSA, and E25519 via the `cryptography` and `PyNaCl` libraries is available by installing the `crypto` and `pynacl` extras: :: $ pip install securesystemslib[crypto] $ pip install securesystemslib[pynacl] Usage ++++++++++++ Create RSA Keys ~~~~~~~~~~~~~~~ Note: In the instructions below, lines that start with *>>>* denote commands that should be entered by the reader, *#* begins the start of a comment, and text without prepended symbols is the output of a command. :: >>> from securesystemslib.interface import * # The following function creates an RSA key pair, where the private key is # saved to "rsa_key1" and the public key to "rsa_key1.pub" (both saved to # the current working directory). A full directory path may be specified # instead of saving keys to the current working directory. If specified # directories do not exist, they will be created. >>> generate_and_write_rsa_keypair( password="password", filepath="rsa_key1", bits=2048) # If the key length is unspecified, it defaults to 3072 bits. A length of # less than 2048 bits raises an exception. A similar function is available # to supply a password on the prompt. If an empty password is entered, the # private key is saved unencrypted. >>> generate_and_write_rsa_keypair_with_prompt("rsa_key2") enter password to encrypt private key file '/path/to/rsa_key2' (leave empty if key should not be encrypted): Confirm: The following four key files should now exist: 1. rsa_key1 2. rsa_key1.pub 3. rsa_key2 4. rsa_key2.pub Import RSA Keys ~~~~~~~~~~~~~~~ :: # Continuing from the previous section . . . # Import an existing public key. >>> public_rsa_key1 = import_rsa_publickey_from_file("rsa_key1.pub") # Import an existing private key. If your private key is encrypted, # which it should be, you either have to pass a 'password' or enter one # on the prompt. >>> private_rsa_key1 = import_rsa_privatekey_from_file("rsa_key1", password="some passphrase") # OR: >>> private_rsa_key1 = import_rsa_privatekey_from_file("rsa_key1", prompt=True) enter password to decrypt private key file '/path/to/rsa_key1' (leave empty if key not encrypted): **import_rsa_privatekey_from_file()** raises a *securesystemslib.exceptions.CryptoError* exception if the key / password is invalid: :: securesystemslib.exceptions.CryptoError: RSA (public, private) tuple cannot be generated from the encrypted PEM string: Bad decrypt. Incorrect password? Note: The specific message provided by the exception might differ depending on which cryptography library is used. Create and Import Ed25519 Keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # Continuing from the previous section . . . # The same generation and import functions as for rsa keys exist for ed25519 >>> generate_and_write_ed25519_keypair_with_prompt('ed25519_key') enter password to encrypt private key file '/path/to/ed25519_key' (leave empty if key should not be encrypted): Confirm: # Import the Ed25519 public key just created . . . >>> public_ed25519_key = import_ed25519_publickey_from_file('ed25519_key.pub') # and its corresponding private key. >>> private_ed25519_key = import_ed25519_privatekey_from_file('ed25519_key', prompt=True) enter password to decrypt private key file '/path/to/ed25519_key' (leave empty if key should not be encrypted): Create and Import ECDSA Keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # The same generation and import functions as for rsa and ed25519 keys # exist for ecdsa >>> generate_and_write_ecdsa_keypair_with_prompt('ecdsa_key') enter password to decrypt private key file '/path/to/ecdsa_key' (leave empty if key should not be encrypted): >>> public_ecdsa_key = import_ecdsa_publickey_from_file('ecdsa_key.pub') >>> private_ecdsa_key = import_ecdsa_privatekey_from_file('ecdsa_key', prompt=True) enter password to decrypt private key file '/path/to/ecdsa_key' (leave empty if key should not be encrypted): Generate ECDSA, Ed25519, and RSA Signatures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note: Users may also access the crypto functions directly to perform cryptographic operations. :: >>> from securesystemslib.keys import * >>> data = b'The quick brown fox jumps over the lazy dog' >>> ed25519_key = generate_ed25519_key() >>> signature = create_signature(ed25519_key, data) >>> rsa_key = generate_rsa_key(2048) >>> signature = create_signature(rsa_key, data) >>> ecdsa_key = generate_ecdsa_key() >>> signature = create_signature(ecdsa_key, data) Verify ECDSA, Ed25519, and RSA Signatures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # Continuing from the previous sections . . . >>> data = b'The quick brown fox jumps over the lazy dog' >>> ed25519_key = generate_ed25519_key() >>> signature = create_signature(ed25519_key, data) >>> verify_signature(ed25519_key, signature, data) True >>> verify_signature(ed25519_key, signature, 'bad_data') False >>> rsa_key = generate_rsa_key() >>> signature = create_signature(rsa_key, data) >>> verify_signature(rsa_key, signature, data) True >>> ecdsa_key = generate_ecdsa_key() >>> signature = create_signature(ecdsa_key, data) >>> verify_signature(ecdsa_key, signature, data) True Miscellaneous functions ~~~~~~~~~~~~~~~~~~~~~~~ **create_rsa_encrypted_pem()** :: # Continuing from the previous sections . . . >>> rsa_key = generate_rsa_key() >>> private = rsa_key['keyval']['private'] >>> passphrase = 'secret' >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) **import_rsakey_from_public_pem()** :: >>> rsa_key = generate_rsa_key() >>> public = rsa_key['keyval']['public'] >>> rsa_key2 = import_rsakey_from_public_pem(public) **import_rsakey_from_pem()** :: >>> rsa_key = generate_rsa_key() >>> public = rsa_key['keyval']['public'] >>> private = rsa_key['keyval']['private'] >>> rsa_key2 = import_rsakey_from_pem(public) >>> rsa_key3 = import_rsakey_from_pem(private) **extract_pem()** :: >>> rsa_key = generate_rsa_key() >>> private_pem = extract_pem(rsakey['keyval']['private'], private_pem=True) >>> public_pem = extract_pem(rsakey['keyval']['public'], private_pem=False) **encrypt_key()** :: >>> ed25519_key = generate_ed25519_key() >>> password = 'secret' >>> encrypted_key = encrypt_key(ed25519_key, password) **decrypt_key()** :: >>> ed25519_key = generate_ed25519_key() >>> password = 'secret' >>> encrypted_key = encrypt_key(ed25519_key, password) >>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), password) >>> decrypted_key == ed25519_key True **create_rsa_encrypted_pem()** :: >>> rsa_key = generate_rsa_key() >>> private = rsa_key['keyval']['private'] >>> passphrase = 'secret' >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) **is_pem_public()** :: >>> rsa_key = generate_rsa_key() >>> public = rsa_key['keyval']['public'] >>> private = rsa_key['keyval']['private'] >>> is_pem_public(public) True >>> is_pem_public(private) False **is_pem_private()** :: >>> rsa_key = generate_rsa_key() >>> private = rsa_key['keyval']['private'] >>> public = rsa_key['keyval']['public'] >>> is_pem_private(private) True >>> is_pem_private(public) False **import_ecdsakey_from_private_pem()** :: >>> ecdsa_key = generate_ecdsa_key() >>> private_pem = ecdsa_key['keyval']['private'] >>> ecdsa_key2 = import_ecdsakey_from_private_pem(private_pem) **import_ecdsakey_from_public_pem()** :: >>> ecdsa_key = generate_ecdsa_key() >>> public = ecdsa_key['keyval']['public'] >>> ecdsa_key2 = import_ecdsakey_from_public_pem(public) **import_ecdsakey_from_pem()** :: >>> ecdsa_key = generate_ecdsa_key() >>> private_pem = ecdsa_key['keyval']['private'] >>> ecdsa_key2 = import_ecdsakey_from_pem(private_pem) >>> public_pem = ecdsa_key['keyval']['public'] >>> ecdsa_key2 = import_ecdsakey_from_pem(public_pem) GnuPG interface ~~~~~~~~~~~~~~~ Signature creation and public key export requires installation of the `gpg` or `gpg2` command line tool, which may be downloaded from `https://gnupg.org/download `_. It is also needed to generate the supported RSA or DSA signing keys (see `gpg` man pages for detailed instructions). Sample keys are available in a test keyring at `tests/gpg_keyrings/rsa`, which may be passed to the signing and export functions using the `homedir` argument (if not passed the default keyring is used). The GPG client to use can be also specified with the help of environment variable `GNUPG`. :: >>> import securesystemslib.gpg.functions as gpg >>> data = b"The quick brown fox jumps over the lazy dog" >>> signing_key_id = "8465A1E2E0FB2B40ADB2478E18FB3F537E0C8A17" >>> keyring = "tests/gpg_keyrings/rsa" >>> signature = gpg.create_signature(data, signing_key_id, homedir=keyring) >>> public_key = gpg.export_pubkey(non_default_signing_key, homedir=keyring) >>> gpg.verify_signature(signature, public_key, data) True Testing ++++++++++++ Testing is done with `tox `_, which can be installed with pip: :: $ pip install tox Secure Systems Library supports multiple versions of Python. For that reason, the project is tested against multiple virtual environments with different Python versions. If you run :: $ tox this will run all tests creating virtual environments for all python versions described in the *tox.ini* file. If you want to run the tests against specific python version, for example Python 3.7, you will use: :: $ tox -e py37 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/requirements-dev.txt0000644000076500000240000000035200000000000021335 0ustar00lukpstaff00000000000000# Install securesystemslib in editable mode with all runtime and test # requirements for local testing with tox, and also for the running test suite # or individual tests manually tox -r requirements.txt -r requirements-test.txt -e . ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/requirements-min.txt0000644000076500000240000000015300000000000021341 0ustar00lukpstaff00000000000000# Minimal runtime requirements (see 'install_requires' in setup.py) six subprocess32; python_version < '3' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/requirements-pinned.txt0000644000076500000240000000071000000000000022032 0ustar00lukpstaff00000000000000cffi==1.14.5 # via cryptography, pynacl colorama==0.4.4 cryptography==3.3.2 ; python_version < "3" # cryptography < 3.4 for python2 compat cryptography==3.4.6 ; python_version >= "3" enum34==1.1.10 ; python_version < "3" # via cryptography ipaddress==1.0.23 ; python_version < "3" # via cryptography pycparser==2.20 # via cffi pynacl==1.4.0 six==1.15.0 subprocess32==3.5.4 ; python_version < "3" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/requirements-test.txt0000644000076500000240000000020400000000000021532 0ustar00lukpstaff00000000000000# test runtime dependencies (see 'tests_require' field in setup.py) mock; python_version < "3.3" # additional test tools coverage ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/requirements.txt0000644000076500000240000000257000000000000020565 0ustar00lukpstaff00000000000000# All runtime requirements including extras (see 'install_requires' and # 'extras_require' in setup.py) # # This file together with 'pip-compile' is used to generate a pinned # requirements file with all immediate and transitive dependencies. # # 'requirements-pinned.txt' is updated on GitHub with Dependabot, which # triggers CI/CD builds to automatically test against updated dependencies. # # Below instructions can be used to re-generate 'requirements-pinned.txt', e.g. # if: # - requirements are added or removed from this file # - Python version support is changed # - CI/CD build breaks due to updates (e.g. transitive dependency conflicts) # # 1. Use this script to create a pinned requirements file for each Python # version # ``` # for v in 2.7 3.6 3.7 3.8 3.9; do # mkvirtualenv sslib-env-${v} -p python${v}; # pip install pip-tools; # pip-compile --no-header -o requirements-${v}.txt requirements.txt; # deactivate; # rmvirtualenv sslib-env-${v}; # done; # # ``` # 2. Use this command to merge per-version files # `sort -o requirements-pinned.txt -u requirements-?.?.txt` # 2. Manually add environment markers to requirements-pinned.txt # 3. Use this command to remove per-version files # `rm requirements-?.?.txt` # cryptography >= 3.3.2; python_version >= '3' cryptography >= 3.3.2, < 3.4; python_version < '3' pynacl colorama six subprocess32; python_version < '3' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.8736975 securesystemslib-0.20.0/securesystemslib/0000755000076500000240000000000000000000000020702 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/__init__.py0000755000076500000240000000136500000000000023023 0ustar00lukpstaff00000000000000import logging # Configure a basic 'securesystemslib' top-level logger with a StreamHandler # (print to console) and the WARNING log level (print messages of type # warning, error or critical). This is similar to what 'logging.basicConfig' # would do with the root logger. All 'securesystemslib.*' loggers default to # this top-level logger and thus may be configured (e.g. formatted, silenced, # etc.) with it. It can be accessed via logging.getLogger('securesystemslib'). logger = logging.getLogger(__name__) logger.setLevel(logging.WARNING) logger.addHandler(logging.StreamHandler()) # Global constants # TODO: Replace hard-coded key types with these constants (and add more) KEY_TYPE_RSA = "rsa" KEY_TYPE_ED25519 = "ed25519" KEY_TYPE_ECDSA = "ecdsa" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.8792474 securesystemslib-0.20.0/securesystemslib/_vendor/0000755000076500000240000000000000000000000022336 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/_vendor/__init__.py0000755000076500000240000000000000000000000024440 0ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.8814907 securesystemslib-0.20.0/securesystemslib/_vendor/ed25519/0000755000076500000240000000000000000000000023334 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/_vendor/ed25519/__init__.py0000644000076500000240000000000000000000000025433 0ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/_vendor/ed25519/ed25519.py0000644000076500000240000001702500000000000024711 0ustar00lukpstaff00000000000000# ed25519.py - Optimized version of the reference implementation of Ed25519 # # Written in 2011? by Daniel J. Bernstein # 2013 by Donald Stufft # 2013 by Alex Gaynor # 2013 by Greg Price # # To the extent possible under law, the author(s) have dedicated all copyright # and related and neighboring rights to this software to the public domain # worldwide. This software is distributed without any warranty. # # You should have received a copy of the CC0 Public Domain Dedication along # with this software. If not, see # . """ NB: This code is not safe for use with secret keys or secret data. The only safe use of this code is for verifying signatures on public messages. Functions for computing the public key of a secret key and for signing a message are included, namely publickey_unsafe and signature_unsafe, for testing purposes only. The root of the problem is that Python's long-integer arithmetic is not designed for use in cryptography. Specifically, it may take more or less time to execute an operation depending on the values of the inputs, and its memory access patterns may also depend on the inputs. This opens it to timing and cache side-channel attacks which can disclose data to an attacker. We rely on Python's long-integer arithmetic, so we cannot handle secrets without risking their disclosure. """ import hashlib import operator import sys __version__ = "1.0.dev0" # Useful for very coarse version differentiation. PY3 = sys.version_info[0] == 3 if PY3: indexbytes = operator.getitem intlist2bytes = bytes int2byte = operator.methodcaller("to_bytes", 1, "big") else: int2byte = chr range = xrange # noqa: F821 def indexbytes(buf, i): return ord(buf[i]) def intlist2bytes(l): return b"".join(chr(c) for c in l) b = 256 q = 2 ** 255 - 19 l = 2 ** 252 + 27742317777372353535851937790883648493 def H(m): return hashlib.sha512(m).digest() def pow2(x, p): """== pow(x, 2**p, q)""" while p > 0: x = x * x % q p -= 1 return x def inv(z): r"""$= z^{-1} \mod q$, for z != 0""" # Adapted from curve25519_athlon.c in djb's Curve25519. z2 = z * z % q # 2 z9 = pow2(z2, 2) * z % q # 9 z11 = z9 * z2 % q # 11 z2_5_0 = (z11 * z11) % q * z9 % q # 31 == 2^5 - 2^0 z2_10_0 = pow2(z2_5_0, 5) * z2_5_0 % q # 2^10 - 2^0 z2_20_0 = pow2(z2_10_0, 10) * z2_10_0 % q # ... z2_40_0 = pow2(z2_20_0, 20) * z2_20_0 % q z2_50_0 = pow2(z2_40_0, 10) * z2_10_0 % q z2_100_0 = pow2(z2_50_0, 50) * z2_50_0 % q z2_200_0 = pow2(z2_100_0, 100) * z2_100_0 % q z2_250_0 = pow2(z2_200_0, 50) * z2_50_0 % q # 2^250 - 2^0 return pow2(z2_250_0, 5) * z11 % q # 2^255 - 2^5 + 11 = q - 2 d = -121665 * inv(121666) % q I = pow(2, (q - 1) // 4, q) def xrecover(y): xx = (y * y - 1) * inv(d * y * y + 1) x = pow(xx, (q + 3) // 8, q) if (x * x - xx) % q != 0: x = (x * I) % q if x % 2 != 0: x = q - x return x By = 4 * inv(5) Bx = xrecover(By) B = (Bx % q, By % q, 1, (Bx * By) % q) ident = (0, 1, 1, 0) def edwards_add(P, Q): # This is formula sequence 'addition-add-2008-hwcd-3' from # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html (x1, y1, z1, t1) = P (x2, y2, z2, t2) = Q a = (y1 - x1) * (y2 - x2) % q b = (y1 + x1) * (y2 + x2) % q c = t1 * 2 * d * t2 % q dd = z1 * 2 * z2 % q e = b - a f = dd - c g = dd + c h = b + a x3 = e * f y3 = g * h t3 = e * h z3 = f * g return (x3 % q, y3 % q, z3 % q, t3 % q) def edwards_double(P): # This is formula sequence 'dbl-2008-hwcd' from # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html (x1, y1, z1, t1) = P a = x1 * x1 % q b = y1 * y1 % q c = 2 * z1 * z1 % q # dd = -a e = ((x1 + y1) * (x1 + y1) - a - b) % q g = -a + b # dd + b f = g - c h = -a - b # dd - b x3 = e * f y3 = g * h t3 = e * h z3 = f * g return (x3 % q, y3 % q, z3 % q, t3 % q) def scalarmult(P, e): if e == 0: return ident Q = scalarmult(P, e // 2) Q = edwards_double(Q) if e & 1: Q = edwards_add(Q, P) return Q # Bpow[i] == scalarmult(B, 2**i) Bpow = [] def make_Bpow(): P = B for i in range(253): Bpow.append(P) P = edwards_double(P) make_Bpow() def scalarmult_B(e): """ Implements scalarmult(B, e) more efficiently. """ # scalarmult(B, l) is the identity e = e % l P = ident for i in range(253): if e & 1: P = edwards_add(P, Bpow[i]) e = e // 2 assert e == 0, e return P def encodeint(y): bits = [(y >> i) & 1 for i in range(b)] return b"".join( [ int2byte(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b // 8) ] ) def encodepoint(P): (x, y, z, t) = P zi = inv(z) x = (x * zi) % q y = (y * zi) % q bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1] return b"".join( [ int2byte(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b // 8) ] ) def bit(h, i): return (indexbytes(h, i // 8) >> (i % 8)) & 1 def publickey_unsafe(sk): """ Not safe to use with secret keys or secret data. See module docstring. This function should be used for testing only. """ h = H(sk) a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) A = scalarmult_B(a) return encodepoint(A) def Hint(m): h = H(m) return sum(2 ** i * bit(h, i) for i in range(2 * b)) def signature_unsafe(m, sk, pk): """ Not safe to use with secret keys or secret data. See module docstring. This function should be used for testing only. """ h = H(sk) a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) r = Hint( intlist2bytes([indexbytes(h, j) for j in range(b // 8, b // 4)]) + m ) R = scalarmult_B(r) S = (r + Hint(encodepoint(R) + pk + m) * a) % l return encodepoint(R) + encodeint(S) def isoncurve(P): (x, y, z, t) = P return ( z % q != 0 and x * y % q == z * t % q and (y * y - x * x - z * z - d * t * t) % q == 0 ) def decodeint(s): return sum(2 ** i * bit(s, i) for i in range(0, b)) def decodepoint(s): y = sum(2 ** i * bit(s, i) for i in range(0, b - 1)) x = xrecover(y) if x & 1 != bit(s, b - 1): x = q - x P = (x, y, 1, (x * y) % q) if not isoncurve(P): raise ValueError("decoding point that is not on curve") return P class SignatureMismatch(Exception): pass def checkvalid(s, m, pk): """ Not safe to use when any argument is secret. See module docstring. This function should be used only for verifying public signatures of public messages. """ if len(s) != b // 4: raise ValueError("signature length is wrong") if len(pk) != b // 8: raise ValueError("public-key length is wrong") R = decodepoint(s[: b // 8]) A = decodepoint(pk) S = decodeint(s[b // 8 : b // 4]) h = Hint(encodepoint(R) + pk + m) (x1, y1, z1, t1) = P = scalarmult_B(S) (x2, y2, z2, t2) = Q = edwards_add(R, scalarmult(A, h)) if ( not isoncurve(P) or not isoncurve(Q) or (x1 * z2 - x2 * z1) % q != 0 or (y1 * z2 - y2 * z1) % q != 0 ): raise SignatureMismatch("signature does not pass verification") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/_vendor/ed25519/science.py0000644000076500000240000000220100000000000025312 0ustar00lukpstaff00000000000000# ed25519.py - Optimized version of the reference implementation of Ed25519 # # Written in 2011? by Daniel J. Bernstein # 2013 by Donald Stufft # 2013 by Alex Gaynor # 2013 by Greg Price # # To the extent possible under law, the author(s) have dedicated all copyright # and related and neighboring rights to this software to the public domain # worldwide. This software is distributed without any warranty. # # You should have received a copy of the CC0 Public Domain Dedication along # with this software. If not, see # . import os import timeit import ed25519 seed = os.urandom(32) data = b"The quick brown fox jumps over the lazy dog" private_key = seed public_key = ed25519.publickey_unsafe(seed) signature = ed25519.signature_unsafe(data, private_key, public_key) print("\nTime verify signature") print( timeit.timeit( "ed25519.checkvalid(signature, data, public_key)", setup="from __main__ import ed25519, signature, data, public_key", number=100, ) ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/_vendor/ed25519/test_ed25519.py0000644000076500000240000000751700000000000025755 0ustar00lukpstaff00000000000000# ed25519.py - Optimized version of the reference implementation of Ed25519 # # Written in 2011? by Daniel J. Bernstein # 2013 by Donald Stufft # 2013 by Alex Gaynor # 2013 by Greg Price # # To the extent possible under law, the author(s) have dedicated all copyright # and related and neighboring rights to this software to the public domain # worldwide. This software is distributed without any warranty. # # You should have received a copy of the CC0 Public Domain Dedication along # with this software. If not, see # . import binascii import codecs import os import pytest import ed25519 def ed25519_known_answers(): # Known answers taken from: http://ed25519.cr.yp.to/python/sign.input # File Format is a lined based file where each line has sk, pk, m, sm # - Each field hex # - Each field colon-terminated # - sk includes pk at end # - sm includes m at end path = os.path.join(os.path.dirname(__file__), "test_data", "ed25519") with codecs.open(path, "r", encoding="utf-8") as fp: for line in fp: x = line.split(":") yield ( # Secret Key # Secret key is 32 bytes long, or 64 hex characters and has # public key appended to it x[0][0:64].encode("ascii"), # Public Key x[1].encode("ascii"), # Message x[2].encode("ascii"), # Signed Message x[3].encode("ascii"), # Signature Only # Signature comes from the Signed Message, it is 32 bytes long # and has the message appended to it binascii.hexlify( binascii.unhexlify(x[3].encode("ascii"))[:64] ), ) @pytest.mark.parametrize( ("secret_key", "public_key", "message", "signed", "signature"), ed25519_known_answers(), ) def test_ed25519_kat(secret_key, public_key, message, signed, signature): sk = binascii.unhexlify(secret_key) m = binascii.unhexlify(message) pk = ed25519.publickey_unsafe(sk) sig = ed25519.signature_unsafe(m, sk, pk) # Assert that the signature and public key are what we expected assert binascii.hexlify(pk) == public_key assert binascii.hexlify(sig) == signature # Validate the signature using the checkvalid routine ed25519.checkvalid(sig, m, pk) # Assert that we cannot forge a message try: if len(m) == 0: forgedm = b"x" else: forgedm = ed25519.intlist2bytes( [ ed25519.indexbytes(m, i) + (i == len(m) - 1) for i in range(len(m)) ] ) except ValueError: # TODO: Yes this means that we "pass" a test if we can't generate a # forged message. This matches the original test suite, it's # unclear if it was intentional there or not. pass else: with pytest.raises(ed25519.SignatureMismatch): ed25519.checkvalid(sig, forgedm, pk) def test_checkparams(): # Taken from checkparams.py from DJB assert ed25519.b >= 10 assert 8 * len(ed25519.H(b"hash input")) == 2 * ed25519.b assert pow(2, ed25519.q - 1, ed25519.q) == 1 assert ed25519.q % 4 == 1 assert pow(2, ed25519.l - 1, ed25519.l) == 1 assert ed25519.l >= 2 ** (ed25519.b - 4) assert ed25519.l <= 2 ** (ed25519.b - 3) assert pow(ed25519.d, (ed25519.q - 1) // 2, ed25519.q) == ed25519.q - 1 assert pow(ed25519.I, 2, ed25519.q) == ed25519.q - 1 assert ed25519.isoncurve(ed25519.B) x, y, z, t = P = ed25519.scalarmult(ed25519.B, ed25519.l) assert ed25519.isoncurve(P) assert (x, y) == (0, z) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/_vendor/ssl_match_hostname.py0000644000076500000240000000424300000000000026566 0ustar00lukpstaff00000000000000""" We copy some functions from the Python 3.3.0 ssl module. http://hg.python.org/releasing/3.3.0/file/1465cbbc8f64/Lib/ssl.py """ import re class CertificateError(ValueError): pass def _dnsname_to_pat(dn): pats = [] for frag in dn.split(r'.'): if frag == '*': # When '*' is a fragment by itself, it matches a non-empty dotless # fragment. pats.append('[^.]+') else: # Otherwise, '*' matches any dotless fragment. frag = re.escape(frag) pats.append(frag.replace(r'\*', '[^.]*')) return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) def match_hostname(cert, hostname): """Verify that *cert* (in decoded format as returned by SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules are mostly followed, but IP addresses are not accepted for *hostname*. CertificateError is raised on failure. On success, the function returns nothing. """ if not cert: raise ValueError("empty or no certificate") dnsnames = [] san = cert.get('subjectAltName', ()) for key, value in san: if key == 'DNS': if _dnsname_to_pat(value).match(hostname): return dnsnames.append(value) if not dnsnames: # The subject is only checked when there is no dNSName entry # in subjectAltName for sub in cert.get('subject', ()): for key, value in sub: # XXX according to RFC 2818, the most specific Common Name # must be used. if key == 'commonName': if _dnsname_to_pat(value).match(hostname): return dnsnames.append(value) if len(dnsnames) > 1: raise CertificateError("hostname %r " "doesn't match either of %s" % (hostname, ', '.join(map(repr, dnsnames)))) elif len(dnsnames) == 1: raise CertificateError("hostname %r " "doesn't match %r" % (hostname, dnsnames[0])) else: raise CertificateError("no appropriate commonName or " "subjectAltName fields were found") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/ecdsa_keys.py0000755000076500000240000004105600000000000023377 0ustar00lukpstaff00000000000000""" ecdsa_keys.py Vladimir Diaz November 22, 2016. See LICENSE for licensing information. The goal of this module is to support ECDSA keys and signatures. ECDSA is an elliptic-curve digital signature algorithm. It grants a similar level of security as RSA, but uses smaller keys. No subexponential-time algorithm is known for the elliptic curve discrete logarithm problem. https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm 'securesystemslib.ecdsa_keys.py' calls the 'cryptography' library to perform all of the ecdsa-related operations. The ecdsa-related functions included here are generate(), create_signature() and verify_signature(). The 'cryptography' library is used by ecdsa_keys.py to perform the actual ECDSA computations, and the functions listed above can be viewed as an easy-to-use public interface. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import logging # Import cryptography modules to support ecdsa keys and signatures. CRYPTO = True NO_CRYPTO_MSG = "ECDSA key support requires the cryptography library" try: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.serialization import load_pem_public_key from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.exceptions import (InvalidSignature, UnsupportedAlgorithm) _SCHEME_HASHER = { 'ecdsa-sha2-nistp256': ec.ECDSA(hashes.SHA256()), 'ecdsa-sha2-nistp384': ec.ECDSA(hashes.SHA384()) } except ImportError: CRYPTO = False # Perform object format-checking and add ability to handle/raise exceptions. from securesystemslib import exceptions from securesystemslib import formats _SUPPORTED_ECDSA_SCHEMES = ['ecdsa-sha2-nistp256'] logger = logging.getLogger(__name__) def generate_public_and_private(scheme='ecdsa-sha2-nistp256'): """ Generate a pair of ECDSA public and private keys with one of the supported, external cryptography libraries. The public and private keys returned conform to 'securesystemslib.formats.PEMECDSA_SCHEMA' and 'securesystemslib.formats.PEMECDSA_SCHEMA', respectively. The public ECDSA public key has the PEM format: TODO: should we encrypt the private keys returned here? Should the create_signature() accept encrypted keys? '-----BEGIN PUBLIC KEY----- ... '-----END PUBLIC KEY-----' The private ECDSA private key has the PEM format: '-----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY-----' >>> public, private = generate_public_and_private() >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(public) True >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(private) True scheme: A string indicating which algorithm to use for the generation of the public and private ECDSA keys. 'ecdsa-sha2-nistp256' is the only currently supported ECDSA algorithm, which is supported by OpenSSH and specified in RFC 5656 (https://tools.ietf.org/html/rfc5656). securesystemslib.exceptions.FormatError, if 'algorithm' is improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is an unsupported algorithm. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. None. A (public, private) tuple that conform to 'securesystemslib.formats.PEMECDSA_SCHEMA' and 'securesystemslib.formats.PEMECDSA_SCHEMA', respectively. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # Does 'scheme' have the correct format? # Verify that 'scheme' is of the correct type, and that it's one of the # supported ECDSA . It must conform to # 'securesystemslib.formats.ECDSA_SCHEME_SCHEMA'. Raise # 'securesystemslib.exceptions.FormatError' if the check fails. formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) public_key = None private_key = None # An if-clause is strictly not needed, since 'ecdsa_sha2-nistp256' is the # only currently supported ECDSA signature scheme. Nevertheness, include the # conditional statement to accomodate any schemes that might be added. if scheme == 'ecdsa-sha2-nistp256': private_key = ec.generate_private_key(ec.SECP256R1, default_backend()) public_key = private_key.public_key() # The ECDSA_SCHEME_SCHEMA.check_match() above should have detected any # invalid 'scheme'. This is a defensive check. else: #pragma: no cover raise exceptions.UnsupportedAlgorithmError('An unsupported' ' scheme specified: ' + repr(scheme) + '.\n Supported' ' algorithms: ' + repr(_SUPPORTED_ECDSA_SCHEMES)) private_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption()) public_pem = public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) return public_pem.decode('utf-8'), private_pem.decode('utf-8') def create_signature(public_key, private_key, data, scheme='ecdsa-sha2-nistp256'): """ Return a (signature, scheme) tuple. >>> requested_scheme = 'ecdsa-sha2-nistp256' >>> public, private = generate_public_and_private(requested_scheme) >>> data = b'The quick brown fox jumps over the lazy dog' >>> signature, scheme = create_signature(public, private, data, requested_scheme) >>> securesystemslib.formats.ECDSASIGNATURE_SCHEMA.matches(signature) True >>> requested_scheme == scheme True public: The ECDSA public key in PEM format. private: The ECDSA private key in PEM format. data: Byte data used by create_signature() to generate the signature returned. scheme: The signature scheme used to generate the signature. For example: 'ecdsa-sha2-nistp256'. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.CryptoError, if a signature cannot be created. securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is not one of the supported signature schemes. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. None. A signature dictionary conformat to 'securesystemslib.format.SIGNATURE_SCHEMA'. ECDSA signatures are XX bytes, however, the hexlified signature is stored in the dictionary returned. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # Do 'public_key' and 'private_key' have the correct format? # This check will ensure that the arguments conform to # 'securesystemslib.formats.PEMECDSA_SCHEMA'. Raise # 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMECDSA_SCHEMA.check_match(public_key) # Is 'private_key' properly formatted? formats.PEMECDSA_SCHEMA.check_match(private_key) # Is 'scheme' properly formatted? formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) # 'ecdsa-sha2-nistp256' is the only currently supported ECDSA scheme, so this # if-clause isn't strictly needed. Nevertheless, the conditional statement # is included to accommodate multiple schemes that can potentially be added # in the future. if scheme == 'ecdsa-sha2-nistp256': try: private_key = load_pem_private_key(private_key.encode('utf-8'), password=None, backend=default_backend()) signature = private_key.sign(data, ec.ECDSA(hashes.SHA256())) except TypeError as e: raise exceptions.CryptoError('Could not create' ' signature: ' + str(e)) # A defensive check for an invalid 'scheme'. The # ECDSA_SCHEME_SCHEMA.check_match() above should have already validated it. else: #pragma: no cover raise exceptions.UnsupportedAlgorithmError('Unsupported' ' signature scheme is specified: ' + repr(scheme)) return signature, scheme def verify_signature(public_key, scheme, signature, data): """ Verify that 'signature' was produced by the private key associated with 'public_key'. >>> scheme = 'ecdsa-sha2-nistp256' >>> public, private = generate_public_and_private(scheme) >>> data = b'The quick brown fox jumps over the lazy dog' >>> signature, scheme = create_signature(public, private, data, scheme) >>> verify_signature(public, scheme, signature, data) True >>> verify_signature(public, scheme, signature, b'bad data') False public_key: The ECDSA public key in PEM format. The public key is needed to verify 'signature'. scheme: The signature scheme used to generate 'signature'. For example: 'ecdsa-sha2-nistp256'. signature: The signature to be verified, which should have been generated by the private key associated with 'public_key'. 'data'. data: Byte data that was used by create_signature() to generate 'signature'. securesystemslib.exceptions.FormatError, if any of the arguments are improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is not one of the supported signature schemes. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. None. Boolean, indicating whether the 'signature' of data was generated by the private key associated with 'public_key'. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # Are the arguments properly formatted? # If not, raise 'securesystemslib.exceptions.FormatError'. formats.PEMECDSA_SCHEMA.check_match(public_key) formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) formats.ECDSASIGNATURE_SCHEMA.check_match(signature) ecdsa_key = load_pem_public_key(public_key.encode('utf-8'), backend=default_backend()) if not isinstance(ecdsa_key, ec.EllipticCurvePublicKey): raise exceptions.FormatError('Invalid ECDSA public' ' key: ' + repr(public_key)) else: logger.debug('Loaded a valid ECDSA public key.') # verify() raises an 'InvalidSignature' exception if 'signature' # is invalid. try: ecdsa_key.verify(signature, data, _SCHEME_HASHER[scheme]) return True except (TypeError, InvalidSignature): return False def create_ecdsa_public_and_private_from_pem(pem, password=None): """ Create public and private ECDSA keys from a private 'pem'. The public and private keys are strings in PEM format: public: '-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----', private: '-----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY-----'}} >>> junk, private = generate_public_and_private() >>> public, private = create_ecdsa_public_and_private_from_pem(private) >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(public) True >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(private) True >>> passphrase = 'secret' >>> encrypted_pem = create_ecdsa_encrypted_pem(private, passphrase) >>> public, private = create_ecdsa_public_and_private_from_pem(encrypted_pem, passphrase) >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(public) True >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(private) True pem: A string in PEM format. The private key is extracted and returned in an ecdsakey object. password: (optional) The password, or passphrase, to decrypt the private part of the ECDSA key if it is encrypted. 'password' is not used directly as the encryption key, a stronger encryption key is derived from it. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if the ECDSA key pair could not be extracted, possibly due to an unsupported algorithm. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. None. A dictionary containing the ECDSA keys and other identifying information. Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # Does 'pem' have the correct format? # This check will ensure 'pem' conforms to # 'securesystemslib.formats.ECDSARSA_SCHEMA'. formats.PEMECDSA_SCHEMA.check_match(pem) if password is not None: formats.PASSWORD_SCHEMA.check_match(password) password = password.encode('utf-8') else: logger.debug('The password/passphrase is unset. The PEM is expected' ' to be unencrypted.') public = None private = None # Generate the public and private ECDSA keys. The pyca/cryptography library # performs the actual import operation. try: private = load_pem_private_key(pem.encode('utf-8'), password=password, backend=default_backend()) except (ValueError, UnsupportedAlgorithm) as e: raise exceptions.CryptoError('Could not import private' ' PEM.\n' + str(e)) public = private.public_key() # Serialize public and private keys to PEM format. private = private.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption()) public = public.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) return public.decode('utf-8'), private.decode('utf-8') def create_ecdsa_encrypted_pem(private_pem, passphrase): """ Return a string in PEM format, where the private part of the ECDSA key is encrypted. The private part of the ECDSA key is encrypted as done by pyca/cryptography: "Encrypt using the best available encryption for a given key's backend. This is a curated encryption choice and the algorithm may change over time." >>> junk, private = generate_public_and_private() >>> passphrase = 'secret' >>> encrypted_pem = create_ecdsa_encrypted_pem(private, passphrase) >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(encrypted_pem) True private_pem: The private ECDSA key string in PEM format. passphrase: The passphrase, or password, to encrypt the private part of the ECDSA key. 'passphrase' is not used directly as the encryption key, a stronger encryption key is derived from it. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.CryptoError, if an ECDSA key in encrypted PEM format cannot be created. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. None. A string in PEM format, where the private RSA portion is encrypted. Conforms to 'securesystemslib.formats.PEMECDSA_SCHEMA'. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # Does 'private_key' have the correct format? # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMRSA_SCHEMA.check_match(private_pem) # Does 'passphrase' have the correct format? formats.PASSWORD_SCHEMA.check_match(passphrase) private = load_pem_private_key(private_pem.encode('utf-8'), password=None, backend=default_backend()) encrypted_private_pem = \ private.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.BestAvailableEncryption(passphrase.encode('utf-8'))) return encrypted_private_pem if __name__ == '__main__': # The interactive sessions of the documentation strings can # be tested by running 'ecdsa_keys.py' as a standalone module. # python -B ecdsa_keys.py import doctest doctest.testmod() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/ed25519_keys.py0000755000076500000240000003150700000000000023316 0ustar00lukpstaff00000000000000""" ed25519_keys.py Vladimir Diaz September 24, 2013. See LICENSE for licensing information. The goal of this module is to support Ed25519 signatures. Ed25519 is an elliptic-curve public key signature scheme, its main strength being small signatures (64 bytes) and small public keys (32 bytes). http://ed25519.cr.yp.to/ 'securesystemslib/ed25519_keys.py' calls 'ed25519.py', which is the pure Python implementation of ed25519 optimized for a faster runtime. The Python reference implementation is concise, but very slow (verifying signatures takes ~9 seconds on an Intel core 2 duo @ 2.2 ghz x 2). The optimized version can verify signatures in ~2 seconds. http://ed25519.cr.yp.to/software.html https://github.com/pyca/ed25519 Optionally, ed25519 cryptographic operations may be executed by PyNaCl, which is a Python binding to the NaCl library and is faster than the pure python implementation. Verifying signatures can take approximately 0.0009 seconds. PyNaCl relies on the libsodium C library. PyNaCl is required for key and signature generation. Verifying signatures may be done in pure Python. https://github.com/pyca/pynacl https://github.com/jedisct1/libsodium http://nacl.cr.yp.to/ https://github.com/pyca/ed25519 The ed25519-related functions included here are generate(), create_signature() and verify_signature(). The 'ed25519' and PyNaCl (i.e., 'nacl') modules used by ed25519_keys.py perform the actual ed25519 computations and the functions listed above can be viewed as an easy-to-use public interface. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals # 'os' required to generate OS-specific randomness (os.urandom) suitable for # cryptographic use. # http://docs.python.org/2/library/os.html#miscellaneous-functions import os # Import the python implementation of the ed25519 algorithm provided by pyca, # which is an optimized version of the one provided by ed25519's authors. # Note: The pure Python version does not include protection against side-channel # attacks. Verifying signatures can take approximately 2 seconds on an intel # core 2 duo @ 2.2 ghz x 2). Optionally, the PyNaCl module may be used to # speed up ed25519 cryptographic operations. # http://ed25519.cr.yp.to/software.html # https://github.com/pyca/ed25519 # https://github.com/pyca/pynacl # # Import the PyNaCl library, if available. It is recommended this library be # used over the pure python implementation of Ed25519, due to its speedier # routines and side-channel protections available in the libsodium library. NACL = True NO_NACL_MSG = "ed25519 key support requires the nacl library" try: from nacl.encoding import RawEncoder from nacl.signing import (SigningKey, VerifyKey) # avoid conflicts with own exceptions of same name from nacl import exceptions as nacl_exceptions except ImportError: NACL = False # The optimized pure Python implementation of Ed25519. If # PyNaCl cannot be imported and an attempt to use is made in this module, a # 'securesystemslib.exceptions.UnsupportedLibraryError' exception is raised. from securesystemslib._vendor.ed25519 import ed25519 as python_ed25519 from securesystemslib import exceptions from securesystemslib import formats # Supported ed25519 signing schemes: 'ed25519'. The pure Python implementation # (i.e., ed25519') and PyNaCl (i.e., 'nacl', libsodium + Python bindings) # modules are currently supported in the creation of 'ed25519' signatures. # Previously, a distinction was made between signatures made by the pure Python # implementation and PyNaCl. _SUPPORTED_ED25519_SIGNING_SCHEMES = ['ed25519'] def generate_public_and_private(): """ Generate a pair of ed25519 public and private keys with PyNaCl. The public and private keys returned conform to 'securesystemslib.formats.ED25519PUBLIC_SCHEMA' and 'securesystemslib.formats.ED25519SEED_SCHEMA', respectively. An ed25519 seed key is a random 32-byte string. Public keys are also 32 bytes. >>> public, private = generate_public_and_private() >>> securesystemslib.formats.ED25519PUBLIC_SCHEMA.matches(public) True >>> securesystemslib.formats.ED25519SEED_SCHEMA.matches(private) True None. securesystemslib.exceptions.UnsupportedLibraryError, if the PyNaCl ('nacl') module is unavailable. NotImplementedError, if a randomness source is not found by 'os.urandom'. The ed25519 keys are generated by first creating a random 32-byte seed with os.urandom() and then calling PyNaCl's nacl.signing.SigningKey(). A (public, private) tuple that conform to 'securesystemslib.formats.ED25519PUBLIC_SCHEMA' and 'securesystemslib.formats.ED25519SEED_SCHEMA', respectively. """ if not NACL: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_NACL_MSG) # Generate ed25519's seed key by calling os.urandom(). The random bytes # returned should be suitable for cryptographic use and is OS-specific. # Raise 'NotImplementedError' if a randomness source is not found. # ed25519 seed keys are fixed at 32 bytes (256-bit keys). # http://blog.mozilla.org/warner/2011/11/29/ed25519-keys/ seed = os.urandom(32) public = None # Generate the public key. PyNaCl (i.e., 'nacl' module) performs the actual # key generation. nacl_key = SigningKey(seed) public = nacl_key.verify_key.encode(encoder=RawEncoder()) return public, seed def create_signature(public_key, private_key, data, scheme): """ Return a (signature, scheme) tuple, where the signature scheme is 'ed25519' and is always generated by PyNaCl (i.e., 'nacl'). The signature returned conforms to 'securesystemslib.formats.ED25519SIGNATURE_SCHEMA', and has the form: '\xae\xd7\x9f\xaf\x95{bP\x9e\xa8YO Z\x86\x9d...' A signature is a 64-byte string. >>> public, private = generate_public_and_private() >>> data = b'The quick brown fox jumps over the lazy dog' >>> scheme = 'ed25519' >>> signature, scheme = \ create_signature(public, private, data, scheme) >>> securesystemslib.formats.ED25519SIGNATURE_SCHEMA.matches(signature) True >>> scheme == 'ed25519' True >>> signature, scheme = \ create_signature(public, private, data, scheme) >>> securesystemslib.formats.ED25519SIGNATURE_SCHEMA.matches(signature) True >>> scheme == 'ed25519' True public: The ed25519 public key, which is a 32-byte string. private: The ed25519 private key, which is a 32-byte string. data: Data object used by create_signature() to generate the signature. scheme: The signature scheme used to generate the signature. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.CryptoError, if a signature cannot be created. securesystemslib.exceptions.UnsupportedLibraryError, if the PyNaCl ('nacl') module is unavailable. nacl.signing.SigningKey.sign() called to generate the actual signature. A signature dictionary conformat to 'securesystemslib.format.SIGNATURE_SCHEMA'. ed25519 signatures are 64 bytes, however, the hexlified signature is stored in the dictionary returned. """ if not NACL: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_NACL_MSG) # Does 'public_key' have the correct format? # This check will ensure 'public_key' conforms to # 'securesystemslib.formats.ED25519PUBLIC_SCHEMA', which must have length 32 # bytes. Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.ED25519PUBLIC_SCHEMA.check_match(public_key) # Is 'private_key' properly formatted? formats.ED25519SEED_SCHEMA.check_match(private_key) # Is 'scheme' properly formatted? formats.ED25519_SIG_SCHEMA.check_match(scheme) # Signing the 'data' object requires a seed and public key. # nacl.signing.SigningKey.sign() generates the signature. signature = None # An if-clause is not strictly needed here, since 'ed25519' is the only # currently supported scheme. Nevertheless, include the conditional # statement to accommodate schemes that might be added in the future. if scheme == 'ed25519': try: nacl_key = SigningKey(private_key) nacl_sig = nacl_key.sign(data) signature = nacl_sig.signature except (ValueError, TypeError, nacl_exceptions.CryptoError) as e: raise exceptions.CryptoError('An "ed25519" signature' ' could not be created with PyNaCl.' + str(e)) # This is a defensive check for a valid 'scheme', which should have already # been validated in the check_match() above. else: #pragma: no cover raise exceptions.UnsupportedAlgorithmError('Unsupported' ' signature scheme is specified: ' + repr(scheme)) return signature, scheme def verify_signature(public_key, scheme, signature, data): """ Determine whether the private key corresponding to 'public_key' produced 'signature'. verify_signature() will use the public key, the 'scheme' and 'sig', and 'data' arguments to complete the verification. >>> public, private = generate_public_and_private() >>> data = b'The quick brown fox jumps over the lazy dog' >>> scheme = 'ed25519' >>> signature, scheme = \ create_signature(public, private, data, scheme) >>> verify_signature(public, scheme, signature, data) True >>> bad_data = b'The sly brown fox jumps over the lazy dog' >>> bad_signature, scheme = \ create_signature(public, private, bad_data, scheme) >>> verify_signature(public, scheme, bad_signature, data) False public_key: The public key is a 32-byte string. scheme: 'ed25519' signature scheme used by either the pure python implementation (i.e., ed25519.py) or PyNacl (i.e., 'nacl'). signature: The signature is a 64-byte string. data: Data object used by securesystemslib.ed25519_keys.create_signature() to generate 'signature'. 'data' is needed here to verify the signature. securesystemslib.exceptions.UnsupportedAlgorithmError. Raised if the signature scheme 'scheme' is not one supported by securesystemslib.ed25519_keys.create_signature(). securesystemslib.exceptions.FormatError. Raised if the arguments are improperly formatted. nacl.signing.VerifyKey.verify() called if available, otherwise securesystemslib._vendor.ed25519.ed25519.checkvalid() called to do the verification. Boolean. True if the signature is valid, False otherwise. """ # Does 'public_key' have the correct format? # This check will ensure 'public_key' conforms to # 'securesystemslib.formats.ED25519PUBLIC_SCHEMA', which must have length 32 # bytes. Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.ED25519PUBLIC_SCHEMA.check_match(public_key) # Is 'scheme' properly formatted? formats.ED25519_SIG_SCHEMA.check_match(scheme) # Is 'signature' properly formatted? formats.ED25519SIGNATURE_SCHEMA.check_match(signature) # Verify 'signature'. Before returning the Boolean result, ensure 'ed25519' # was used as the signature scheme. public = public_key valid_signature = False if scheme in _SUPPORTED_ED25519_SIGNING_SCHEMES: if NACL: try: nacl_verify_key = VerifyKey(public) nacl_verify_key.verify(data, signature) valid_signature = True except nacl_exceptions.BadSignatureError: pass # Verify 'ed25519' signature with the pure Python implementation. else: try: python_ed25519.checkvalid(signature, data, public) valid_signature = True # The pure Python implementation raises 'Exception' if 'signature' is # invalid. except Exception: pass # This is a defensive check for a valid 'scheme', which should have already # been validated in the ED25519_SIG_SCHEMA.check_match(scheme) above. else: #pragma: no cover message = 'Unsupported ed25519 signature scheme: ' + repr(scheme) + '.\n' + \ 'Supported schemes: ' + repr(_SUPPORTED_ED25519_SIGNING_SCHEMES) + '.' raise exceptions.UnsupportedAlgorithmError(message) return valid_signature if __name__ == '__main__': # The interactive sessions of the documentation strings can # be tested by running 'ed25519_keys.py' as a standalone module. # python -B ed25519_keys.py import doctest doctest.testmod() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/exceptions.py0000755000076500000240000000601200000000000023437 0ustar00lukpstaff00000000000000""" exceptions.py Geremy Condra Vladimir Diaz VD: April 4, 2012 Revision. See LICENSE for licensing information. Define exceptions. The names chosen for exception classes should end in 'Error' (except where there is a good reason not to). """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals class Error(Exception): """Indicate a generic error.""" pass class Warning(Warning): """Generic warning. It is used by the 'warnings' module.""" pass class FormatError(Error): """Indicate an error while validating an object's format.""" pass class InvalidMetadataJSONError(FormatError): """Indicate that a metadata file is not valid JSON.""" def __init__(self, exception): # Store the original exception. self.exception = exception def __str__(self): # Show the original exception. return repr(self.exception) class UnsupportedAlgorithmError(Error): """Indicate an error while trying to identify a user-specified algorithm.""" pass class BadHashError(Error): """Indicate an error while checking the value a hash object.""" def __init__(self, expected_hash, observed_hash): self.expected_hash = expected_hash self.observed_hash = observed_hash def __str__(self): return 'Observed hash (' + repr(self.observed_hash)+\ ') != expected hash (' + repr(self.expected_hash)+')' class BadPasswordError(Error): """Indicate an error after encountering an invalid password.""" pass class CryptoError(Error): """Indicate any cryptography-related errors.""" pass class BadSignatureError(CryptoError): """Indicate that some metadata has a bad signature.""" def __init__(self, metadata_role_name): self.metadata_role_name = metadata_role_name def __str__(self): return repr(self.metadata_role_name) + ' metadata has bad signature.' class UnknownMethodError(CryptoError): """Indicate that a user-specified cryptograpthic method is unknown.""" pass class UnsupportedLibraryError(Error): """Indicate that a supported library could not be located or imported.""" pass class InvalidNameError(Error): """Indicate an error while trying to validate any type of named object.""" pass class NotFoundError(Error): """If a required configuration or resource is not found.""" pass class URLMatchesNoPatternError(Error): """If a URL does not match a user-specified regular expression.""" pass class InvalidConfigurationError(Error): """If a configuration object does not match the expected format.""" pass class StorageError(Error): """Indicate an error occured during interaction with an abstracted storage backend.""" pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/formats.py0000755000076500000240000005722400000000000022744 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ formats.py Geremy Condra Vladimir Diaz Refactored April 30, 2012. -vladimir.v.diaz See LICENSE for licensing information. A central location for all format-related checking of securesystemslib objects. Note: 'formats.py' depends heavily on 'schema.py', so the 'schema.py' module should be read and understood before tackling this module. 'formats.py' can be broken down into three sections. (1) Schemas and object matching. (2) Classes that represent Role Metadata and help produce correctly formatted files. (3) Functions that help produce or verify securesystemslib objects. The first section deals with schemas and object matching based on format. There are two ways of checking the format of objects. The first method raises a 'securesystemslib.exceptions.FormatError' exception if the match fails and the other returns a Boolean result. securesystemslib.formats..check_match(object) securesystemslib.formats..matches(object) Example: rsa_key = {'keytype': 'rsa' 'keyid': 34892fc465ac76bc3232fab 'keyval': {'public': 'public_key', 'private': 'private_key'} securesystemslib.formats.RSAKEY_SCHEMA.check_match(rsa_key) securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key) In this example, if a dict key or dict value is missing or incorrect, the match fails. There are numerous variations of object checking provided by 'formats.py' and 'schema.py'. The second section deals with the role metadata classes. There are multiple top-level roles, each with differing metadata formats. Example: root_object = securesystemslib.formats.RootFile.from_metadata(root_metadata_file) targets_metadata = securesystemslib.formats.TargetsFile.make_metadata(...) The input and output of these classes are checked against their respective schema to ensure correctly formatted metadata. The last section contains miscellaneous functions related to the format of securesystemslib objects. Example: signable_object = make_signable(unsigned_object) """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import binascii import calendar import re import datetime import time import six from securesystemslib import exceptions from securesystemslib import schema as SCHEMA # Note that in the schema definitions below, the 'SCHEMA.Object' types allow # additional keys which are not defined. Thus, any additions to them will be # easily backwards compatible with clients that are already deployed. ANY_STRING_SCHEMA = SCHEMA.AnyString() LIST_OF_ANY_STRING_SCHEMA = SCHEMA.ListOf(ANY_STRING_SCHEMA) # A datetime in 'YYYY-MM-DDTHH:MM:SSZ' ISO 8601 format. The "Z" zone designator # for the zero UTC offset is always used (i.e., a numerical offset is not # supported.) Example: '2015-10-21T13:20:00Z'. Note: This is a simple format # check, and an ISO8601 string should be fully verified when it is parsed. ISO8601_DATETIME_SCHEMA = SCHEMA.RegularExpression(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z') # A Unix/POSIX time format. An integer representing the number of seconds # since the epoch (January 1, 1970.) Metadata uses this format for the # 'expires' field. Set 'hi' to the upper timestamp limit (year 2038), the max # value of an int. UNIX_TIMESTAMP_SCHEMA = SCHEMA.Integer(lo=0, hi=2147483647) # A hexadecimal value in '23432df87ab..' format. HEX_SCHEMA = SCHEMA.RegularExpression(r'[a-fA-F0-9]+') HASH_SCHEMA = HEX_SCHEMA # A dict in {'sha256': '23432df87ab..', 'sha512': '34324abc34df..', ...} format. HASHDICT_SCHEMA = SCHEMA.DictOf( key_schema = SCHEMA.AnyString(), value_schema = HASH_SCHEMA) # Uniform Resource Locator identifier (e.g., 'https://www.updateframework.com/'). # TODO: Some level of restriction here would be good.... Note that I pulled # this from securesystemslib, since it's neither sophisticated nor used # by anyone else. URL_SCHEMA = SCHEMA.AnyString() # A key identifier (e.g., a hexadecimal value identifying an RSA key). KEYID_SCHEMA = HASH_SCHEMA # A list of KEYID_SCHEMA. KEYIDS_SCHEMA = SCHEMA.ListOf(KEYID_SCHEMA) # The signing scheme used by a key to generate a signature (e.g., # 'rsassa-pss-sha256' is one of the signing schemes for key type 'rsa'). SCHEME_SCHEMA = SCHEMA.AnyString() # A path string, whether relative or absolute, e.g. 'metadata/root/' PATH_SCHEMA = SCHEMA.AnyNonemptyString() PATHS_SCHEMA = SCHEMA.ListOf(PATH_SCHEMA) # An integer representing logger levels, such as logging.CRITICAL (=50). # Must be between 0 and 50. LOGLEVEL_SCHEMA = SCHEMA.Integer(lo=0, hi=50) # A string representing a named object. NAME_SCHEMA = SCHEMA.AnyString() NAMES_SCHEMA = SCHEMA.ListOf(NAME_SCHEMA) # A byte string representing data. DATA_SCHEMA = SCHEMA.AnyBytes() # A text string. For instance, a string entered by the user. TEXT_SCHEMA = SCHEMA.AnyString() # Supported hash algorithms. HASHALGORITHMS_SCHEMA = SCHEMA.ListOf(SCHEMA.OneOf( [SCHEMA.String('md5'), SCHEMA.String('sha1'), SCHEMA.String('sha224'), SCHEMA.String('sha256'), SCHEMA.String('sha384'), SCHEMA.String('sha512'), SCHEMA.String('blake2s'), SCHEMA.String('blake2b'), SCHEMA.String('blake2b-256')])) # The contents of an encrypted key. Encrypted keys are saved to files # in this format. ENCRYPTEDKEY_SCHEMA = SCHEMA.AnyString() # A value that is either True or False, on or off, etc. BOOLEAN_SCHEMA = SCHEMA.Boolean() # The minimum number of bits for an RSA key. Must be 2048 bits, or greater # (recommended by TUF). Recommended RSA key sizes: # http://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm#table1 RSAKEYBITS_SCHEMA = SCHEMA.Integer(lo=2048) # The supported ECDSA signature schemes ECDSA_SCHEME_SCHEMA = SCHEMA.RegularExpression(r'ecdsa-sha2-nistp(256|384)') # A pyca-cryptography signature. PYCACRYPTOSIGNATURE_SCHEMA = SCHEMA.AnyBytes() # An RSA key in PEM format. PEMRSA_SCHEMA = SCHEMA.AnyString() # An ECDSA key in PEM format. PEMECDSA_SCHEMA = SCHEMA.AnyString() # A string representing a password. PASSWORD_SCHEMA = SCHEMA.AnyString() # A list of passwords. PASSWORDS_SCHEMA = SCHEMA.ListOf(PASSWORD_SCHEMA) # The actual values of a key, as opposed to meta data such as a key type and # key identifier ('rsa', 233df889cb). For RSA keys, the key value is a pair of # public and private keys in PEM Format stored as strings. KEYVAL_SCHEMA = SCHEMA.Object( object_name = 'KEYVAL_SCHEMA', public = SCHEMA.AnyString(), private = SCHEMA.Optional(SCHEMA.AnyString())) # Public keys CAN have a private portion (for backwards compatibility) which # MUST be an empty string PUBLIC_KEYVAL_SCHEMA = SCHEMA.Object( object_name = 'KEYVAL_SCHEMA', public = SCHEMA.AnyString(), private = SCHEMA.Optional(SCHEMA.String(""))) # Supported securesystemslib key types. KEYTYPE_SCHEMA = SCHEMA.OneOf( [SCHEMA.String('rsa'), SCHEMA.String('ed25519'), SCHEMA.String('ecdsa'), SCHEMA.RegularExpression(r'ecdsa-sha2-nistp(256|384)')]) # A generic securesystemslib key. All securesystemslib keys should be saved to # metadata files in this format. KEY_SCHEMA = SCHEMA.Object( object_name = 'KEY_SCHEMA', keytype = SCHEMA.AnyString(), scheme = SCHEME_SCHEMA, keyval = KEYVAL_SCHEMA, expires = SCHEMA.Optional(ISO8601_DATETIME_SCHEMA)) # Like KEY_SCHEMA, but requires keyval's private portion to be unset or empty, # and optionally includes the supported keyid hash algorithms used to generate # the key's keyid. PUBLIC_KEY_SCHEMA = SCHEMA.Object( object_name = 'PUBLIC_KEY_SCHEMA', keytype = SCHEMA.AnyString(), keyid_hash_algorithms = SCHEMA.Optional(HASHALGORITHMS_SCHEMA), keyval = PUBLIC_KEYVAL_SCHEMA, expires = SCHEMA.Optional(ISO8601_DATETIME_SCHEMA)) # A securesystemslib key object. This schema simplifies validation of keys # that may be one of the supported key types. Supported key types: 'rsa', # 'ed25519'. ANYKEY_SCHEMA = SCHEMA.Object( object_name = 'ANYKEY_SCHEMA', keytype = KEYTYPE_SCHEMA, scheme = SCHEME_SCHEMA, keyid = KEYID_SCHEMA, keyid_hash_algorithms = SCHEMA.Optional(HASHALGORITHMS_SCHEMA), keyval = KEYVAL_SCHEMA, expires = SCHEMA.Optional(ISO8601_DATETIME_SCHEMA)) # A list of securesystemslib key objects. ANYKEYLIST_SCHEMA = SCHEMA.ListOf(ANYKEY_SCHEMA) # RSA signature schemes. RSA_SCHEME_SCHEMA = SCHEMA.OneOf([ SCHEMA.RegularExpression(r'rsassa-pss-(md5|sha1|sha224|sha256|sha384|sha512)'), SCHEMA.RegularExpression(r'rsa-pkcs1v15-(md5|sha1|sha224|sha256|sha384|sha512)')]) # An RSA securesystemslib key. RSAKEY_SCHEMA = SCHEMA.Object( object_name = 'RSAKEY_SCHEMA', keytype = SCHEMA.String('rsa'), scheme = RSA_SCHEME_SCHEMA, keyid = KEYID_SCHEMA, keyid_hash_algorithms = SCHEMA.Optional(HASHALGORITHMS_SCHEMA), keyval = KEYVAL_SCHEMA) # An ECDSA securesystemslib key. ECDSAKEY_SCHEMA = SCHEMA.Object( object_name = 'ECDSAKEY_SCHEMA', keytype = SCHEMA.OneOf([SCHEMA.String('ecdsa'), SCHEMA.RegularExpression(r'ecdsa-sha2-nistp(256|384)')]), scheme = ECDSA_SCHEME_SCHEMA, keyid = KEYID_SCHEMA, keyid_hash_algorithms = SCHEMA.Optional(HASHALGORITHMS_SCHEMA), keyval = KEYVAL_SCHEMA) # An ED25519 raw public key, which must be 32 bytes. ED25519PUBLIC_SCHEMA = SCHEMA.LengthBytes(32) # An ED25519 raw seed key, which must be 32 bytes. ED25519SEED_SCHEMA = SCHEMA.LengthBytes(32) # An ED25519 raw signature, which must be 64 bytes. ED25519SIGNATURE_SCHEMA = SCHEMA.LengthBytes(64) # An ECDSA signature. ECDSASIGNATURE_SCHEMA = SCHEMA.AnyBytes() # Ed25519 signature schemes. The vanilla Ed25519 signature scheme is currently # supported. ED25519_SIG_SCHEMA = SCHEMA.OneOf([SCHEMA.String('ed25519')]) # An ed25519 key. ED25519KEY_SCHEMA = SCHEMA.Object( object_name = 'ED25519KEY_SCHEMA', keytype = SCHEMA.String('ed25519'), scheme = ED25519_SIG_SCHEMA, keyid = KEYID_SCHEMA, keyid_hash_algorithms = SCHEMA.Optional(HASHALGORITHMS_SCHEMA), keyval = KEYVAL_SCHEMA) # GPG key scheme definitions GPG_HASH_ALGORITHM_STRING = "pgp+SHA2" GPG_RSA_PUBKEY_METHOD_STRING = "pgp+rsa-pkcsv1.5" GPG_DSA_PUBKEY_METHOD_STRING = "pgp+dsa-fips-180-2" GPG_ED25519_PUBKEY_METHOD_STRING = "pgp+eddsa-ed25519" def _create_gpg_pubkey_with_subkey_schema(pubkey_schema): """Helper method to extend the passed public key schema with an optional dictionary of sub public keys "subkeys" with the same schema.""" schema = pubkey_schema subkey_schema_tuple = ("subkeys", SCHEMA.Optional( SCHEMA.DictOf( key_schema=KEYID_SCHEMA, value_schema=pubkey_schema ) ) ) # Any subclass of `securesystemslib.schema.Object` stores the schemas that # define the attributes of the object in its `_required` property, even if # such a schema is of type `Optional`. # TODO: Find a way that does not require to access a protected member schema._required.append(subkey_schema_tuple) # pylint: disable=protected-access return schema GPG_RSA_PUBKEYVAL_SCHEMA = SCHEMA.Object( object_name = "GPG_RSA_PUBKEYVAL_SCHEMA", e = SCHEMA.AnyString(), n = HEX_SCHEMA ) # We have to define GPG_RSA_PUBKEY_SCHEMA in two steps, because it is # self-referential. Here we define a shallow _GPG_RSA_PUBKEY_SCHEMA, which we # use below to create the self-referential GPG_RSA_PUBKEY_SCHEMA. _GPG_RSA_PUBKEY_SCHEMA = SCHEMA.Object( object_name = "GPG_RSA_PUBKEY_SCHEMA", type = SCHEMA.String("rsa"), method = SCHEMA.String(GPG_RSA_PUBKEY_METHOD_STRING), hashes = SCHEMA.ListOf(SCHEMA.String(GPG_HASH_ALGORITHM_STRING)), creation_time = SCHEMA.Optional(UNIX_TIMESTAMP_SCHEMA), validity_period = SCHEMA.Optional(SCHEMA.Integer(lo=0)), keyid = KEYID_SCHEMA, keyval = SCHEMA.Object( public = GPG_RSA_PUBKEYVAL_SCHEMA, private = SCHEMA.String("") ) ) GPG_RSA_PUBKEY_SCHEMA = _create_gpg_pubkey_with_subkey_schema( _GPG_RSA_PUBKEY_SCHEMA) GPG_DSA_PUBKEYVAL_SCHEMA = SCHEMA.Object( object_name = "GPG_DSA_PUBKEYVAL_SCHEMA", y = HEX_SCHEMA, p = HEX_SCHEMA, q = HEX_SCHEMA, g = HEX_SCHEMA ) # C.f. comment above _GPG_RSA_PUBKEY_SCHEMA definition _GPG_DSA_PUBKEY_SCHEMA = SCHEMA.Object( object_name = "GPG_DSA_PUBKEY_SCHEMA", type = SCHEMA.String("dsa"), method = SCHEMA.String(GPG_DSA_PUBKEY_METHOD_STRING), hashes = SCHEMA.ListOf(SCHEMA.String(GPG_HASH_ALGORITHM_STRING)), creation_time = SCHEMA.Optional(UNIX_TIMESTAMP_SCHEMA), validity_period = SCHEMA.Optional(SCHEMA.Integer(lo=0)), keyid = KEYID_SCHEMA, keyval = SCHEMA.Object( public = GPG_DSA_PUBKEYVAL_SCHEMA, private = SCHEMA.String("") ) ) GPG_DSA_PUBKEY_SCHEMA = _create_gpg_pubkey_with_subkey_schema( _GPG_DSA_PUBKEY_SCHEMA) GPG_ED25519_PUBKEYVAL_SCHEMA = SCHEMA.Object( object_name = "GPG_ED25519_PUBKEYVAL_SCHEMA", q = HEX_SCHEMA, ) # C.f. comment above _GPG_RSA_PUBKEY_SCHEMA definition _GPG_ED25519_PUBKEY_SCHEMA = SCHEMA.Object( object_name = "GPG_ED25519_PUBKEY_SCHEMA", type = SCHEMA.String("eddsa"), method = SCHEMA.String(GPG_ED25519_PUBKEY_METHOD_STRING), hashes = SCHEMA.ListOf(SCHEMA.String(GPG_HASH_ALGORITHM_STRING)), creation_time = SCHEMA.Optional(UNIX_TIMESTAMP_SCHEMA), validity_period = SCHEMA.Optional(SCHEMA.Integer(lo=0)), keyid = KEYID_SCHEMA, keyval = SCHEMA.Object( public = GPG_ED25519_PUBKEYVAL_SCHEMA, private = SCHEMA.String("") ) ) GPG_ED25519_PUBKEY_SCHEMA = _create_gpg_pubkey_with_subkey_schema( _GPG_ED25519_PUBKEY_SCHEMA) GPG_PUBKEY_SCHEMA = SCHEMA.OneOf([GPG_RSA_PUBKEY_SCHEMA, GPG_DSA_PUBKEY_SCHEMA, GPG_ED25519_PUBKEY_SCHEMA]) GPG_SIGNATURE_SCHEMA = SCHEMA.Object( object_name = "SIGNATURE_SCHEMA", keyid = KEYID_SCHEMA, short_keyid = SCHEMA.Optional(KEYID_SCHEMA), other_headers = HEX_SCHEMA, signature = HEX_SCHEMA, info = SCHEMA.Optional(SCHEMA.Any()), ) # A single signature of an object. Indicates the signature, and the KEYID of # the signing key. I debated making the signature schema not contain the key # ID and instead have the signatures of a file be a dictionary with the key # being the keyid and the value being the signature schema without the keyid. # That would be under the argument that a key should only be able to sign a # file once. SIGNATURE_SCHEMA = SCHEMA.Object( object_name = 'SIGNATURE_SCHEMA', keyid = KEYID_SCHEMA, sig = HEX_SCHEMA) # A dict where the dict keys hold a keyid and the dict values a key object. KEYDICT_SCHEMA = SCHEMA.DictOf( key_schema = KEYID_SCHEMA, value_schema = KEY_SCHEMA) ANY_SIGNATURE_SCHEMA = SCHEMA.OneOf([SIGNATURE_SCHEMA, GPG_SIGNATURE_SCHEMA]) # List of ANY_SIGNATURE_SCHEMA. SIGNATURES_SCHEMA = SCHEMA.ListOf(ANY_SIGNATURE_SCHEMA) # A signable object. Holds the signing role and its associated signatures. SIGNABLE_SCHEMA = SCHEMA.Object( object_name = 'SIGNABLE_SCHEMA', signed = SCHEMA.Any(), signatures = SIGNATURES_SCHEMA) # Note: Verification keys can have private portions but in case of GPG we # only have a PUBKEY_SCHEMA (because we never export private gpg keys from # the gpg keyring) ANY_VERIFICATION_KEY_SCHEMA = SCHEMA.OneOf([ANYKEY_SCHEMA, GPG_PUBKEY_SCHEMA]) VERIFICATION_KEY_DICT_SCHEMA = SCHEMA.DictOf( key_schema = KEYID_SCHEMA, value_schema = ANY_VERIFICATION_KEY_SCHEMA) ANY_KEYDICT_SCHEMA = SCHEMA.OneOf([KEYDICT_SCHEMA, VERIFICATION_KEY_DICT_SCHEMA]) ANY_PUBKEY_SCHEMA = SCHEMA.OneOf([PUBLIC_KEY_SCHEMA, GPG_PUBKEY_SCHEMA]) ANY_PUBKEY_DICT_SCHEMA = SCHEMA.DictOf( key_schema = KEYID_SCHEMA, value_schema = ANY_PUBKEY_SCHEMA) def datetime_to_unix_timestamp(datetime_object): """ Convert 'datetime_object' (in datetime.datetime()) format) to a Unix/POSIX timestamp. For example, Python's time.time() returns a Unix timestamp, and includes the number of microseconds. 'datetime_object' is converted to UTC. >>> datetime_object = datetime.datetime(1985, 10, 26, 1, 22) >>> timestamp = datetime_to_unix_timestamp(datetime_object) >>> timestamp 499137720 datetime_object: The datetime.datetime() object to convert to a Unix timestamp. securesystemslib.exceptions.FormatError, if 'datetime_object' is not a datetime.datetime() object. None. A unix (posix) timestamp (e.g., 499137660). """ # Is 'datetime_object' a datetime.datetime() object? # Raise 'securesystemslib.exceptions.FormatError' if not. if not isinstance(datetime_object, datetime.datetime): message = repr(datetime_object) + ' is not a datetime.datetime() object.' raise exceptions.FormatError(message) unix_timestamp = calendar.timegm(datetime_object.timetuple()) return unix_timestamp def unix_timestamp_to_datetime(unix_timestamp): """ Convert 'unix_timestamp' (i.e., POSIX time, in UNIX_TIMESTAMP_SCHEMA format) to a datetime.datetime() object. 'unix_timestamp' is the number of seconds since the epoch (January 1, 1970.) >>> datetime_object = unix_timestamp_to_datetime(1445455680) >>> datetime_object datetime.datetime(2015, 10, 21, 19, 28) unix_timestamp: An integer representing the time (e.g., 1445455680). Conformant to 'securesystemslib.formats.UNIX_TIMESTAMP_SCHEMA'. securesystemslib.exceptions.FormatError, if 'unix_timestamp' is improperly formatted. None. A datetime.datetime() object corresponding to 'unix_timestamp'. """ # Is 'unix_timestamp' properly formatted? # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. UNIX_TIMESTAMP_SCHEMA.check_match(unix_timestamp) # Convert 'unix_timestamp' to a 'time.struct_time', in UTC. The Daylight # Savings Time (DST) flag is set to zero. datetime.fromtimestamp() is not # used because it returns a local datetime. struct_time = time.gmtime(unix_timestamp) # Extract the (year, month, day, hour, minutes, seconds) arguments for the # datetime object to be returned. datetime_object = datetime.datetime(*struct_time[:6]) return datetime_object def format_base64(data): """ Return the base64 encoding of 'data' with whitespace and '=' signs omitted. data: Binary or buffer of data to convert. securesystemslib.exceptions.FormatError, if the base64 encoding fails or the argument is invalid. None. A base64-encoded string. """ try: return binascii.b2a_base64(data).decode('utf-8').rstrip('=\n ') except (TypeError, binascii.Error) as e: raise exceptions.FormatError('Invalid base64' ' encoding: ' + str(e)) def parse_base64(base64_string): """ Parse a base64 encoding with whitespace and '=' signs omitted. base64_string: A string holding a base64 value. securesystemslib.exceptions.FormatError, if 'base64_string' cannot be parsed due to an invalid base64 encoding. None. A byte string representing the parsed based64 encoding of 'base64_string'. """ if not isinstance(base64_string, six.string_types): message = 'Invalid argument: '+repr(base64_string) raise exceptions.FormatError(message) extra = len(base64_string) % 4 if extra: padding = '=' * (4 - extra) base64_string = base64_string + padding try: return binascii.a2b_base64(base64_string.encode('utf-8')) except (TypeError, binascii.Error) as e: raise exceptions.FormatError('Invalid base64' ' encoding: ' + str(e)) def _canonical_string_encoder(string): """ Encode 'string' to canonical string format. string: The string to encode. None. None. A string with the canonical-encoded 'string' embedded. """ string = '"%s"' % re.sub(r'(["\\])', r'\\\1', string) return string def _encode_canonical(object, output_function): # Helper for encode_canonical. Older versions of json.encoder don't # even let us replace the separators. if isinstance(object, six.string_types): output_function(_canonical_string_encoder(object)) elif object is True: output_function("true") elif object is False: output_function("false") elif object is None: output_function("null") elif isinstance(object, six.integer_types): output_function(str(object)) elif isinstance(object, (tuple, list)): output_function("[") if len(object): for item in object[:-1]: _encode_canonical(item, output_function) output_function(",") _encode_canonical(object[-1], output_function) output_function("]") elif isinstance(object, dict): output_function("{") if len(object): items = sorted(six.iteritems(object)) for key, value in items[:-1]: output_function(_canonical_string_encoder(key)) output_function(":") _encode_canonical(value, output_function) output_function(",") key, value = items[-1] output_function(_canonical_string_encoder(key)) output_function(":") _encode_canonical(value, output_function) output_function("}") else: raise exceptions.FormatError('I cannot encode '+repr(object)) def encode_canonical(object, output_function=None): """ Encode 'object' in canonical JSON form, as specified at http://wiki.laptop.org/go/Canonical_JSON . It's a restricted dialect of JSON in which keys are always lexically sorted, there is no whitespace, floats aren't allowed, and only quote and backslash get escaped. The result is encoded in UTF-8, and the resulting bits are passed to output_function (if provided), or joined into a string and returned. Note: This function should be called prior to computing the hash or signature of a JSON object in securesystemslib. For example, generating a signature of a signing role object such as 'ROOT_SCHEMA' is required to ensure repeatable hashes are generated across different json module versions and platforms. Code elsewhere is free to dump JSON objects in any format they wish (e.g., utilizing indentation and single quotes around object keys). These objects are only required to be in "canonical JSON" format when their hashes or signatures are needed. >>> encode_canonical("") '""' >>> encode_canonical([1, 2, 3]) '[1,2,3]' >>> encode_canonical([]) '[]' >>> encode_canonical({"A": [99]}) '{"A":[99]}' >>> encode_canonical({"x" : 3, "y" : 2}) '{"x":3,"y":2}' object: The object to be encoded. output_function: The result will be passed as arguments to 'output_function' (e.g., output_function('result')). securesystemslib.exceptions.FormatError, if 'object' cannot be encoded or 'output_function' is not callable. The results are fed to 'output_function()' if 'output_function' is set. A string representing the 'object' encoded in canonical JSON form. """ result = None # If 'output_function' is unset, treat it as # appending to a list. if output_function is None: result = [] output_function = result.append try: _encode_canonical(object, output_function) except (TypeError, exceptions.FormatError) as e: message = 'Could not encode ' + repr(object) + ': ' + str(e) raise exceptions.FormatError(message) # Return the encoded 'object' as a string. # Note: Implies 'output_function' is None, # otherwise results are sent to 'output_function'. if result is not None: return ''.join(result) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1614341428.896545 securesystemslib-0.20.0/securesystemslib/gpg/0000755000076500000240000000000000000000000021457 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/gpg/__init__.py0000644000076500000240000000136200000000000023572 0ustar00lukpstaff00000000000000""" gpg Santiago Torres-Arias Nov 15, 2017 See LICENSE for licensing information. This module was written due to the lack of other python (such as pygpg) modules that can provide an abstraction to the RFC4480 encoded messages from GPG. The closest candidate we could find was the python bindings for gpgme, we oped to use a Popen-based python-only construction given that gpgme is often shipped separately and other popular tools using gpg (e.g., git) don't use these bindings either. This is because users willing to use gpg signing are almost guaranteed to have gpg installed, yet the same assumption can't be made for the gpgme python bindings. """ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/gpg/common.py0000644000076500000240000007401000000000000023323 0ustar00lukpstaff00000000000000""" common.py Santiago Torres-Arias Nov 15, 2017 See LICENSE for licensing information. Provides algorithm-agnostic gpg public key and signature parsing functions. The functions select the appropriate functions for each algorithm and call them. """ import struct import binascii import logging import collections from securesystemslib import formats from securesystemslib.gpg import util as gpg_util from securesystemslib.gpg.exceptions import (PacketVersionNotSupportedError, SignatureAlgorithmNotSupportedError, KeyNotFoundError, PacketParsingError) from securesystemslib.gpg.constants import ( PACKET_TYPE_PRIMARY_KEY, PACKET_TYPE_USER_ID, PACKET_TYPE_USER_ATTR, PACKET_TYPE_SUB_KEY, PACKET_TYPE_SIGNATURE, SUPPORTED_PUBKEY_PACKET_VERSIONS, SIGNATURE_TYPE_BINARY, SIGNATURE_TYPE_CERTIFICATES, SIGNATURE_TYPE_SUB_KEY_BINDING, SUPPORTED_SIGNATURE_PACKET_VERSIONS, FULL_KEYID_SUBPACKET, PARTIAL_KEYID_SUBPACKET, SHA1,SHA256, SHA512, KEY_EXPIRATION_SUBPACKET, PRIMARY_USERID_SUBPACKET, SIG_CREATION_SUBPACKET) from securesystemslib.gpg.handlers import ( SIGNATURE_HANDLERS, SUPPORTED_SIGNATURE_ALGORITHMS) log = logging.getLogger(__name__) def parse_pubkey_payload(data): """ Parse the passed public-key packet (payload only) and construct a public key dictionary. data: An RFC4880 public key packet payload as described in section 5.5.2. (version 4) of the RFC. NOTE: The payload can be parsed from a full key packet (header + payload) by using securesystemslib.gpg.util.parse_packet_header. WARNING: this doesn't support armored pubkey packets, so use with care. pubkey packets are a little bit more complicated than the signature ones ValueError If the passed public key data is empty. securesystemslib.gpg.exceptions.PacketVersionNotSupportedError If the packet version does not match securesystemslib.gpg.constants.SUPPORTED_PUBKEY_PACKET_VERSIONS securesystemslib.gpg.exceptions.SignatureAlgorithmNotSupportedError If the signature algorithm does not match one of securesystemslib.gpg.constants.SUPPORTED_SIGNATURE_ALGORITHMS None. A public key in the format securesystemslib.formats.GPG_PUBKEY_SCHEMA """ if not data: raise ValueError("Could not parse empty pubkey payload.") ptr = 0 keyinfo = {} version_number = data[ptr] ptr += 1 if version_number not in SUPPORTED_PUBKEY_PACKET_VERSIONS: raise PacketVersionNotSupportedError( "Pubkey packet version '{}' not supported, must be one of {}".format( version_number, SUPPORTED_PUBKEY_PACKET_VERSIONS)) # NOTE: Uncomment this line to decode the time of creation time_of_creation = struct.unpack(">I", data[ptr:ptr + 4]) ptr += 4 algorithm = data[ptr] ptr += 1 # TODO: Should we only export keys with signing capabilities? # Section 5.5.2 of RFC4880 describes a public-key algorithm octet with one # of the values described in section 9.1 that could be used to determine the # capabilities. However, in case of RSA subkeys this field doesn't seem to # correctly encode the capabilities. It always has the value 1, i.e. # RSA (Encrypt or Sign). # For RSA public keys we would have to parse the subkey's signature created # with the master key, for the signature's key flags subpacket, identified # by the value 27 (see section 5.2.3.1.) containing a list of binary flags # as described in section 5.2.3.21. if algorithm not in SUPPORTED_SIGNATURE_ALGORITHMS: raise SignatureAlgorithmNotSupportedError("Signature algorithm '{}' not " "supported, please verify that your gpg configuration is creating " "either DSA, RSA, or EdDSA signatures (see RFC4880 9.1. Public-Key " "Algorithms).".format(algorithm)) keyinfo['type'] = SUPPORTED_SIGNATURE_ALGORITHMS[algorithm]['type'] keyinfo['method'] = SUPPORTED_SIGNATURE_ALGORITHMS[algorithm]['method'] handler = SIGNATURE_HANDLERS[keyinfo['type']] keyinfo['keyid'] = gpg_util.compute_keyid(data) key_params = handler.get_pubkey_params(data[ptr:]) return { "method": keyinfo['method'], "type": keyinfo['type'], "hashes": [formats.GPG_HASH_ALGORITHM_STRING], "creation_time": time_of_creation[0], "keyid": keyinfo['keyid'], "keyval" : { "private": "", "public": key_params } } def parse_pubkey_bundle(data): """ Parse packets from passed gpg public key data, associating self-signatures with the packets they correspond to, based on the structure of V4 keys defined in RFC4880 12.1 Key Structures. The returned raw key bundle may be used to further enrich the master key, with certified information (e.g. key expiration date) taken from self-signatures, and/or to verify that the parsed subkeys are bound to the primary key via signatures. data: Public key data as written to stdout by GPG_EXPORT_PUBKEY_COMMAND. securesystemslib.gpg.exceptions.PacketParsingError If data is empty. If data cannot be parsed. None. A raw public key bundle where self-signatures are associated with their corresponding packets. See `key_bundle` for details. """ if not data: raise PacketParsingError("Cannot parse keys from empty gpg data.") # Temporary data structure to hold parsed gpg packets key_bundle = { PACKET_TYPE_PRIMARY_KEY: { "key": {}, "packet": None, "signatures": [] }, PACKET_TYPE_USER_ID: collections.OrderedDict(), PACKET_TYPE_USER_ATTR: collections.OrderedDict(), PACKET_TYPE_SUB_KEY: collections.OrderedDict() } # Iterate over gpg data and parse out packets of different types position = 0 while position < len(data): try: packet_type, header_len, body_len, packet_length = \ gpg_util.parse_packet_header(data[position:]) packet = data[position:position+packet_length] payload = packet[header_len:] # The first (and only the first) packet in the bundle must be the master # key. See RFC4880 12.1 Key Structures, V4 version keys # TODO: Do we need additional key structure assertions? e.g. # - there must be least one User ID packet, or # - order and type of signatures, or # - disallow duplicate packets if packet_type != PACKET_TYPE_PRIMARY_KEY and \ not key_bundle[PACKET_TYPE_PRIMARY_KEY]["key"]: raise PacketParsingError("First packet must be a primary key ('{}'), " "got '{}'.".format(PACKET_TYPE_PRIMARY_KEY, packet_type)) elif packet_type == PACKET_TYPE_PRIMARY_KEY and \ key_bundle[PACKET_TYPE_PRIMARY_KEY]["key"]: raise PacketParsingError("Unexpected primary key.") # Fully parse master key to fail early, e.g. if key is malformed # or not supported, but also retain original packet for subkey binding # signature verification elif packet_type == PACKET_TYPE_PRIMARY_KEY: key_bundle[PACKET_TYPE_PRIMARY_KEY] = { "key": parse_pubkey_payload(bytearray(payload)), "packet": packet, "signatures": [] } # Other non-signature packets in the key bundle include User IDs and User # Attributes, required to verify primary key certificates, and subkey # packets. For each packet we create a new ordered dictionary entry. We # use a dictionary to aggregate signatures by packet below, # and it must be ordered because each signature packet belongs to the # most recently parsed packet of a type. elif packet_type in {PACKET_TYPE_USER_ID, PACKET_TYPE_USER_ATTR, PACKET_TYPE_SUB_KEY}: key_bundle[packet_type][packet] = { "header_len": header_len, "body_len": body_len, "signatures": [] } # The remaining relevant packets are signatures, required to bind subkeys # to the primary key, or to gather additional information about the # primary key, e.g. expiration date. # A signature corresponds to the most recently parsed packet of a type, # where the type is given by the availability of respective packets. # We test availability and assign accordingly as per the order of packet # types defined in RFC4880 12.1 (bottom-up). elif packet_type == PACKET_TYPE_SIGNATURE: for _type in [PACKET_TYPE_SUB_KEY, PACKET_TYPE_USER_ATTR, PACKET_TYPE_USER_ID]: if key_bundle[_type]: # Add to most recently added packet's signatures of matching type key_bundle[_type][next(reversed(key_bundle[_type]))]\ ["signatures"].append(packet) break else: # If no packets are available for any of above types (yet), the # signature belongs to the primary key key_bundle[PACKET_TYPE_PRIMARY_KEY]["signatures"].append(packet) else: log.info("Ignoring gpg key packet '{}', we only handle packets of " "types '{}' (see RFC4880 4.3. Packet Tags).".format(packet_type, [PACKET_TYPE_PRIMARY_KEY, PACKET_TYPE_USER_ID, PACKET_TYPE_USER_ATTR, PACKET_TYPE_SUB_KEY, PACKET_TYPE_SIGNATURE])) # Both errors might be raised in parse_packet_header and in this loop except (PacketParsingError, IndexError) as e: raise PacketParsingError("Invalid public key data at position {}: {}." .format(position, e)) # Go to next packet position += packet_length return key_bundle def _assign_certified_key_info(bundle): """ Helper function to verify User ID certificates corresponding to a gpg master key, in order to enrich the master key with additional information (e.g. expiration dates). The enriched master key is returned. NOTE: Currently we only consider User ID certificates. We can do the same for User Attribute certificates by iterating over bundle[PACKET_TYPE_USER_ATTR] instead of bundle[PACKET_TYPE_USER_ID], and replacing the signed_content constant '\xb4' with '\xd1' (see RFC4880 section 5.2.4. paragraph 4). bundle: GPG key bundle as parsed in parse_pubkey_bundle(). None. None. A public key in the format securesystemslib.formats.GPG_PUBKEY_SCHEMA. """ # Create handler shortcut handler = SIGNATURE_HANDLERS[bundle[PACKET_TYPE_PRIMARY_KEY]["key"]["type"]] is_primary_user = False validity_period = None sig_creation_time = None # Verify User ID signatures to gather information about primary key # (see Notes about certification signatures in RFC 4880 5.2.3.3.) for user_id_packet, packet_data in bundle[PACKET_TYPE_USER_ID].items(): # Construct signed content (see RFC4880 section 5.2.4. paragraph 4) signed_content = (bundle[PACKET_TYPE_PRIMARY_KEY]["packet"] + b"\xb4\x00\x00\x00" + user_id_packet[1:]) for signature_packet in packet_data["signatures"]: try: signature = parse_signature_packet(signature_packet, supported_hash_algorithms={SHA1, SHA256, SHA512}, supported_signature_types=SIGNATURE_TYPE_CERTIFICATES, include_info=True) # verify_signature requires a "keyid" even if it is short. # (see parse_signature_packet for more information about keyids) signature["keyid"] = signature["keyid"] or signature["short_keyid"] # TODO: Revise exception taxonomy: # It's okay to ignore some exceptions (unsupported algorithms etc.) but # we should blow up if a signature is malformed (missing subpackets). except Exception as e: log.info(e) continue if not bundle[PACKET_TYPE_PRIMARY_KEY]["key"]["keyid"].endswith( signature["keyid"]): log.info("Ignoring User ID certificate issued by '{}'.".format( signature["keyid"])) continue is_valid = handler.verify_signature(signature, bundle[PACKET_TYPE_PRIMARY_KEY]["key"], signed_content, signature["info"]["hash_algorithm"]) if not is_valid: log.info("Ignoring invalid User ID self-certificate issued " "by '{}'.".format(signature["keyid"])) continue # If the signature is valid, we try to extract subpackets relevant to # the primary key, i.e. expiration time. # NOTE: There might be multiple User IDs per primary key and multiple # certificates per User ID. RFC4880 5.2.3.19. and last paragraph of # 5.2.3.3. provides some suggestions about ambiguity, but delegates the # responsibility to the implementer. # Ambiguity resolution scheme: # We take the key expiration time from the most recent certificate, i.e. # the certificate with the highest signature creation time. Additionally, # we prioritize certificates with primary user id flag set True. Note # that, if the ultimately prioritized certificate does not have a key # expiration time subpacket, we don't assign one, even if there were # certificates of lower priority carrying that subpacket. tmp_validity_period = \ signature["info"]["subpackets"].get(KEY_EXPIRATION_SUBPACKET) # No key expiration time, go to next certificate if tmp_validity_period is None: continue # Create shortcut to mandatory pre-parsed creation time subpacket tmp_sig_creation_time = signature["info"]["creation_time"] tmp_is_primary_user = \ signature["info"]["subpackets"].get(PRIMARY_USERID_SUBPACKET) if tmp_is_primary_user is not None: tmp_is_primary_user = bool(tmp_is_primary_user[0]) # If we already have a primary user certified expiration date and this # is none, we don't consider it, and go to next certificate if is_primary_user and not tmp_is_primary_user: continue if not sig_creation_time or sig_creation_time < tmp_sig_creation_time: # This is the most recent certificate that has a validity_period and # doesn't have lower priority in regard to the primary user id flag. We # accept it the keys validty_period, until we get a newer value from # a certificate with higher priority. validity_period = struct.unpack(">I", tmp_validity_period)[0] # We also keep track of the used certificate's primary user id flag and # the signature creation time, for prioritization. is_primary_user = tmp_is_primary_user sig_creation_time = tmp_sig_creation_time if validity_period is not None: bundle[PACKET_TYPE_PRIMARY_KEY]["key"]["validity_period"] = validity_period return bundle[PACKET_TYPE_PRIMARY_KEY]["key"] def _get_verified_subkeys(bundle): """ Helper function to verify the subkey binding signature for all subkeys in the passed bundle in order to enrich subkeys with additional information (e.g. expiration dates). Only valid (i.e. parsable) subkeys that are verifiably bound to the the master key of the bundle are returned. All other subkeys are discarded. bundle: GPG key bundle as parsed in parse_pubkey_bundle(). None. None. A dictionary of public keys in the format securesystemslib.formats.GPG_PUBKEY_SCHEMA, with keyids as dict keys. """ # Create handler shortcut handler = SIGNATURE_HANDLERS[bundle[PACKET_TYPE_PRIMARY_KEY]["key"]["type"]] # Verify subkey binding signatures and only keep verified keys # See notes about subkey binding signature in RFC4880 5.2.3.3 verified_subkeys = {} for subkey_packet, packet_data in bundle[PACKET_TYPE_SUB_KEY].items(): try: # Parse subkey if possible and skip if invalid (e.g. not-supported) subkey = parse_pubkey_payload( bytearray(subkey_packet[-packet_data["body_len"]:])) # TODO: Revise exception taxonomy except Exception as e: log.info(e) continue # Construct signed content (see RFC4880 section 5.2.4. paragraph 3) signed_content = (bundle[PACKET_TYPE_PRIMARY_KEY]["packet"] + b"\x99" + subkey_packet[1:]) # Filter sub key binding signature from other signatures, e.g. subkey # binding revocation signatures key_binding_signatures = [] for signature_packet in packet_data["signatures"]: try: signature = parse_signature_packet(signature_packet, supported_hash_algorithms={SHA1, SHA256, SHA512}, supported_signature_types={SIGNATURE_TYPE_SUB_KEY_BINDING}, include_info=True) # verify_signature requires a "keyid" even if it is short. # (see parse_signature_packet for more information about keyids) signature["keyid"] = signature["keyid"] or signature["short_keyid"] key_binding_signatures.append(signature) # TODO: Revise exception taxonomy except Exception as e: log.info(e) continue # NOTE: As per the V4 key structure diagram in RFC4880 section 12.1., a # subkey must be followed by exactly one Primary-Key-Binding-Signature. # Based on inspection of real-world keys and other parts of the RFC (e.g. # the paragraph below the diagram and paragraph 0x18: Subkey Binding # Signature in section 5.2.1.) the mandated signature is actually a # *subkey binding signature*, which in case of a signing subkey, must have # an *embedded primary key binding signature*. if len(key_binding_signatures) != 1: log.info("Ignoring subkey '{}' due to wrong amount of key binding " "signatures ({}), must be exactly 1.".format(subkey["keyid"], len(key_binding_signatures))) continue is_valid = handler.verify_signature(signature, bundle[PACKET_TYPE_PRIMARY_KEY]["key"], signed_content, signature["info"]["hash_algorithm"]) if not is_valid: log.info("Ignoring subkey '{}' due to invalid key binding signature." .format(subkey["keyid"])) continue # If the signature is valid, we may also extract relevant information from # its "info" field (e.g. subkey expiration date) and assign to it to the # subkey here validity_period = \ signature["info"]["subpackets"].get(KEY_EXPIRATION_SUBPACKET) if validity_period is not None: subkey["validity_period"] = struct.unpack(">I", validity_period)[0] verified_subkeys[subkey["keyid"]] = subkey return verified_subkeys def get_pubkey_bundle(data, keyid): """ Call function to extract and verify master key and subkeys from the passed gpg key data, where either the master key or one of the subkeys matches the passed keyid. NOTE: - If the keyid matches one of the subkeys, a warning is issued to notify the user about potential privilege escalation - Subkeys with invalid key binding signatures are discarded data: Public key data as written to stdout by securesystemslib.gpg.constants.GPG_EXPORT_PUBKEY_COMMAND. keyid: The keyid of the master key or one of its subkeys expected to be contained in the passed gpg data. securesystemslib.gpg.exceptions.PacketParsingError If the key data could not be parsed securesystemslib.gpg.exceptions.KeyNotFoundError If the passed data is empty. If no master key or subkeys could be found that matches the passed keyid. securesystemslib.exceptions.FormatError If the passed keyid does not match securesystemslib.formats.KEYID_SCHEMA None. A public key in the format securesystemslib.formats.GPG_PUBKEY_SCHEMA with optional subkeys. """ formats.KEYID_SCHEMA.check_match(keyid) if not data: raise KeyNotFoundError("Could not find gpg key '{}' in empty exported key " "data.".format(keyid)) # Parse out master key and subkeys (enriched and verified via certificates # and binding signatures) raw_key_bundle = parse_pubkey_bundle(data) master_public_key = _assign_certified_key_info(raw_key_bundle) sub_public_keys = _get_verified_subkeys(raw_key_bundle) # Since GPG returns all pubkeys associated with a keyid (master key and # subkeys) we check which key matches the passed keyid. # If the matching key is a subkey, we warn the user because we return # the whole bundle (master plus all subkeys) and not only the subkey. # If no matching key is found we raise a KeyNotFoundError. for idx, public_key in enumerate( [master_public_key] + list(sub_public_keys.values())): if public_key and public_key["keyid"].endswith(keyid.lower()): if idx > 1: log.warning("Exporting master key '{}' including subkeys '{}' for" " passed keyid '{}'.".format(master_public_key["keyid"], ", ".join(list(sub_public_keys.keys())), keyid)) break else: raise KeyNotFoundError("Could not find gpg key '{}' in exported key data." .format(keyid)) # Add subkeys dictionary to master pubkey "subkeys" field if subkeys exist if sub_public_keys: master_public_key["subkeys"] = sub_public_keys return master_public_key def parse_signature_packet(data, supported_signature_types=None, supported_hash_algorithms=None, include_info=False): """ Parse the signature information on an RFC4880-encoded binary signature data buffer. NOTE: Older gpg versions (< FULLY_SUPPORTED_MIN_VERSION) might only reveal the partial key id. It is the callers responsibility to determine the full keyid based on the partial keyid, e.g. by exporting the related public and replacing the partial keyid with the full keyid. data: the RFC4880-encoded binary signature data buffer as described in section 5.2 (and 5.2.3.1). supported_signature_types: (optional) a set of supported signature_types, the signature packet may be (see securesystemslib.gpg.constants for available types). If None is specified the signature packet must be of type SIGNATURE_TYPE_BINARY. supported_hash_algorithms: (optional) a set of supported hash algorithm ids, the signature packet may use. Available ids are SHA1, SHA256, SHA512 (see securesystemslib.gpg.constants). If None is specified, the signature packet must use SHA256. include_info: (optional) a boolean that indicates whether an opaque dictionary should be added to the returned signature under the key "info". Default is False. ValueError: if the signature packet is not supported or the data is malformed IndexError: if the signature packet is incomplete None. A signature dictionary matching securesystemslib.formats.GPG_SIGNATURE_SCHEMA with the following special characteristics: - The "keyid" field is an empty string if it cannot be determined - The "short_keyid" is not added if it cannot be determined - At least one of non-empty "keyid" or "short_keyid" are part of the signature """ if not supported_signature_types: supported_signature_types = {SIGNATURE_TYPE_BINARY} if not supported_hash_algorithms: supported_hash_algorithms = {SHA256} _, header_len, _, packet_len = gpg_util.parse_packet_header( data, PACKET_TYPE_SIGNATURE) data = bytearray(data[header_len:packet_len]) ptr = 0 # we get the version number, which we also expect to be v4, or we bail # FIXME: support v3 type signatures (which I haven't seen in the wild) version_number = data[ptr] ptr += 1 if version_number not in SUPPORTED_SIGNATURE_PACKET_VERSIONS: raise ValueError("Signature version '{}' not supported, must be one of " "{}.".format(version_number, SUPPORTED_SIGNATURE_PACKET_VERSIONS)) # Per default we only parse "signatures of a binary document". Other types # may be allowed by passing type constants via `supported_signature_types`. # Types include revocation signatures, key binding signatures, persona # certifications, etc. (see RFC 4880 section 5.2.1.). signature_type = data[ptr] ptr += 1 if signature_type not in supported_signature_types: raise ValueError("Signature type '{}' not supported, must be one of {} " "(see RFC4880 5.2.1. Signature Types).".format(signature_type, supported_signature_types)) signature_algorithm = data[ptr] ptr += 1 if signature_algorithm not in SUPPORTED_SIGNATURE_ALGORITHMS: raise ValueError("Signature algorithm '{}' not " "supported, please verify that your gpg configuration is creating " "either DSA, RSA, or EdDSA signatures (see RFC4880 9.1. Public-Key " "Algorithms).".format(signature_algorithm)) key_type = SUPPORTED_SIGNATURE_ALGORITHMS[signature_algorithm]['type'] handler = SIGNATURE_HANDLERS[key_type] hash_algorithm = data[ptr] ptr += 1 if hash_algorithm not in supported_hash_algorithms: raise ValueError("Hash algorithm '{}' not supported, must be one of {}" " (see RFC4880 9.4. Hash Algorithms).".format(hash_algorithm, supported_hash_algorithms)) # Obtain the hashed octets hashed_octet_count = struct.unpack(">H", data[ptr:ptr+2])[0] ptr += 2 hashed_subpackets = data[ptr:ptr+hashed_octet_count] hashed_subpacket_info = gpg_util.parse_subpackets(hashed_subpackets) # Check whether we were actually able to read this much hashed octets if len(hashed_subpackets) != hashed_octet_count: # pragma: no cover raise ValueError("This signature packet seems to be corrupted." "It is missing hashed octets!") ptr += hashed_octet_count other_headers_ptr = ptr unhashed_octet_count = struct.unpack(">H", data[ptr: ptr + 2])[0] ptr += 2 unhashed_subpackets = data[ptr:ptr+unhashed_octet_count] unhashed_subpacket_info = gpg_util.parse_subpackets(unhashed_subpackets) ptr += unhashed_octet_count # Use the info dict to return further signature information that may be # needed for intermediate processing, but does not have to be on the eventual # signature datastructure info = { "signature_type": signature_type, "hash_algorithm": hash_algorithm, "creation_time": None, "subpackets": {}, } keyid = "" short_keyid = "" # Parse "Issuer" (short keyid) and "Issuer Fingerprint" (full keyid) type # subpackets # Strategy: Loop over all unhashed and hashed subpackets (in that order!) and # store only the last of a type. Due to the order in the loop, hashed # subpackets are prioritized over unhashed subpackets (see NOTEs below). # NOTE: A subpacket may be found either in the hashed or unhashed subpacket # sections of a signature. If a subpacket is not hashed, then the information # in it cannot be considered definitive because it is not part of the # signature proper. (see RFC4880 5.2.3.2.) # NOTE: Signatures may contain conflicting information in subpackets. In most # cases, an implementation SHOULD use the last subpacket, but MAY use any # conflict resolution scheme that makes more sense. (see RFC4880 5.2.4.1.) for idx, subpacket_tuple in \ enumerate(unhashed_subpacket_info + hashed_subpacket_info): # The idx indicates if the info is from the unhashed (first) or # hashed (second) of the above concatenated lists is_hashed = (idx >= len(unhashed_subpacket_info)) subpacket_type, subpacket_data = subpacket_tuple # Warn if expiration subpacket is not hashed if subpacket_type == KEY_EXPIRATION_SUBPACKET: if not is_hashed: log.warning("Expiration subpacket not hashed, gpg client possibly " "exporting a weakly configured key.") # Full keyids are only available in newer signatures # (see RFC4880 and rfc4880bis-06 5.2.3.1.) if subpacket_type == FULL_KEYID_SUBPACKET: # pragma: no cover # Exclude from coverage for consistent results across test envs # NOTE: The first byte of the subpacket payload is a version number # (see rfc4880bis-06 5.2.3.28.) keyid = binascii.hexlify(subpacket_data[1:]).decode("ascii") # We also return the short keyid, because the full might not be available if subpacket_type == PARTIAL_KEYID_SUBPACKET: short_keyid = binascii.hexlify(subpacket_data).decode("ascii") if subpacket_type == SIG_CREATION_SUBPACKET: info["creation_time"] = struct.unpack(">I", subpacket_data)[0] info["subpackets"][subpacket_type] = subpacket_data # Fail if there is no keyid at all (this should not happen) if not (keyid or short_keyid): # pragma: no cover raise ValueError("This signature packet seems to be corrupted. It does " "not have an 'Issuer' or 'Issuer Fingerprint' subpacket (see RFC4880 " "and rfc4880bis-06 5.2.3.1. Signature Subpacket Specification).") # Fail if keyid and short keyid are specified but don't match if keyid and not keyid.endswith(short_keyid): # pragma: no cover raise ValueError("This signature packet seems to be corrupted. The key ID " "'{}' of the 'Issuer' subpacket must match the lower 64 bits of the " "fingerprint '{}' of the 'Issuer Fingerprint' subpacket (see RFC4880 " "and rfc4880bis-06 5.2.3.28. Issuer Fingerprint).".format( short_keyid, keyid)) if not info["creation_time"]: # pragma: no cover raise ValueError("This signature packet seems to be corrupted. It does " "not have a 'Signature Creation Time' subpacket (see RFC4880 5.2.3.4 " "Signature Creation Time).") # Uncomment this variable to obtain the left-hash-bits information (used for # early rejection) #left_hash_bits = struct.unpack(">H", data[ptr:ptr+2])[0] ptr += 2 # Finally, fetch the actual signature (as opposed to signature metadata). signature = handler.get_signature_params(data[ptr:]) signature_data = { 'keyid': "{}".format(keyid), 'other_headers': binascii.hexlify(data[:other_headers_ptr]).decode('ascii'), 'signature': binascii.hexlify(signature).decode('ascii') } if short_keyid: # pragma: no branch signature_data["short_keyid"] = short_keyid if include_info: signature_data["info"] = info return signature_data ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/gpg/constants.py0000644000076500000240000000546300000000000024055 0ustar00lukpstaff00000000000000""" constants.py Santiago Torres-Arias Nov 15, 2017 See LICENSE for licensing information. aggregates all the constant definitions and lookup structures for signature handling """ import logging import os from securesystemslib import process log = logging.getLogger(__name__) def is_available_gnupg(gnupg): gpg_version_cmd = gnupg + " --version" try: process.run(gpg_version_cmd, stdout=process.PIPE, stderr=process.PIPE) return True except OSError: return False GPG_COMMAND = "" HAVE_GPG = False GPG_ENV_COMMAND = os.environ.get('GNUPG') GPG2_COMMAND = "gpg2" GPG1_COMMAND = "gpg" # By default, we allow providing GPG client through the environment # assuming gpg2 as default value and test if exists. Otherwise, we assume gpg # exists. if GPG_ENV_COMMAND: if is_available_gnupg(GPG_ENV_COMMAND): GPG_COMMAND = GPG_ENV_COMMAND elif is_available_gnupg(GPG2_COMMAND): GPG_COMMAND = GPG2_COMMAND elif is_available_gnupg(GPG1_COMMAND): GPG_COMMAND = GPG1_COMMAND if GPG_COMMAND: # Use bool to skip tests or fail early and gracefully if no gpg is available HAVE_GPG = True GPG_VERSION_COMMAND = GPG_COMMAND + " --version" FULLY_SUPPORTED_MIN_VERSION = "2.1.0" NO_GPG_MSG = "GPG support requires a GPG client. 'gpg2' or 'gpg' with version {} or newer is" \ " fully supported.".format(FULLY_SUPPORTED_MIN_VERSION) GPG_SIGN_COMMAND = GPG_COMMAND + \ " --detach-sign --digest-algo SHA256 {keyarg} {homearg}" GPG_EXPORT_PUBKEY_COMMAND = GPG_COMMAND + " {homearg} --export {keyid}" # See RFC4880 section 4.3. Packet Tags for a list of all packet types The # relevant packets defined below are described in sections 5.2 (signature), # 5.5.1.1 (primary pubkey) and 5.5.1.2 (pub subkey), 5.12 (user id) and 5.13 # (user attribute) PACKET_TYPE_SIGNATURE = 0x02 PACKET_TYPE_PRIMARY_KEY = 0x06 PACKET_TYPE_USER_ID = 0x0D PACKET_TYPE_USER_ATTR = 0x11 PACKET_TYPE_SUB_KEY = 0x0E # See sections 5.2.3 (signature) and 5.5.2 (public key) of RFC4880 SUPPORTED_SIGNATURE_PACKET_VERSIONS = {0x04} SUPPORTED_PUBKEY_PACKET_VERSIONS = {0x04} # The constants for hash algorithms are taken from section 9.4 of RFC4880. SHA1 = 0x02 SHA256 = 0x08 SHA512 = 0x0A # See section 5.2.1 of RFC4880 SIGNATURE_TYPE_BINARY = 0x00 SIGNATURE_TYPE_SUB_KEY_BINDING = 0x18 SIGNATURE_TYPE_CERTIFICATES = {0x10, 0x11, 0x12, 0x13} # See section 5.2.3.4 (Signature Creation Time) of RFC4880 SIG_CREATION_SUBPACKET = 0x02 # See section 5.2.3.5. (Issuer) of RFC4880 PARTIAL_KEYID_SUBPACKET = 0x10 # See section 5.2.3.6 (Key Expiration Time) of RFC4880 KEY_EXPIRATION_SUBPACKET = 0x09 # See section 5.2.3.19 (Primary User ID) of RFC4880 PRIMARY_USERID_SUBPACKET = 0x19 # See section 5.2.3.28. (Issuer Fingerprint) of rfc4880bis-06 FULL_KEYID_SUBPACKET = 0x21 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/gpg/dsa.py0000644000076500000240000001600300000000000022600 0ustar00lukpstaff00000000000000""" dsa.py Santiago Torres-Arias Nov 15, 2017 See LICENSE for licensing information. DSA-specific handling routines for signature verification and key parsing """ import binascii CRYPTO = True NO_CRYPTO_MSG = 'DSA key support for GPG requires the cryptography library' try: from cryptography.exceptions import InvalidSignature from cryptography.hazmat import backends from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.hazmat.primitives.asymmetric import utils as dsautils except ImportError: CRYPTO = False from securesystemslib import exceptions from securesystemslib import formats from securesystemslib.gpg.exceptions import PacketParsingError from securesystemslib.gpg import util as gpg_util def create_pubkey(pubkey_info): """ Create and return a DSAPublicKey object from the passed pubkey_info using pyca/cryptography. pubkey_info: The DSA pubkey info dictionary as specified by securesystemslib.formats.GPG_DSA_PUBKEY_SCHEMA securesystemslib.exceptions.FormatError if pubkey_info does not match securesystemslib.formats.GPG_DSA_PUBKEY_SCHEMA securesystemslib.exceptions.UnsupportedLibraryError if the cryptography module is not available A cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey based on the passed pubkey_info. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) formats.GPG_DSA_PUBKEY_SCHEMA.check_match(pubkey_info) y = int(pubkey_info['keyval']['public']['y'], 16) g = int(pubkey_info['keyval']['public']['g'], 16) p = int(pubkey_info['keyval']['public']['p'], 16) q = int(pubkey_info['keyval']['public']['q'], 16) parameter_numbers = dsa.DSAParameterNumbers(p, q, g) pubkey = dsa.DSAPublicNumbers(y, parameter_numbers).public_key( backends.default_backend()) return pubkey def get_pubkey_params(data): """ Parse the public-key parameters as multi-precision-integers. data: the RFC4880-encoded public key parameters data buffer as described in the fifth paragraph of section 5.5.2. securesystemslib.gpg.exceptions.PacketParsingError: if the public key parameters are malformed None. The parsed DSA public key in the format securesystemslib.formats.GPG_DSA_PUBKEY_SCHEMA. """ ptr = 0 prime_p_length = gpg_util.get_mpi_length(data[ptr: ptr + 2]) ptr += 2 prime_p = data[ptr:ptr + prime_p_length] if len(prime_p) != prime_p_length: # pragma: no cover raise PacketParsingError("This MPI was truncated!") ptr += prime_p_length group_order_q_length = gpg_util.get_mpi_length(data[ptr: ptr + 2]) ptr += 2 group_order_q = data[ptr:ptr + group_order_q_length] if len(group_order_q) != group_order_q_length: # pragma: no cover raise PacketParsingError("This MPI has been truncated!") ptr += group_order_q_length generator_length = gpg_util.get_mpi_length(data[ptr: ptr + 2]) ptr += 2 generator = data[ptr:ptr + generator_length] if len(generator) != generator_length: # pragma: no cover raise PacketParsingError("This MPI has been truncated!") ptr += generator_length value_y_length = gpg_util.get_mpi_length(data[ptr: ptr + 2]) ptr += 2 value_y = data[ptr:ptr + value_y_length] if len(value_y) != value_y_length: # pragma: no cover raise PacketParsingError("This MPI has been truncated!") return { "y": binascii.hexlify(value_y).decode('ascii'), "p": binascii.hexlify(prime_p).decode("ascii"), "g": binascii.hexlify(generator).decode("ascii"), "q": binascii.hexlify(group_order_q).decode("ascii"), } def get_signature_params(data): """ Parse the signature parameters as multi-precision-integers. data: the RFC4880-encoded signature data buffer as described in the fourth paragraph of section 5.2.2 securesystemslib.gpg.exceptions.PacketParsingError: if the public key parameters are malformed securesystemslib.exceptions.UnsupportedLibraryError: if the cryptography module is not available None. The decoded signature buffer """ if not CRYPTO: # pragma: no cover return exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) ptr = 0 r_length = gpg_util.get_mpi_length(data[ptr:ptr+2]) ptr += 2 r = data[ptr:ptr + r_length] if len(r) != r_length: # pragma: no cover raise PacketParsingError("r-value truncated in signature") ptr += r_length s_length = gpg_util.get_mpi_length(data[ptr: ptr+2]) ptr += 2 s = data[ptr: ptr + s_length] if len(s) != s_length: # pragma: no cover raise PacketParsingError("s-value truncated in signature") s = int(binascii.hexlify(s), 16) r = int(binascii.hexlify(r), 16) signature = dsautils.encode_dss_signature(r, s) return signature def verify_signature(signature_object, pubkey_info, content, hash_algorithm_id): """ Verify the passed signature against the passed content with the passed DSA public key using pyca/cryptography. signature_object: A signature dictionary as specified by securesystemslib.formats.GPG_SIGNATURE_SCHEMA pubkey_info: The DSA public key info dictionary as specified by securesystemslib.formats.GPG_DSA_PUBKEY_SCHEMA hash_algorithm_id: one of SHA1, SHA256, SHA512 (see securesystemslib.gpg.constants) used to verify the signature NOTE: Overrides any hash algorithm specification in "pubkey_info"'s "hashes" or "method" fields. content: The signed bytes against which the signature is verified securesystemslib.exceptions.FormatError if: signature_object does not match securesystemslib.formats.GPG_SIGNATURE_SCHEMA pubkey_info does not match securesystemslib.formats.GPG_DSA_PUBKEY_SCHEMA securesystemslib.exceptions.UnsupportedLibraryError if: the cryptography module is not available ValueError: if the passed hash_algorithm_id is not supported (see securesystemslib.gpg.util.get_hashing_class) True if signature verification passes and False otherwise """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) formats.GPG_SIGNATURE_SCHEMA.check_match(signature_object) formats.GPG_DSA_PUBKEY_SCHEMA.check_match(pubkey_info) hasher = gpg_util.get_hashing_class(hash_algorithm_id) pubkey_object = create_pubkey(pubkey_info) digest = gpg_util.hash_object( binascii.unhexlify(signature_object['other_headers']), hasher(), content) try: pubkey_object.verify( binascii.unhexlify(signature_object['signature']), digest, dsautils.Prehashed(hasher()) ) return True except InvalidSignature: return False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/gpg/eddsa.py0000644000076500000240000001573200000000000023121 0ustar00lukpstaff00000000000000""" eddsa.py Lukas Puehringer Oct 22, 2019 See LICENSE for licensing information. EdDSA/ed25519 algorithm-specific handling routines for pubkey and signature parsing and verification. """ import binascii from securesystemslib import exceptions from securesystemslib import formats from securesystemslib.gpg import util as gpg_util from securesystemslib.gpg.exceptions import PacketParsingError CRYPTO = True NO_CRYPTO_MSG = 'EdDSA key support for GPG requires the cryptography library' try: from cryptography.hazmat.primitives.asymmetric import ed25519 as pyca_ed25519 from cryptography.exceptions import InvalidSignature except ImportError: CRYPTO = False # ECC Curve OID (see RFC4880-bis8 9.2.) ED25519_PUBLIC_KEY_OID = bytearray.fromhex("2B 06 01 04 01 DA 47 0F 01") # EdDSA Point Format (see RFC4880-bis8 13.3.) ED25519_PUBLIC_KEY_LENGTH = 33 ED25519_PUBLIC_KEY_PREFIX = 0x40 # EdDSA signature byte length (see RFC 8032 5.1.6. (6)) ED25519_SIG_LENGTH = 64 def get_pubkey_params(data): """ Parse algorithm-specific part for EdDSA public keys See RFC4880-bis8 sections 5.6.5. Algorithm-Specific Part for EdDSA Keys, 9.2. ECC Curve OID and 13.3. EdDSA Point Format for more details. data: The EdDSA public key data AFTER the one-octet number denoting the public-key algorithm of this key. securesystemslib.gpg.exceptions.PacketParsingError or IndexError: if the public key data is malformed. None. A dictionary with an element "q" that holds the ascii hex representation of the MPI of an EC point representing an EdDSA public key that conforms with securesystemslib.formats.GPG_ED25519_PUBKEY_SCHEMA. """ ptr = 0 curve_oid_len = data[ptr] ptr += 1 curve_oid = data[ptr:ptr + curve_oid_len] ptr += curve_oid_len # See 9.2. ECC Curve OID if curve_oid != ED25519_PUBLIC_KEY_OID: raise PacketParsingError( "bad ed25519 curve OID '{}', expected {}'".format( curve_oid, ED25519_PUBLIC_KEY_OID)) # See 13.3. EdDSA Point Format public_key_len = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 if public_key_len != ED25519_PUBLIC_KEY_LENGTH: raise PacketParsingError( "bad ed25519 MPI length '{}', expected {}'".format( public_key_len, ED25519_PUBLIC_KEY_LENGTH)) public_key_prefix = data[ptr] ptr += 1 if public_key_prefix != ED25519_PUBLIC_KEY_PREFIX: raise PacketParsingError( "bad ed25519 MPI prefix '{}', expected '{}'".format( public_key_prefix, ED25519_PUBLIC_KEY_PREFIX)) public_key = data[ptr:ptr + public_key_len - 1] return { "q": binascii.hexlify(public_key).decode("ascii") } def get_signature_params(data): """ Parse algorithm-specific fields for EdDSA signatures. See RFC4880-bis8 section 5.2.3. Version 4 and 5 Signature Packet Formats for more details. data: The EdDSA signature data AFTER the two-octet field holding the left 16 bits of the signed hash value. IndexError if the signature data is malformed. None. The concatenation of the parsed MPI R and S values of the EdDSA signature, i.e. ENC(R) || ENC(S) (see RFC8032 3.4 Verify). """ ptr = 0 r_length = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 r = data[ptr:ptr + r_length] ptr += r_length s_length = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 s = data[ptr:ptr + s_length] # Left-zero-pad 'r' and 's' values that are shorter than required by RFC 8032 # (5.1.6.), to make up for omitted leading zeros in RFC 4880 (3.2.) MPIs. # This is especially important for 's', which is little-endian. r = r.rjust(ED25519_SIG_LENGTH // 2, b"\x00") s = s.rjust(ED25519_SIG_LENGTH // 2, b"\x00") return r + s def create_pubkey(pubkey_info): """ Create and return an Ed25519PublicKey object from the passed pubkey_info using pyca/cryptography. pubkey_info: The ED25519 public key dictionary as specified by securesystemslib.formats.GPG_ED25519_PUBKEY_SCHEMA securesystemslib.exceptions.FormatError if pubkey_info does not match securesystemslib.formats.GPG_DSA_PUBKEY_SCHEMA securesystemslib.exceptions.UnsupportedLibraryError if the cryptography module is unavailable A cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey based on the passed pubkey_info. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) formats.GPG_ED25519_PUBKEY_SCHEMA.check_match(pubkey_info) public_bytes = binascii.unhexlify(pubkey_info["keyval"]["public"]["q"]) public_key = pyca_ed25519.Ed25519PublicKey.from_public_bytes(public_bytes) return public_key def verify_signature(signature_object, pubkey_info, content, hash_algorithm_id): """ Verify the passed signature against the passed content with the passed ED25519 public key using pyca/cryptography. signature_object: A signature dictionary as specified by securesystemslib.formats.GPG_SIGNATURE_SCHEMA pubkey_info: The DSA public key info dictionary as specified by securesystemslib.formats.GPG_ED25519_PUBKEY_SCHEMA hash_algorithm_id: one of SHA1, SHA256, SHA512 (see securesystemslib.gpg.constants) used to verify the signature NOTE: Overrides any hash algorithm specification in "pubkey_info"'s "hashes" or "method" fields. content: The signed bytes against which the signature is verified securesystemslib.exceptions.FormatError if: signature_object does not match securesystemslib.formats.GPG_SIGNATURE_SCHEMA pubkey_info does not match securesystemslib.formats.GPG_ED25519_PUBKEY_SCHEMA securesystemslib.exceptions.UnsupportedLibraryError if: the cryptography module is unavailable ValueError: if the passed hash_algorithm_id is not supported (see securesystemslib.gpg.util.get_hashing_class) True if signature verification passes and False otherwise. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) formats.GPG_SIGNATURE_SCHEMA.check_match(signature_object) formats.GPG_ED25519_PUBKEY_SCHEMA.check_match(pubkey_info) hasher = gpg_util.get_hashing_class(hash_algorithm_id) pubkey_object = create_pubkey(pubkey_info) # See RFC4880-bis8 14.8. EdDSA and 5.2.4 "Computing Signatures" digest = gpg_util.hash_object( binascii.unhexlify(signature_object["other_headers"]), hasher(), content) try: pubkey_object.verify( binascii.unhexlify(signature_object["signature"]), digest ) return True except InvalidSignature: return False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/gpg/exceptions.py0000644000076500000240000000246700000000000024223 0ustar00lukpstaff00000000000000""" exceptions.py Santiago Torres-Arias Lukas Puehringer Dec 8, 2017 See LICENSE for licensing information. Define Exceptions used in the gpg package. Following the practice from securesystemslib the names chosen for exception classes should end in 'Error' (except where there is a good reason not to). """ import datetime class PacketParsingError(Exception): pass class KeyNotFoundError(Exception): pass class PacketVersionNotSupportedError(Exception): pass class SignatureAlgorithmNotSupportedError(Exception): pass class CommandError(Exception): pass class KeyExpirationError(Exception): def __init__(self, key): super(KeyExpirationError, self).__init__() self.key = key def __str__(self): creation_time = datetime.datetime.utcfromtimestamp( self.key["creation_time"]) expiration_time = datetime.datetime.utcfromtimestamp( self.key["creation_time"] + self.key["validity_period"]) validity_period = expiration_time - creation_time return ("GPG key '{}' created on '{:%Y-%m-%d %H:%M} UTC' with validity " "period '{}' expired on '{:%Y-%m-%d %H:%M} UTC'.".format( self.key["keyid"], creation_time, validity_period, expiration_time)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/gpg/functions.py0000644000076500000240000002421500000000000024045 0ustar00lukpstaff00000000000000""" functions.py Santiago Torres-Arias Nov 15, 2017 See LICENSE for licensing information. publicly-usable functions for exporting public-keys, signing data and verifying signatures. """ import logging import time from securesystemslib import exceptions from securesystemslib import formats from securesystemslib.gpg.common import ( get_pubkey_bundle, parse_signature_packet) from securesystemslib.gpg.exceptions import ( CommandError, KeyExpirationError) from securesystemslib.gpg.constants import ( FULLY_SUPPORTED_MIN_VERSION, GPG_EXPORT_PUBKEY_COMMAND, GPG_SIGN_COMMAND, HAVE_GPG, NO_GPG_MSG, SHA256) from securesystemslib.gpg.handlers import ( SIGNATURE_HANDLERS) from securesystemslib import process from securesystemslib.gpg.rsa import CRYPTO log = logging.getLogger(__name__) NO_CRYPTO_MSG = "GPG support requires the cryptography library" def create_signature(content, keyid=None, homedir=None): """ Calls the gpg command line utility to sign the passed content with the key identified by the passed keyid from the gpg keyring at the passed homedir. The executed base command is defined in securesystemslib.gpgp.constants.GPG_SIGN_COMMAND. NOTE: On not fully supported versions of GPG, i.e. versions below securesystemslib.gpg.constants.FULLY_SUPPORTED_MIN_VERSION the returned signature does not contain the full keyid. As a work around, we export the public key bundle identified by the short keyid to compute the full keyid and add it to the returned signature. content: The content to be signed. (bytes) keyid: (optional) The keyid of the gpg signing keyid. If not passed the default key in the keyring is used. homedir: (optional) Path to the gpg keyring. If not passed the default keyring is used. securesystemslib.exceptions.FormatError: If the keyid was passed and does not match securesystemslib.formats.KEYID_SCHEMA ValueError: If the gpg command failed to create a valid signature. OSError: If the gpg command is not present or non-executable. securesystemslib.exceptions.UnsupportedLibraryError: If the gpg command is not available, or the cryptography library is not installed. securesystemslib.gpg.exceptions.CommandError: If the gpg command returned a non-zero exit code securesystemslib.gpg.exceptions.KeyNotFoundError: If the used gpg version is not fully supported and no public key can be found for short keyid. None. The created signature in the format: securesystemslib.formats.GPG_SIGNATURE_SCHEMA. """ if not HAVE_GPG: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_GPG_MSG) if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) keyarg = "" if keyid: formats.KEYID_SCHEMA.check_match(keyid) keyarg = "--local-user {}".format(keyid) homearg = "" if homedir: homearg = "--homedir {}".format(homedir).replace("\\", "/") command = GPG_SIGN_COMMAND.format(keyarg=keyarg, homearg=homearg) gpg_process = process.run(command, input=content, check=False, stdout=process.PIPE, stderr=process.PIPE) # TODO: It's suggested to take a look at `--status-fd` for proper error # reporting, as there is no clear distinction between the return codes # https://lists.gnupg.org/pipermail/gnupg-devel/2005-December/022559.html if gpg_process.returncode != 0: raise CommandError("Command '{}' returned " "non-zero exit status '{}', stderr was:\n{}.".format(gpg_process.args, gpg_process.returncode, gpg_process.stderr.decode())) signature_data = gpg_process.stdout signature = parse_signature_packet(signature_data) # On GPG < 2.1 we cannot derive the full keyid from the signature data. # Instead we try to compute the keyid from the public part of the signing # key or its subkeys, identified by the short keyid. # parse_signature_packet is guaranteed to return at least one of keyid or # short_keyid. # Exclude the following code from coverage for consistent coverage across # test environments. if not signature["keyid"]: # pragma: no cover log.warning("The created signature does not include the hashed subpacket" " '33' (full keyid). You probably have a gpg version <{}." " We will export the public keys associated with the short keyid to" " compute the full keyid.".format(FULLY_SUPPORTED_MIN_VERSION)) short_keyid = signature["short_keyid"] # Export public key bundle (master key including with optional subkeys) public_key_bundle = export_pubkey(short_keyid, homedir) # Test if the short keyid matches the master key ... master_key_full_keyid = public_key_bundle["keyid"] if master_key_full_keyid.endswith(short_keyid.lower()): signature["keyid"] = master_key_full_keyid # ... or one of the subkeys, and add the full keyid to the signature dict. else: for sub_key_full_keyid in list( public_key_bundle.get("subkeys", {}).keys()): if sub_key_full_keyid.endswith(short_keyid.lower()): signature["keyid"] = sub_key_full_keyid break # If there is still no full keyid something went wrong if not signature["keyid"]: # pragma: no cover raise ValueError("Full keyid could not be determined for signature '{}'". format(signature)) # It is okay now to remove the optional short keyid to save space signature.pop("short_keyid", None) return signature def verify_signature(signature_object, pubkey_info, content): """ Verifies the passed signature against the passed content using the passed public key, or one of its subkeys, associated by the signature's keyid. The function selects the appropriate verification algorithm (rsa or dsa) based on the "type" field in the passed public key object. signature_object: A signature object in the format: securesystemslib.formats.GPG_SIGNATURE_SCHEMA pubkey_info: A public key object in the format: securesystemslib.formats.GPG_PUBKEY_SCHEMA content: The content to be verified. (bytes) securesystemslib.gpg.exceptions.KeyExpirationError: if the passed public key has expired securesystemslib.exceptions.UnsupportedLibraryError: if the cryptography module is unavailable None. True if signature verification passes, False otherwise. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) formats.GPG_PUBKEY_SCHEMA.check_match(pubkey_info) formats.GPG_SIGNATURE_SCHEMA.check_match(signature_object) handler = SIGNATURE_HANDLERS[pubkey_info['type']] sig_keyid = signature_object["keyid"] verification_key = pubkey_info # If the keyid on the signature matches a subkey of the passed key, # we use that subkey for verification instead of the master key. if sig_keyid in list(pubkey_info.get("subkeys", {}).keys()): verification_key = pubkey_info["subkeys"][sig_keyid] creation_time = verification_key.get("creation_time") validity_period = verification_key.get("validity_period") if creation_time and validity_period and \ creation_time + validity_period < time.time(): raise KeyExpirationError(verification_key) return handler.verify_signature( signature_object, verification_key, content, SHA256) def export_pubkey(keyid, homedir=None): """Exports a public key from a GnuPG keyring. Arguments: keyid: An OpenPGP keyid in KEYID_SCHEMA format. homedir (optional): A path to the GnuPG home directory. If not set the default GnuPG home directory is used. Raises: ValueError: Keyid is not a string. UnsupportedLibraryError: The gpg command or pyca/cryptography are not available. KeyNotFoundError: No key or subkey was found for that keyid. Side Effects: Calls system gpg command in a subprocess. Returns: An OpenPGP public key object in GPG_PUBKEY_SCHEMA format. """ if not HAVE_GPG: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_GPG_MSG) if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) if not formats.KEYID_SCHEMA.matches(keyid): # FIXME: probably needs smarter parsing of what a valid keyid is so as to # not export more than one pubkey packet. raise ValueError("we need to export an individual key. Please provide a " " valid keyid! Keyid was '{}'.".format(keyid)) homearg = "" if homedir: homearg = "--homedir {}".format(homedir).replace("\\", "/") # TODO: Consider adopting command error handling from `create_signature` # above, e.g. in a common 'run gpg command' utility function command = GPG_EXPORT_PUBKEY_COMMAND.format(keyid=keyid, homearg=homearg) gpg_process = process.run(command, stdout=process.PIPE, stderr=process.PIPE) key_packet = gpg_process.stdout key_bundle = get_pubkey_bundle(key_packet, keyid) return key_bundle def export_pubkeys(keyids, homedir=None): """Exports multiple public keys from a GnuPG keyring. Arguments: keyids: A list of OpenPGP keyids in KEYID_SCHEMA format. homedir (optional): A path to the GnuPG home directory. If not set the default GnuPG home directory is used. Raises: TypeError: Keyids is not iterable. ValueError: A Keyid is not a string. UnsupportedLibraryError: The gpg command or pyca/cryptography are not available. KeyNotFoundError: No key or subkey was found for that keyid. Side Effects: Calls system gpg command in a subprocess. Returns: A dict of OpenPGP public key objects in GPG_PUBKEY_SCHEMA format as values, and their keyids as dict keys. """ public_key_dict = {} for gpg_keyid in keyids: public_key = export_pubkey(gpg_keyid, homedir=homedir) keyid = public_key["keyid"] public_key_dict[keyid] = public_key return public_key_dict ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/gpg/handlers.py0000644000076500000240000000156100000000000023634 0ustar00lukpstaff00000000000000""" handlers.py Santiago Torres-Arias Jan 15, 2020 See LICENSE for licensing information. Provides links from signatures/algorithms to modules implementing the signature verification and key parsing. """ from securesystemslib.gpg import rsa from securesystemslib.gpg import dsa from securesystemslib.gpg import eddsa # See section 9.1. (public-key algorithms) of RFC4880 (-bis8) SUPPORTED_SIGNATURE_ALGORITHMS = { 0x01: { "type":"rsa", "method": "pgp+rsa-pkcsv1.5", "handler": rsa }, 0x11: { "type": "dsa", "method": "pgp+dsa-fips-180-2", "handler": dsa }, 0x16: { "type": "eddsa", "method": "pgp+eddsa-ed25519", "handler": eddsa } } SIGNATURE_HANDLERS = { "rsa": rsa, "dsa": dsa, "eddsa": eddsa } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/gpg/rsa.py0000644000076500000240000001461500000000000022625 0ustar00lukpstaff00000000000000""" rsa.py Santiago Torres-Arias Nov 15, 2017 See LICENSE for licensing information. RSA-specific handling routines for signature verification and key parsing """ import binascii CRYPTO = True NO_CRYPTO_MSG = 'RSA key support for GPG requires the cryptography library' try: from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat import backends from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric import utils from cryptography.exceptions import InvalidSignature except ImportError: CRYPTO = False from securesystemslib import exceptions from securesystemslib import formats from securesystemslib.gpg import util as gpg_util from securesystemslib.gpg.exceptions import PacketParsingError def create_pubkey(pubkey_info): """ Create and return an RSAPublicKey object from the passed pubkey_info using pyca/cryptography. pubkey_info: The RSA pubkey info dictionary as specified by securesystemslib.formats.GPG_RSA_PUBKEY_SCHEMA securesystemslib.exceptions.FormatError if pubkey_info does not match securesystemslib.formats.GPG_RSA_PUBKEY_SCHEMA securesystemslib.exceptions.UnsupportedLibraryError if the cryptography module is unavailable A cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey based on the passed pubkey_info. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) formats.GPG_RSA_PUBKEY_SCHEMA.check_match(pubkey_info) e = int(pubkey_info['keyval']['public']['e'], 16) n = int(pubkey_info['keyval']['public']['n'], 16) pubkey = rsa.RSAPublicNumbers(e, n).public_key(backends.default_backend()) return pubkey def get_pubkey_params(data): """ Parse the public key parameters as multi-precision-integers. data: the RFC4880-encoded public key parameters data buffer as described in the fifth paragraph of section 5.5.2. securesystemslib.gpg.exceptions.PacketParsingError: if the public key parameters are malformed None. The parsed RSA public key in the format securesystemslib.formats.GPG_RSA_PUBKEY_SCHEMA. """ ptr = 0 modulus_length = gpg_util.get_mpi_length(data[ptr: ptr + 2]) ptr += 2 modulus = data[ptr:ptr + modulus_length] if len(modulus) != modulus_length: # pragma: no cover raise PacketParsingError("This modulus MPI was truncated!") ptr += modulus_length exponent_e_length = gpg_util.get_mpi_length(data[ptr: ptr + 2]) ptr += 2 exponent_e = data[ptr:ptr + exponent_e_length] if len(exponent_e) != exponent_e_length: # pragma: no cover raise PacketParsingError("This e MPI has been truncated!") return { "e": binascii.hexlify(exponent_e).decode('ascii'), "n": binascii.hexlify(modulus).decode("ascii"), } def get_signature_params(data): """ Parse the signature parameters as multi-precision-integers. data: the RFC4880-encoded signature data buffer as described in the third paragraph of section 5.2.2. securesystemslib.gpg.exceptions.PacketParsingError: if the public key parameters are malformed None. The decoded signature buffer """ ptr = 0 signature_length = gpg_util.get_mpi_length(data[ptr:ptr+2]) ptr += 2 signature = data[ptr:ptr + signature_length] if len(signature) != signature_length: # pragma: no cover raise PacketParsingError("This signature was truncated!") return signature def verify_signature(signature_object, pubkey_info, content, hash_algorithm_id): """ Verify the passed signature against the passed content with the passed RSA public key using pyca/cryptography. signature_object: A signature dictionary as specified by securesystemslib.formats.GPG_SIGNATURE_SCHEMA pubkey_info: The RSA public key info dictionary as specified by securesystemslib.formats.GPG_RSA_PUBKEY_SCHEMA content: The signed bytes against which the signature is verified hash_algorithm_id: one of SHA1, SHA256, SHA512 (see securesystemslib.gpg.constants) used to verify the signature NOTE: Overrides any hash algorithm specification in "pubkey_info"'s "hashes" or "method" fields. securesystemslib.exceptions.FormatError if: signature_object does not match securesystemslib.formats.GPG_SIGNATURE_SCHEMA, pubkey_info does not match securesystemslib.formats.GPG_RSA_PUBKEY_SCHEMA securesystemslib.exceptions.UnsupportedLibraryError if: the cryptography module is unavailable ValueError: if the passed hash_algorithm_id is not supported (see securesystemslib.gpg.util.get_hashing_class) True if signature verification passes and False otherwise """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) formats.GPG_SIGNATURE_SCHEMA.check_match(signature_object) formats.GPG_RSA_PUBKEY_SCHEMA.check_match(pubkey_info) hasher = gpg_util.get_hashing_class(hash_algorithm_id) pubkey_object = create_pubkey(pubkey_info) # zero-pad the signature due to a discrepancy between the openssl backend # and the gnupg interpretation of PKCSv1.5. Read more at: # https://github.com/in-toto/in-toto/issues/171#issuecomment-440039256 # we are skipping this if on the tests because well, how would one test this # deterministically. pubkey_length = len(pubkey_info['keyval']['public']['n']) signature_length = len(signature_object['signature']) if pubkey_length != signature_length: # pragma: no cover zero_pad = "0"*(pubkey_length - signature_length) signature_object['signature'] = "{}{}".format(zero_pad, signature_object['signature']) digest = gpg_util.hash_object( binascii.unhexlify(signature_object['other_headers']), hasher(), content) try: pubkey_object.verify( binascii.unhexlify(signature_object['signature']), digest, padding.PKCS1v15(), utils.Prehashed(hasher()) ) return True except InvalidSignature: return False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/gpg/util.py0000644000076500000240000002543300000000000023015 0ustar00lukpstaff00000000000000""" util.py Santiago Torres-Arias Nov 15, 2017 See LICENSE for licensing information. general-purpose utilities for binary data handling and pgp data parsing """ import struct import binascii import re import logging from distutils.version import StrictVersion # pylint: disable=no-name-in-module,import-error CRYPTO = True NO_CRYPTO_MSG = 'gpg.utils requires the cryptography library' try: from cryptography.hazmat import backends from cryptography.hazmat.primitives import hashes as hashing except ImportError: CRYPTO = False from securesystemslib import exceptions from securesystemslib import process from securesystemslib.gpg import constants from securesystemslib.gpg.exceptions import PacketParsingError log = logging.getLogger(__name__) def get_mpi_length(data): """ parses an MPI (Multi-Precision Integer) buffer and returns the appropriate length. This is mostly done to perform bitwise to byte-wise conversion. See RFC4880 section 3.2. Multiprecision Integers for details. data: The MPI data None None The length of the MPI contained at the beginning of this data buffer. """ bitlength = int(struct.unpack(">H", data)[0]) # Notice the /8 at the end, this length is the bitlength, not the length of # the data in bytes (as len reports it) return int((bitlength - 1)/8) + 1 def hash_object(headers, algorithm, content): """ Hash data prior to signature verification in conformance of the RFC4880 openPGP standard. headers: the additional OpenPGP headers as populated from gpg_generate_signature algorithm: The hash algorithm object defined by the cryptography.io hashes module content: the signed content securesystemslib.exceptions.UnsupportedLibraryError if: the cryptography module is unavailable None The RFC4880-compliant hashed buffer """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # As per RFC4880 Section 5.2.4., we need to hash the content, # signature headers and add a very opinionated trailing header hasher = hashing.Hash(algorithm, backend=backends.default_backend()) hasher.update(content) hasher.update(headers) hasher.update(b'\x04\xff') hasher.update(struct.pack(">I", len(headers))) return hasher.finalize() def parse_packet_header(data, expected_type=None): """ Parse out packet type and header and body lengths from an RFC4880 packet. data: An RFC4880 packet as described in section 4.2 of the rfc. expected_type: (optional) Used to error out if the packet does not have the expected type. See securesystemslib.gpg.constants.PACKET_TYPE_* for available types. securesystemslib.gpg.exceptions.PacketParsingError If the new format packet length encodes a partial body length If the old format packet length encodes an indeterminate length If header or body length could not be determined If the expected_type was passed and does not match the packet type IndexError If the passed data is incomplete None. A tuple of packet type, header length, body length and packet length. (see RFC4880 4.3. for the list of available packet types) """ data = bytearray(data) header_len = None body_len = None # If Bit 6 of 1st octet is set we parse a New Format Packet Length, and # an Old Format Packet Lengths otherwise if data[0] & 0b01000000: # In new format packet lengths the packet type is encoded in Bits 5-0 of # the 1st octet of the packet packet_type = data[0] & 0b00111111 # The rest of the packet header is the body length header, which may # consist of one, two or five octets. To disambiguate the RFC, the first # octet of the body length header is the second octet of the packet. if data[1] < 192: header_len = 2 body_len = data[1] elif data[1] >= 192 and data[1] <= 223: header_len = 3 body_len = (data[1] - 192 << 8) + data[2] + 192 elif data[1] >= 224 and data[1] < 255: raise PacketParsingError("New length " "format packets of partial body lengths are not supported") elif data[1] == 255: header_len = 6 body_len = data[2] << 24 | data[3] << 16 | data[4] << 8 | data[5] else: # pragma: no cover # Unreachable: octet must be between 0 and 255 raise PacketParsingError("Invalid new length") else: # In old format packet lengths the packet type is encoded in Bits 5-2 of # the 1st octet and the length type in Bits 1-0 packet_type = (data[0] & 0b00111100) >> 2 length_type = data[0] & 0b00000011 # The body length is encoded using one, two, or four octets, starting # with the second octet of the packet if length_type == 0: body_len = data[1] header_len = 2 elif length_type == 1: header_len = 3 body_len = struct.unpack(">H", data[1:header_len])[0] elif length_type == 2: header_len = 5 body_len = struct.unpack(">I", data[1:header_len])[0] elif length_type == 3: raise PacketParsingError("Old length " "format packets of indeterminate length are not supported") else: # pragma: no cover (unreachable) # Unreachable: bits 1-0 must be one of 0 to 3 raise PacketParsingError("Invalid old length") if header_len is None or body_len is None: # pragma: no cover # Unreachable: One of above must have assigned lengths or raised error raise PacketParsingError("Could not determine packet length") if expected_type is not None and packet_type != expected_type: raise PacketParsingError("Expected packet " "{}, but got {} instead!".format(expected_type, packet_type)) return packet_type, header_len, body_len, header_len + body_len def compute_keyid(pubkey_packet_data): """ compute a keyid from an RFC4880 public-key buffer pubkey_packet_data: the public-key packet buffer securesystemslib.exceptions.UnsupportedLibraryError if: the cryptography module is unavailable None The RFC4880-compliant hashed buffer """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) hasher = hashing.Hash(hashing.SHA1(), backend=backends.default_backend()) hasher.update(b'\x99') hasher.update(struct.pack(">H", len(pubkey_packet_data))) hasher.update(bytes(pubkey_packet_data)) return binascii.hexlify(hasher.finalize()).decode("ascii") def parse_subpacket_header(data): """ Parse out subpacket header as per RFC4880 5.2.3.1. Signature Subpacket Specification. """ # NOTE: Although the RFC does not state it explicitly, the length encoded # in the header must be greater equal 1, as it includes the mandatory # subpacket type octet. # Hence, passed bytearrays like [0] or [255, 0, 0, 0, 0], which encode a # subpacket length 0 are invalid. # The caller has to deal with the resulting IndexError. if data[0] < 192: length_len = 1 length = data[0] elif data[0] >= 192 and data[0] < 255: length_len = 2 length = ((data[0] - 192 << 8) + (data[1] + 192)) elif data[0] == 255: length_len = 5 length = struct.unpack(">I", data[1:length_len])[0] else: # pragma: no cover (unreachable) raise PacketParsingError("Invalid subpacket header") return data[length_len], length_len + 1, length - 1, length_len + length def parse_subpackets(data): """ parse the subpackets fields data: the unparsed subpacketoctets IndexErrorif the subpackets octets are incomplete or malformed None A list of tuples with like: [ (packet_type, data), (packet_type, data), ... ] """ parsed_subpackets = [] position = 0 while position < len(data): subpacket_type, header_len, _, subpacket_len = \ parse_subpacket_header(data[position:]) payload = data[position+header_len:position+subpacket_len] parsed_subpackets.append((subpacket_type, payload)) position += subpacket_len return parsed_subpackets def get_version(): """ Uses `gpg2 --version` to get the version info of the installed gpg2 and extracts and returns the version number. The executed base command is defined in constants.GPG_VERSION_COMMAND. securesystemslib.exceptions.UnsupportedLibraryError: If the gpg command is not available Version number string, e.g. "2.1.22" """ if not constants.HAVE_GPG: # pragma: no cover raise exceptions.UnsupportedLibraryError(constants.NO_GPG_MSG) command = constants.GPG_VERSION_COMMAND gpg_process = process.run(command, stdout=process.PIPE, stderr=process.PIPE, universal_newlines=True) full_version_info = gpg_process.stdout version_string = re.search(r'(\d\.\d\.\d+)', full_version_info).group(1) return version_string def is_version_fully_supported(): """ Compares the version of installed gpg2 with the minimal fully supported gpg2 version (2.1.0). True if the version returned by `get_version` is greater-equal constants.FULLY_SUPPORTED_MIN_VERSION, False otherwise. """ installed_version = get_version() # Excluded so that coverage does not vary in different test environments if (StrictVersion(installed_version) >= StrictVersion(constants.FULLY_SUPPORTED_MIN_VERSION)): # pragma: no cover return True else: # pragma: no cover return False def get_hashing_class(hash_algorithm_id): """ Return a pyca/cryptography hashing class reference for the passed RFC4880 hash algorithm ID. hash_algorithm_id: one of SHA1, SHA256, SHA512 (see securesystemslib.gpg.constants) ValueError if the passed hash_algorithm_id is not supported. A pyca/cryptography hashing class """ supported_hashing_algorithms = [constants.SHA1, constants.SHA256, constants.SHA512] corresponding_hashing_classes = [hashing.SHA1, hashing.SHA256, hashing.SHA512] # Map supported hash algorithm ids to corresponding hashing classes hashing_class = dict(zip(supported_hashing_algorithms, corresponding_hashing_classes)) try: return hashing_class[hash_algorithm_id] except KeyError: raise ValueError("Hash algorithm '{}' not supported, must be one of '{}' " "(see RFC4880 9.4. Hash Algorithms).".format(hash_algorithm_id, supported_hashing_algorithms)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/hash.py0000755000076500000240000003162000000000000022204 0ustar00lukpstaff00000000000000#!/usr/bin/env python2 """ hash.py Vladimir Diaz February 28, 2012. Based on a previous version of this module. See LICENSE for licensing information. Support secure hashing and message digests. Any hash-related routines that securesystemslib requires should be located in this module. Simplifying the creation of digest objects, and providing a central location for hash routines are the main goals of this module. Support routines implemented include functions to create digest objects given a filename or file object. Only the standard hashlib library is currently supported, but pyca/cryptography support will be added in the future. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import hashlib import six from securesystemslib import exceptions from securesystemslib import formats from securesystemslib.storage import FilesystemBackend DEFAULT_CHUNK_SIZE = 4096 DEFAULT_HASH_ALGORITHM = 'sha256' DEFAULT_HASH_LIBRARY = 'hashlib' SUPPORTED_LIBRARIES = ['hashlib'] # If `pyca_crypto` is installed, add it to supported libraries try: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes as _pyca_hashes import binascii # Dictionary of `pyca/cryptography` supported hash algorithms. PYCA_DIGEST_OBJECTS_CACHE = { "md5": _pyca_hashes.MD5, "sha1": _pyca_hashes.SHA1, "sha224": _pyca_hashes.SHA224, "sha256": _pyca_hashes.SHA256, "sha384": _pyca_hashes.SHA384, "sha512": _pyca_hashes.SHA512 } SUPPORTED_LIBRARIES.append('pyca_crypto') class PycaDiggestWrapper(object): """ A wrapper around `cryptography.hazmat.primitives.hashes.Hash` which adds additional methods to meet expected interface for digest objects: digest_object.digest_size digest_object.hexdigest() digest_object.update('data') digest_object.digest() algorithm: Specific for `cryptography.hazmat.primitives.hashes.Hash` object, but needed for `rsa_keys.py` digest_size: Returns original's object digest size. digest(self) -> bytes: Calls original's object `finalize` method and returns digest as bytes. NOTE: `cryptography.hazmat.primitives.hashes.Hash` allows calling `finalize` method just once on the same instance, so everytime `digest` methods is called, we replace internal object (`_digest_obj`). hexdigest(self) -> str: Returns a string hex representation of digest. update(self, data) -> None: Updates digest object data by calling the original's object `update` method. """ def __init__(self, digest_obj): self._digest_obj = digest_obj @property def algorithm(self): return self._digest_obj.algorithm @property def digest_size(self): return self._digest_obj.algorithm.digest_size def digest(self): digest_obj_copy = self._digest_obj.copy() digest = self._digest_obj.finalize() self._digest_obj = digest_obj_copy return digest def hexdigest(self): return binascii.hexlify(self.digest()).decode('utf-8') def update(self, data): self._digest_obj.update(data) except ImportError: #pragma: no cover pass def digest(algorithm=DEFAULT_HASH_ALGORITHM, hash_library=DEFAULT_HASH_LIBRARY): """ Provide the caller with the ability to create digest objects without having to worry about crypto library availability or which library to use. The caller also has the option of specifying which hash algorithm and/or library to use. # Creation of a digest object using defaults or by specifying hash # algorithm and library. digest_object = securesystemslib.hash.digest() digest_object = securesystemslib.hash.digest('sha384') digest_object = securesystemslib.hash.digest('sha256', 'hashlib') # The expected interface for digest objects. digest_object.digest_size digest_object.hexdigest() digest_object.update('data') digest_object.digest() # Added hash routines by this module. digest_object = securesystemslib.hash.digest_fileobject(file_object) digest_object = securesystemslib.hash.digest_filename(filename) algorithm: The hash algorithm (e.g., 'md5', 'sha1', 'sha256'). hash_library: The crypto library to use for the given hash algorithm (e.g., 'hashlib'). securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if an unsupported hashing algorithm is specified, or digest could not be generated with given the algorithm. securesystemslib.exceptions.UnsupportedLibraryError, if an unsupported library was requested via 'hash_library'. None. Digest object e.g. hashlib.new(algorithm) or PycaDiggestWrapper object """ # Are the arguments properly formatted? If not, raise # 'securesystemslib.exceptions.FormatError'. formats.NAME_SCHEMA.check_match(algorithm) formats.NAME_SCHEMA.check_match(hash_library) # Was a hashlib digest object requested and is it supported? # If so, return the digest object. if hash_library == 'hashlib' and hash_library in SUPPORTED_LIBRARIES: try: if algorithm == 'blake2b-256': return hashlib.new('blake2b', digest_size=32) else: return hashlib.new(algorithm) except (ValueError, TypeError): # ValueError: the algorithm value was unknown # TypeError: unexpected argument digest_size (on old python) raise exceptions.UnsupportedAlgorithmError(algorithm) # Was a pyca_crypto digest object requested and is it supported? elif hash_library == 'pyca_crypto' and hash_library in SUPPORTED_LIBRARIES: try: hash_algorithm = PYCA_DIGEST_OBJECTS_CACHE[algorithm]() return PycaDiggestWrapper( _pyca_hashes.Hash(hash_algorithm, default_backend())) except KeyError: raise exceptions.UnsupportedAlgorithmError(algorithm) # The requested hash library is not supported. else: raise exceptions.UnsupportedLibraryError('Unsupported' ' library requested. Supported hash' ' libraries: ' + repr(SUPPORTED_LIBRARIES)) def digest_fileobject(file_object, algorithm=DEFAULT_HASH_ALGORITHM, hash_library=DEFAULT_HASH_LIBRARY, normalize_line_endings=False): """ Generate a digest object given a file object. The new digest object is updated with the contents of 'file_object' prior to returning the object to the caller. file_object: File object whose contents will be used as the data to update the hash of a digest object to be returned. algorithm: The hash algorithm (e.g., 'md5', 'sha1', 'sha256'). hash_library: The library providing the hash algorithms (e.g., 'hashlib'). normalize_line_endings: (default False) Whether or not to normalize line endings for cross-platform support. Note that this results in ambiguous hashes (e.g. 'abc\n' and 'abc\r\n' will produce the same hash), so be careful to only apply this to text files (not binary), when that equivalence is desirable and cannot result in easily-maliciously-corrupted files producing the same hash as a valid file. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if an unsupported hashing algorithm was specified via 'algorithm'. securesystemslib.exceptions.UnsupportedLibraryError, if an unsupported crypto library was specified via 'hash_library'. None. Digest object e.g. hashlib.new(algorithm) or PycaDiggestWrapper object """ # Are the arguments properly formatted? If not, raise # 'securesystemslib.exceptions.FormatError'. formats.NAME_SCHEMA.check_match(algorithm) formats.NAME_SCHEMA.check_match(hash_library) # Digest object returned whose hash will be updated using 'file_object'. # digest() raises: # securesystemslib.exceptions.UnsupportedAlgorithmError # securesystemslib.exceptions.UnsupportedLibraryError digest_object = digest(algorithm, hash_library) # Defensively seek to beginning, as there's no case where we don't # intend to start from the beginning of the file. file_object.seek(0) # Read the contents of the file object in at most 4096-byte chunks. # Update the hash with the data read from each chunk and return after # the entire file is processed. while True: data = file_object.read(DEFAULT_CHUNK_SIZE) if not data: break if normalize_line_endings: while data[-1:] == b'\r': c = file_object.read(1) if not c: break data += c data = ( data # First Windows .replace(b'\r\n', b'\n') # Then Mac .replace(b'\r', b'\n') ) if not isinstance(data, six.binary_type): digest_object.update(data.encode('utf-8')) else: digest_object.update(data) return digest_object def digest_filename(filename, algorithm=DEFAULT_HASH_ALGORITHM, hash_library=DEFAULT_HASH_LIBRARY, normalize_line_endings=False, storage_backend=None): """ Generate a digest object, update its hash using a file object specified by filename, and then return it to the caller. filename: The filename belonging to the file object to be used. algorithm: The hash algorithm (e.g., 'md5', 'sha1', 'sha256'). hash_library: The library providing the hash algorithms (e.g., 'hashlib'). normalize_line_endings: Whether or not to normalize line endings for cross-platform support. storage_backend: An object which implements securesystemslib.storage.StorageBackendInterface. When no object is passed a FilesystemBackend will be instantiated and used. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if the given 'algorithm' is unsupported. securesystemslib.exceptions.UnsupportedLibraryError, if the given 'hash_library' is unsupported. None. Digest object e.g. hashlib.new(algorithm) or PycaDiggestWrapper object """ # Are the arguments properly formatted? If not, raise # 'securesystemslib.exceptions.FormatError'. formats.PATH_SCHEMA.check_match(filename) formats.NAME_SCHEMA.check_match(algorithm) formats.NAME_SCHEMA.check_match(hash_library) digest_object = None if storage_backend is None: storage_backend = FilesystemBackend() # Open 'filename' in read+binary mode. with storage_backend.get(filename) as file_object: # Create digest_object and update its hash data from file_object. # digest_fileobject() raises: # securesystemslib.exceptions.UnsupportedAlgorithmError # securesystemslib.exceptions.UnsupportedLibraryError digest_object = digest_fileobject( file_object, algorithm, hash_library, normalize_line_endings) return digest_object def digest_from_rsa_scheme(scheme, hash_library=DEFAULT_HASH_LIBRARY): """ Get digest object from RSA scheme. scheme: A string that indicates the signature scheme used to generate 'signature'. Currently supported RSA schemes are defined in `securesystemslib.keys.RSA_SIGNATURE_SCHEMES` hash_library: The crypto library to use for the given hash algorithm (e.g., 'hashlib'). securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if an unsupported hashing algorithm is specified, or digest could not be generated with given the algorithm. securesystemslib.exceptions.UnsupportedLibraryError, if an unsupported library was requested via 'hash_library'. None. Digest object e.g. hashlib.new(algorithm) or PycaDiggestWrapper object """ # Are the arguments properly formatted? If not, raise # 'securesystemslib.exceptions.FormatError'. formats.RSA_SCHEME_SCHEMA.check_match(scheme) # Get hash algorithm from rsa scheme (hash algorithm id is specified after # the last dash; e.g. rsassa-pss-sha256 -> sha256) hash_algorithm = scheme.split('-')[-1] return digest(hash_algorithm, hash_library) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/interface.py0000755000076500000240000011023200000000000023216 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ interface.py Vladimir Diaz January 5, 2017. See LICENSE for licensing information. Provide an interface to the cryptography functions available in securesystemslib. The interface can be used with the Python interpreter in interactive mode, or imported directly into a Python module. See 'securesystemslib/README' for the complete guide to using 'interface.py'. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import os import sys import getpass import logging import tempfile import json from securesystemslib import exceptions from securesystemslib import formats from securesystemslib import keys from securesystemslib import settings from securesystemslib import util from securesystemslib.storage import FilesystemBackend from securesystemslib import KEY_TYPE_RSA, KEY_TYPE_ED25519, KEY_TYPE_ECDSA logger = logging.getLogger(__name__) try: from colorama import Fore TERM_RED = Fore.RED TERM_RESET = Fore.RESET except ImportError: # pragma: no cover logger.debug("Failed to find colorama module, terminal output won't be colored") TERM_RED = '' TERM_RESET = '' # Recommended RSA key sizes: # https://en.wikipedia.org/wiki/Key_size#Asymmetric_algorithm_key_lengths # Based on the above, RSA keys of size 3072 bits are expected to provide # security through 2031 and beyond. DEFAULT_RSA_KEY_BITS = 3072 def get_password(prompt='Password: ', confirm=False): """Prompts user to enter a password. Arguments: prompt (optional): A text displayed on the prompt (stderr). confirm (optional): A boolean indicating if the user needs to enter the same password twice. Returns: The password entered on the prompt. """ formats.TEXT_SCHEMA.check_match(prompt) formats.BOOLEAN_SCHEMA.check_match(confirm) while True: # getpass() prompts the user for a password without echoing # the user input. password = getpass.getpass(prompt, sys.stderr) if not confirm: return password password2 = getpass.getpass('Confirm: ', sys.stderr) if password == password2: return password else: print('Mismatch; try again.') def _get_key_file_encryption_password(password, prompt, path): """Encryption password helper for `_generate_and_write_*_keypair` functions. Combinations of 'password' and 'prompt' -> result (explanation) ---------------------------------------------------------------- None False -> return None (clear non-encryption desire) "" False -> return password (clear encryption desire) False -> raise (bad pw type, unclear encryption desire) True -> raise (unclear password/prompt precedence) None True -> prompt and return password if entered and None otherwise (users on the prompt can only indicate desire to not encrypt by entering no password) """ formats.BOOLEAN_SCHEMA.check_match(prompt) # We don't want to decide which takes precedence so we fail if password is not None and prompt: raise ValueError("passing 'password' and 'prompt=True' is not allowed") # Prompt user for password and confirmation if prompt: password = get_password("enter password to encrypt private key file " "'" + TERM_RED + str(path) + TERM_RESET + "' (leave empty if key " "should not be encrypted): ", confirm=True) # Treat empty password as no password. A user on the prompt can only # indicate the desire to not encrypt by entering no password. if not len(password): return None if password is not None: formats.PASSWORD_SCHEMA.check_match(password) # Fail on empty passed password. A caller should pass None to indicate the # desire to not encrypt. if not len(password): raise ValueError("encryption password must be 1 or more characters long") return password def _get_key_file_decryption_password(password, prompt, path): """Decryption password helper for `import_*_privatekey_from_file` functions. Combinations of 'password' and 'prompt' -> result (explanation) ---------------------------------------------------------------- None False -> return None (clear non-decryption desire) "" False -> return password (clear decryption desire) False -> raise (bad pw type, unclear decryption desire) True -> raise (unclear password/prompt precedence) None True -> prompt and return password if entered and None otherwise (users on the prompt can only indicate desire to not decrypt by entering no password) """ formats.BOOLEAN_SCHEMA.check_match(prompt) # We don't want to decide which takes precedence so we fail if password is not None and prompt: raise ValueError("passing 'password' and 'prompt=True' is not allowed") # Prompt user for password if prompt: password = get_password("enter password to decrypt private key file " "'" + TERM_RED + str(path) + TERM_RESET + "' " "(leave empty if key not encrypted): ", confirm=False) # Treat empty password as no password. A user on the prompt can only # indicate the desire to not decrypt by entering no password. if not len(password): return None if password is not None: formats.PASSWORD_SCHEMA.check_match(password) # No additional vetting needed. Decryption will show if it was correct. return password def _generate_and_write_rsa_keypair(filepath=None, bits=DEFAULT_RSA_KEY_BITS, password=None, prompt=False): """Generates RSA key pair and writes PEM-encoded keys to disk. If a password is passed or entered on the prompt, the private key is encrypted. According to the documentation of the used pyca/cryptography library, encryption is performed "using the best available encryption for a given key's backend", which "is a curated encryption choice and the algorithm may change over time." The private key is written in PKCS#1 and the public key in X.509 SubjectPublicKeyInfo format. NOTE: A signing scheme can be assigned on key import (see import functions). Arguments: filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. bits (optional): The number of bits of the generated RSA key. password (optional): An encryption password. prompt (optional): A boolean indicating if the user should be prompted for an encryption password. If the user enters an empty password, the key is not encrypted. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. ValueError: An empty string is passed as 'password', or both a 'password' is passed and 'prompt' is true. StorageError: Key files cannot be written. Side Effects: Prompts user for a password if 'prompt' is True. Writes key files to disk. Returns: The private key filepath. """ formats.RSAKEYBITS_SCHEMA.check_match(bits) # Generate private RSA key and extract public and private both in PEM rsa_key = keys.generate_rsa_key(bits) public = rsa_key['keyval']['public'] private = rsa_key['keyval']['private'] # Use passed 'filepath' or keyid as file name if not filepath: filepath = os.path.join(os.getcwd(), rsa_key['keyid']) formats.PATH_SCHEMA.check_match(filepath) password = _get_key_file_encryption_password(password, prompt, filepath) # Encrypt the private key if a 'password' was passed or entered on the prompt if password is not None: private = keys.create_rsa_encrypted_pem(private, password) # Create intermediate directories as required util.ensure_parent_dir(filepath) # Write PEM-encoded public key to .pub file_object = tempfile.TemporaryFile() file_object.write(public.encode('utf-8')) util.persist_temp_file(file_object, filepath + '.pub') # Write PEM-encoded private key to file_object = tempfile.TemporaryFile() file_object.write(private.encode('utf-8')) util.persist_temp_file(file_object, filepath) return filepath def generate_and_write_rsa_keypair(password, filepath=None, bits=DEFAULT_RSA_KEY_BITS): """Generates RSA key pair and writes PEM-encoded keys to disk. The private key is encrypted using the best available encryption algorithm chosen by 'pyca/cryptography', which may change over time. The private key is written in PKCS#1 and the public key in X.509 SubjectPublicKeyInfo format. NOTE: A signing scheme can be assigned on key import (see import functions). Arguments: password: An encryption password. filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. bits (optional): The number of bits of the generated RSA key. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. ValueError: An empty string is passed as 'password'. StorageError: Key files cannot be written. Side Effects: Writes key files to disk. Returns: The private key filepath. """ formats.PASSWORD_SCHEMA.check_match(password) return _generate_and_write_rsa_keypair( filepath=filepath, bits=bits, password=password, prompt=False) def generate_and_write_rsa_keypair_with_prompt(filepath=None, bits=DEFAULT_RSA_KEY_BITS): """Generates RSA key pair and writes PEM-encoded keys to disk. The private key is encrypted with a password entered on the prompt, using the best available encryption algorithm chosen by 'pyca/cryptography', which may change over time. The private key is written in PKCS#1 and the public key in X.509 SubjectPublicKeyInfo format. NOTE: A signing scheme can be assigned on key import (see import functions). Arguments: filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. bits (optional): The number of bits of the generated RSA key. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. StorageError: Key files cannot be written. Side Effects: Prompts user for a password. Writes key files to disk. Returns: The private key filepath. """ return _generate_and_write_rsa_keypair( filepath=filepath, bits=bits, password=None, prompt=True) def generate_and_write_unencrypted_rsa_keypair(filepath=None, bits=DEFAULT_RSA_KEY_BITS): """Generates RSA key pair and writes PEM-encoded keys to disk. The private key is written in PKCS#1 and the public key in X.509 SubjectPublicKeyInfo format. NOTE: A signing scheme can be assigned on key import (see import functions). Arguments: filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. bits (optional): The number of bits of the generated RSA key. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. StorageError: Key files cannot be written. Side Effects: Writes unencrypted key files to disk. Returns: The private key filepath. """ return _generate_and_write_rsa_keypair( filepath=filepath, bits=bits, password=None, prompt=False) def import_rsa_privatekey_from_file(filepath, password=None, scheme='rsassa-pss-sha256', prompt=False, storage_backend=None): """Imports PEM-encoded RSA private key from file storage. The expected key format is PKCS#1. If a password is passed or entered on the prompt, the private key is decrypted, otherwise it is treated as unencrypted. Arguments: filepath: The path to read the file from. password (optional): A password to decrypt the key. scheme (optional): The signing scheme assigned to the returned key object. See RSA_SCHEME_SCHEMA for available signing schemes. prompt (optional): A boolean indicating if the user should be prompted for a decryption password. If the user enters an empty password, the key is not decrypted. storage_backend (optional): An object implementing StorageBackendInterface. If not passed a default FilesystemBackend will be used. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. ValueError: Both a 'password' is passed and 'prompt' is true. StorageError: Key file cannot be read. CryptoError: Key cannot be parsed. Returns: An RSA private key object conformant with 'RSAKEY_SCHEMA'. """ formats.PATH_SCHEMA.check_match(filepath) formats.RSA_SCHEME_SCHEMA.check_match(scheme) password = _get_key_file_decryption_password(password, prompt, filepath) if storage_backend is None: storage_backend = FilesystemBackend() with storage_backend.get(filepath) as file_object: pem_key = file_object.read().decode('utf-8') # Optionally decrypt and convert PEM-encoded key to 'RSAKEY_SCHEMA' format rsa_key = keys.import_rsakey_from_private_pem(pem_key, scheme, password) return rsa_key def import_rsa_publickey_from_file(filepath, scheme='rsassa-pss-sha256', storage_backend=None): """Imports PEM-encoded RSA public key from file storage. The expected key format is X.509 SubjectPublicKeyInfo. Arguments: filepath: The path to read the file from. scheme (optional): The signing scheme assigned to the returned key object. See RSA_SCHEME_SCHEMA for available signing schemes. storage_backend (optional): An object implementing StorageBackendInterface. If not passed a default FilesystemBackend will be used. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. StorageError: Key file cannot be read. Error: Public key is malformed. Returns: An RSA public key object conformant with 'RSAKEY_SCHEMA'. """ formats.PATH_SCHEMA.check_match(filepath) formats.RSA_SCHEME_SCHEMA.check_match(scheme) if storage_backend is None: storage_backend = FilesystemBackend() with storage_backend.get(filepath) as file_object: rsa_pubkey_pem = file_object.read().decode('utf-8') # Convert PEM-encoded key to 'RSAKEY_SCHEMA' format try: rsakey_dict = keys.import_rsakey_from_public_pem(rsa_pubkey_pem, scheme) except exceptions.FormatError as e: raise exceptions.Error('Cannot import improperly formatted' ' PEM file.' + repr(str(e))) return rsakey_dict def _generate_and_write_ed25519_keypair(filepath=None, password=None, prompt=False): """Generates ed25519 key pair and writes custom JSON-formatted keys to disk. If a password is passed or entered on the prompt, the private key is encrypted using AES-256 in CTR mode, with the password strengthened in PBKDF2-HMAC-SHA256. NOTE: The custom key format includes 'ed25519' as signing scheme. Arguments: filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. password (optional): An encryption password. prompt (optional): A boolean indicating if the user should be prompted for an encryption password. If the user enters an empty password, the key is not encrypted. Raises: UnsupportedLibraryError: pyca/pynacl or pyca/cryptography is not available. FormatError: Arguments are malformed. ValueError: An empty string is passed as 'password', or both a 'password' is passed and 'prompt' is true. StorageError: Key files cannot be written. Side Effects: Prompts user for a password if 'prompt' is True. Writes key files to disk. Returns: The private key filepath. """ ed25519_key = keys.generate_ed25519_key() # Use passed 'filepath' or keyid as file name if not filepath: filepath = os.path.join(os.getcwd(), ed25519_key['keyid']) formats.PATH_SCHEMA.check_match(filepath) password = _get_key_file_encryption_password(password, prompt, filepath) # Create intermediate directories as required util.ensure_parent_dir(filepath) # Use custom JSON format for ed25519 keys on-disk keytype = ed25519_key['keytype'] keyval = ed25519_key['keyval'] scheme = ed25519_key['scheme'] ed25519key_metadata_format = keys.format_keyval_to_metadata( keytype, scheme, keyval, private=False) # Write public key to .pub file_object = tempfile.TemporaryFile() file_object.write(json.dumps(ed25519key_metadata_format).encode('utf-8')) util.persist_temp_file(file_object, filepath + '.pub') # Encrypt private key if we have a password, store as JSON string otherwise if password is not None: ed25519_key = keys.encrypt_key(ed25519_key, password) else: ed25519_key = json.dumps(ed25519_key) # Write private key to file_object = tempfile.TemporaryFile() file_object.write(ed25519_key.encode('utf-8')) util.persist_temp_file(file_object, filepath) return filepath def generate_and_write_ed25519_keypair(password, filepath=None): """Generates ed25519 key pair and writes custom JSON-formatted keys to disk. The private key is encrypted using AES-256 in CTR mode, with the passed password strengthened in PBKDF2-HMAC-SHA256. NOTE: The custom key format includes 'ed25519' as signing scheme. Arguments: password: An encryption password. filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. Raises: UnsupportedLibraryError: pyca/pynacl or pyca/cryptography is not available. FormatError: Arguments are malformed. ValueError: An empty string is passed as 'password'. StorageError: Key files cannot be written. Side Effects: Writes key files to disk. Returns: The private key filepath. """ formats.PASSWORD_SCHEMA.check_match(password) return _generate_and_write_ed25519_keypair( filepath=filepath, password=password, prompt=False) def generate_and_write_ed25519_keypair_with_prompt(filepath=None): """Generates ed25519 key pair and writes custom JSON-formatted keys to disk. The private key is encrypted using AES-256 in CTR mode, with the password entered on the prompt strengthened in PBKDF2-HMAC-SHA256. NOTE: The custom key format includes 'ed25519' as signing scheme. Arguments: filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. Raises: UnsupportedLibraryError: pyca/pynacl or pyca/cryptography is not available. FormatError: Arguments are malformed. StorageError: Key files cannot be written. Side Effects: Prompts user for a password. Writes key files to disk. Returns: The private key filepath. """ return _generate_and_write_ed25519_keypair( filepath=filepath, password=None, prompt=True) def generate_and_write_unencrypted_ed25519_keypair(filepath=None): """Generates ed25519 key pair and writes custom JSON-formatted keys to disk. NOTE: The custom key format includes 'ed25519' as signing scheme. Arguments: filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. Raises: UnsupportedLibraryError: pyca/pynacl or pyca/cryptography is not available. FormatError: Arguments are malformed. StorageError: Key files cannot be written. Side Effects: Writes unencrypted key files to disk. Returns: The private key filepath. """ return _generate_and_write_ed25519_keypair( filepath=filepath, password=None, prompt=False) def import_ed25519_publickey_from_file(filepath): """Imports custom JSON-formatted ed25519 public key from disk. NOTE: The signing scheme is set at key generation (see generate function). Arguments: filepath: The path to read the file from. Raises: FormatError: Argument is malformed. StorageError: Key file cannot be read. Error: Public key is malformed. Returns: An ed25519 public key object conformant with 'ED25519KEY_SCHEMA'. """ formats.PATH_SCHEMA.check_match(filepath) # Load custom on-disk JSON formatted key and convert to its custom in-memory # dict key representation ed25519_key_metadata = util.load_json_file(filepath) ed25519_key, _ = keys.format_metadata_to_key(ed25519_key_metadata) # Check that the generic loading functions indeed loaded an ed25519 key if ed25519_key['keytype'] != 'ed25519': message = 'Invalid key type loaded: ' + repr(ed25519_key['keytype']) raise exceptions.FormatError(message) return ed25519_key def import_ed25519_privatekey_from_file(filepath, password=None, prompt=False, storage_backend=None): """Imports custom JSON-formatted ed25519 private key from file storage. If a password is passed or entered on the prompt, the private key is decrypted, otherwise it is treated as unencrypted. NOTE: The signing scheme is set at key generation (see generate function). Arguments: filepath: The path to read the file from. password (optional): A password to decrypt the key. prompt (optional): A boolean indicating if the user should be prompted for a decryption password. If the user enters an empty password, the key is not decrypted. storage_backend (optional): An object implementing StorageBackendInterface. If not passed a default FilesystemBackend will be used. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. ValueError: Both a 'password' is passed and 'prompt' is true. StorageError: Key file cannot be read. Error, CryptoError: Key cannot be parsed. Returns: An ed25519 private key object conformant with 'ED25519KEY_SCHEMA'. """ formats.PATH_SCHEMA.check_match(filepath) password = _get_key_file_decryption_password(password, prompt, filepath) if storage_backend is None: storage_backend = FilesystemBackend() with storage_backend.get(filepath) as file_object: json_str = file_object.read() # Load custom on-disk JSON formatted key and convert to its custom # in-memory dict key representation, decrypting it if password is not None return keys.import_ed25519key_from_private_json( json_str, password=password) def _generate_and_write_ecdsa_keypair(filepath=None, password=None, prompt=False): """Generates ecdsa key pair and writes custom JSON-formatted keys to disk. If a password is passed or entered on the prompt, the private key is encrypted using AES-256 in CTR mode, with the password strengthened in PBKDF2-HMAC-SHA256. NOTE: The custom key format includes 'ecdsa-sha2-nistp256' as signing scheme. Arguments: filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. password (optional): An encryption password. prompt (optional): A boolean indicating if the user should be prompted for an encryption password. If the user enters an empty password, the key is not encrypted. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. ValueError: An empty string is passed as 'password', or both a 'password' is passed and 'prompt' is true. StorageError: Key files cannot be written. Side Effects: Prompts user for a password if 'prompt' is True. Writes key files to disk. Returns: The private key filepath. """ ecdsa_key = keys.generate_ecdsa_key() # Use passed 'filepath' or keyid as file name if not filepath: filepath = os.path.join(os.getcwd(), ecdsa_key['keyid']) formats.PATH_SCHEMA.check_match(filepath) password = _get_key_file_encryption_password(password, prompt, filepath) # Create intermediate directories as required util.ensure_parent_dir(filepath) # Use custom JSON format for ecdsa keys on-disk keytype = ecdsa_key['keytype'] keyval = ecdsa_key['keyval'] scheme = ecdsa_key['scheme'] ecdsakey_metadata_format = keys.format_keyval_to_metadata( keytype, scheme, keyval, private=False) # Write public key to .pub file_object = tempfile.TemporaryFile() file_object.write(json.dumps(ecdsakey_metadata_format).encode('utf-8')) util.persist_temp_file(file_object, filepath + '.pub') # Encrypt private key if we have a password, store as JSON string otherwise if password is not None: ecdsa_key = keys.encrypt_key(ecdsa_key, password) else: ecdsa_key = json.dumps(ecdsa_key) # Write private key to file_object = tempfile.TemporaryFile() file_object.write(ecdsa_key.encode('utf-8')) util.persist_temp_file(file_object, filepath) return filepath def generate_and_write_ecdsa_keypair(password, filepath=None): """Generates ecdsa key pair and writes custom JSON-formatted keys to disk. The private key is encrypted using AES-256 in CTR mode, with the passed password strengthened in PBKDF2-HMAC-SHA256. NOTE: The custom key format includes 'ecdsa-sha2-nistp256' as signing scheme. Arguments: password: An encryption password. filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. ValueError: An empty string is passed as 'password'. StorageError: Key files cannot be written. Side Effects: Writes key files to disk. Returns: The private key filepath. """ formats.PASSWORD_SCHEMA.check_match(password) return _generate_and_write_ecdsa_keypair( filepath=filepath, password=password, prompt=False) def generate_and_write_ecdsa_keypair_with_prompt(filepath=None): """Generates ecdsa key pair and writes custom JSON-formatted keys to disk. The private key is encrypted using AES-256 in CTR mode, with the password entered on the prompt strengthened in PBKDF2-HMAC-SHA256. NOTE: The custom key format includes 'ecdsa-sha2-nistp256' as signing scheme. Arguments: filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. StorageError: Key files cannot be written. Side Effects: Prompts user for a password. Writes key files to disk. Returns: The private key filepath. """ return _generate_and_write_ecdsa_keypair( filepath=filepath, password=None, prompt=True) def generate_and_write_unencrypted_ecdsa_keypair(filepath=None): """Generates ecdsa key pair and writes custom JSON-formatted keys to disk. NOTE: The custom key format includes 'ecdsa-sha2-nistp256' as signing scheme. Arguments: filepath (optional): The path to write the private key to. If not passed, the key is written to CWD using the keyid as filename. The public key is written to the same path as the private key using the suffix '.pub'. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. StorageError: Key files cannot be written. Side Effects: Writes unencrypted key files to disk. Returns: The private key filepath. """ return _generate_and_write_ecdsa_keypair( filepath=filepath, password=None, prompt=False) def import_ecdsa_publickey_from_file(filepath): """Imports custom JSON-formatted ecdsa public key from disk. NOTE: The signing scheme is set at key generation (see generate function). Arguments: filepath: The path to read the file from. Raises: FormatError: Argument is malformed. StorageError: Key file cannot be read. Error: Public key is malformed. Returns: An ecdsa public key object conformant with 'ECDSAKEY_SCHEMA'. """ formats.PATH_SCHEMA.check_match(filepath) # Load custom on-disk JSON formatted key and convert to its custom in-memory # dict key representation ecdsa_key_metadata = util.load_json_file(filepath) ecdsa_key, _ = keys.format_metadata_to_key(ecdsa_key_metadata) return ecdsa_key def import_ecdsa_privatekey_from_file(filepath, password=None, prompt=False, storage_backend=None): """Imports custom JSON-formatted ecdsa private key from file storage. If a password is passed or entered on the prompt, the private key is decrypted, otherwise it is treated as unencrypted. NOTE: The signing scheme is set at key generation (see generate function). Arguments: filepath: The path to read the file from. password (optional): A password to decrypt the key. prompt (optional): A boolean indicating if the user should be prompted for a decryption password. If the user enters an empty password, the key is not decrypted. storage_backend (optional): An object implementing StorageBackendInterface. If not passed a default FilesystemBackend will be used. Raises: UnsupportedLibraryError: pyca/cryptography is not available. FormatError: Arguments are malformed. ValueError: Both a 'password' is passed and 'prompt' is true. StorageError: Key file cannot be read. Error, CryptoError: Key cannot be parsed. Returns: An ecdsa private key object conformant with 'ED25519KEY_SCHEMA'. """ formats.PATH_SCHEMA.check_match(filepath) password = _get_key_file_decryption_password(password, prompt, filepath) if storage_backend is None: storage_backend = FilesystemBackend() with storage_backend.get(filepath) as file_object: key_data = file_object.read().decode('utf-8') # Decrypt private key if we have a password, directly load JSON otherwise if password is not None: key_object = keys.decrypt_key(key_data, password) else: key_object = util.load_json_string(key_data) # Raise an exception if an unexpected key type is imported. # NOTE: we support keytype's of ecdsa-sha2-nistp256 and ecdsa-sha2-nistp384 # in order to support key files generated with older versions of # securesystemslib. At some point this backwards compatibility should be # removed. if key_object['keytype'] not in['ecdsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384']: message = 'Invalid key type loaded: ' + repr(key_object['keytype']) raise exceptions.FormatError(message) # Add "keyid_hash_algorithms" so that equal ecdsa keys with different keyids # can be associated using supported keyid_hash_algorithms. key_object['keyid_hash_algorithms'] = settings.HASH_ALGORITHMS return key_object def import_publickeys_from_file(filepaths, key_types=None): """Imports multiple public keys from files. NOTE: The default signing scheme 'rsassa-pss-sha256' is assigned to RSA keys. Use 'import_rsa_publickey_from_file' to specify any other than the default signing scheme for an RSA key. ed25519 and ecdsa keys have the signing scheme included in the custom key format (see generate functions). Arguments: filepaths: A list of paths to public key files. key_types (optional): A list of types of keys to be imported associated with filepaths by index. Must be one of KEY_TYPE_RSA, KEY_TYPE_ED25519 or KEY_TYPE_ECDSA. If not specified, all keys are assumed to be KEY_TYPE_RSA. Raises: TypeError: filepaths or 'key_types' (if passed) is not iterable. FormatError: Argument are malformed, or 'key_types' is passed and does not have the same length as 'filepaths' or contains an unsupported type. UnsupportedLibraryError: pyca/cryptography is not available. StorageError: Key file cannot be read. Error: Public key is malformed. Returns: A dict of public keys in KEYDICT_SCHEMA format. """ if key_types is None: key_types = [KEY_TYPE_RSA] * len(filepaths) if len(key_types) != len(filepaths): raise exceptions.FormatError( "Pass equal amount of 'filepaths' (got {}) and 'key_types (got {}), " "or no 'key_types' at all to default to '{}'.".format( len(filepaths), len(key_types), KEY_TYPE_RSA)) key_dict = {} for idx, filepath in enumerate(filepaths): if key_types[idx] == KEY_TYPE_ED25519: key = import_ed25519_publickey_from_file(filepath) elif key_types[idx] == KEY_TYPE_RSA: key = import_rsa_publickey_from_file(filepath) elif key_types[idx] == KEY_TYPE_ECDSA: key = import_ecdsa_publickey_from_file(filepath) else: raise exceptions.FormatError( "Unsupported key type '{}'. Must be '{}', '{}' or '{}'.".format( key_types[idx], KEY_TYPE_RSA, KEY_TYPE_ED25519, KEY_TYPE_ECDSA)) key_dict[key["keyid"]] = key return key_dict def import_privatekey_from_file(filepath, key_type=None, password=None, prompt=False): """Imports private key from file. If a password is passed or entered on the prompt, the private key is decrypted, otherwise it is treated as unencrypted. NOTE: The default signing scheme 'rsassa-pss-sha256' is assigned to RSA keys. Use 'import_rsa_privatekey_from_file' to specify any other than the default signing scheme for an RSA key. ed25519 and ecdsa keys have the signing scheme included in the custom key format (see generate functions). Arguments: filepath: The path to read the file from. key_type (optional): One of KEY_TYPE_RSA, KEY_TYPE_ED25519 or KEY_TYPE_ECDSA. Default is KEY_TYPE_RSA. password (optional): A password to decrypt the key. prompt (optional): A boolean indicating if the user should be prompted for a decryption password. If the user enters an empty password, the key is not decrypted. Raises: FormatError: Arguments are malformed or 'key_type' is not supported. ValueError: Both a 'password' is passed and 'prompt' is true. UnsupportedLibraryError: pyca/cryptography is not available. StorageError: Key file cannot be read. Error, CryptoError: Key cannot be parsed. Returns: A private key object conformant with one of 'ED25519KEY_SCHEMA', 'RSAKEY_SCHEMA' or 'ECDSAKEY_SCHEMA'. """ if key_type is None: key_type = KEY_TYPE_RSA if key_type == KEY_TYPE_ED25519: return import_ed25519_privatekey_from_file( filepath, password=password, prompt=prompt) elif key_type == KEY_TYPE_RSA: return import_rsa_privatekey_from_file( filepath, password=password, prompt=prompt) elif key_type == KEY_TYPE_ECDSA: return import_ecdsa_privatekey_from_file( filepath, password=password, prompt=prompt) else: raise exceptions.FormatError( "Unsupported key type '{}'. Must be '{}', '{}' or '{}'.".format( key_type, KEY_TYPE_RSA, KEY_TYPE_ED25519, KEY_TYPE_ECDSA)) if __name__ == '__main__': # The interactive sessions of the documentation strings can # be tested by running interface.py as a standalone module: # $ python interface.py. import doctest doctest.testmod() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/keys.py0000755000076500000240000017701700000000000022247 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ keys.py Vladimir Diaz October 4, 2013. See LICENSE for licensing information. The goal of this module is to centralize cryptographic key routines and their supported operations (e.g., creating and verifying signatures). This module is designed to support multiple public-key algorithms, such as RSA, Ed25519, and ECDSA, and multiple cryptography libraries. Which cryptography library to use is determined by the default, or user modified, values set in 'settings.py' https://en.wikipedia.org/wiki/RSA_(algorithm) http://ed25519.cr.yp.to/ https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm The (RSA, ECDSA and Ed25519)-related functions provided include generate_rsa_key(), generate_ed25519_key(), generate_ecdsa_key(), create_signature(), and verify_signature(). The cryptography libraries called by 'securesystemslib.keys.py' generate the actual keys and the functions listed above can be viewed as the easy-to-use public interface. Additional functions contained here include format_keyval_to_metadata() and format_metadata_to_key(). These last two functions produce or use keys compatible with the key structures listed in Metadata files. The key generation functions return a dictionary containing all the information needed of keys, such as public & private keys, and a keyID. create_signature() and verify_signature() are supplemental functions needed for generating signatures and verifying them. Key IDs are used as identifiers for keys (e.g., RSA key). They are the hexadecimal representation of the hash of the key object (specifically, the key object containing only the public key). Review the '_get_keyid()' function of this module to see precisely how keyids are generated. One may get the keyid of a key object by simply accessing the dictionary's 'keyid' key (i.e., rsakey['keyid']). """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals # Required for hexadecimal conversions. Signatures and public/private keys are # hexlified. import binascii import logging from securesystemslib import ecdsa_keys from securesystemslib import ed25519_keys from securesystemslib import exceptions from securesystemslib import formats from securesystemslib import rsa_keys from securesystemslib import settings from securesystemslib import util from securesystemslib.hash import digest # The hash algorithm to use in the generation of keyids. _KEY_ID_HASH_ALGORITHM = 'sha256' # Recommended RSA key sizes: # http://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm#table1 # According to the document above, revised May 6, 2003, RSA keys of # size 3072 provide security through 2031 and beyond. _DEFAULT_RSA_KEY_BITS = 3072 RSA_SIGNATURE_SCHEMES = [ 'rsassa-pss-md5', 'rsassa-pss-sha1', 'rsassa-pss-sha224', 'rsassa-pss-sha256', 'rsassa-pss-sha384', 'rsassa-pss-sha512', 'rsa-pkcs1v15-md5', 'rsa-pkcs1v15-sha1', 'rsa-pkcs1v15-sha224', 'rsa-pkcs1v15-sha256', 'rsa-pkcs1v15-sha384', 'rsa-pkcs1v15-sha512', ] logger = logging.getLogger(__name__) def generate_rsa_key(bits=_DEFAULT_RSA_KEY_BITS, scheme='rsassa-pss-sha256'): """ Generate public and private RSA keys, with modulus length 'bits'. In addition, a keyid identifier for the RSA key is generated. The object returned conforms to 'securesystemslib.formats.RSAKEY_SCHEMA' and has the form: {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', 'keyid': keyid, 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} The public and private keys are strings in PEM format. Although the PyCA cryptography library and/or its crypto backend might set a minimum key size, generate() enforces a minimum key size of 2048 bits. If 'bits' is unspecified, a 3072-bit RSA key is generated, which is the key size recommended by securesystemslib. These key size restrictions are only enforced for keys generated within securesystemslib. RSA keys with sizes lower than what we recommended may still be imported (e.g., with import_rsakey_from_pem(). >>> rsa_key = generate_rsa_key(bits=2048) >>> securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key) True >>> public = rsa_key['keyval']['public'] >>> private = rsa_key['keyval']['private'] >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(public) True >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(private) True bits: The key size, or key length, of the RSA key. 'bits' must be 2048, or greater, and a multiple of 256. scheme: The signature scheme used by the key. It must be one from the list `securesystemslib.keys.RSA_SIGNATURE_SCHEMES`. securesystemslib.exceptions.FormatError, if 'bits' is improperly or invalid (i.e., not an integer and not at least 2048). ValueError, if an exception occurs after calling the RSA key generation routine. The 'ValueError' exception is raised by the key generation function of the cryptography library called. None. A dictionary containing the RSA keys and other identifying information. Conforms to 'securesystemslib.formats.RSAKEY_SCHEMA'. """ # Does 'bits' have the correct format? This check will ensure 'bits' # conforms to 'securesystemslib.formats.RSAKEYBITS_SCHEMA'. 'bits' must be # an integer object, with a minimum value of 2048. Raise # 'securesystemslib.exceptions.FormatError' if the check fails. formats.RSAKEYBITS_SCHEMA.check_match(bits) formats.RSA_SCHEME_SCHEMA.check_match(scheme) # Begin building the RSA key dictionary. rsakey_dict = {} keytype = 'rsa' public = None private = None # Generate the public and private RSA keys. The pyca/cryptography module is # used to generate the actual key. Raise 'ValueError' if 'bits' is less than # 1024, although a 2048-bit minimum is enforced by # securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(). public, private = rsa_keys.generate_rsa_public_and_private(bits) # When loading in PEM keys, extract_pem() is called, which strips any # leading or trailing new line characters. Do the same here before generating # the keyid. public = extract_pem(public, private_pem=False) private = extract_pem(private, private_pem=True) # Generate the keyid of the RSA key. Note: The private key material is not # included in the generation of the 'keyid' identifier. Convert any '\r\n' # (e.g., Windows) newline characters to '\n' so that a consistent keyid is # generated. key_value = {'public': public.replace('\r\n', '\n'), 'private': ''} keyid = _get_keyid(keytype, scheme, key_value) # Build the 'rsakey_dict' dictionary. Update 'key_value' with the RSA # private key prior to adding 'key_value' to 'rsakey_dict'. key_value['private'] = private rsakey_dict['keytype'] = keytype rsakey_dict['scheme'] = scheme rsakey_dict['keyid'] = keyid rsakey_dict['keyid_hash_algorithms'] = settings.HASH_ALGORITHMS rsakey_dict['keyval'] = key_value return rsakey_dict def generate_ecdsa_key(scheme='ecdsa-sha2-nistp256'): """ Generate public and private ECDSA keys, with NIST P-256 + SHA256 (for hashing) being the default scheme. In addition, a keyid identifier for the ECDSA key is generated. The object returned conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA' and has the form: {'keytype': 'ecdsa', 'scheme', 'ecdsa-sha2-nistp256', 'keyid': keyid, 'keyval': {'public': '', 'private': ''}} The public and private keys are strings in TODO format. >>> ecdsa_key = generate_ecdsa_key(scheme='ecdsa-sha2-nistp256') >>> securesystemslib.formats.ECDSAKEY_SCHEMA.matches(ecdsa_key) True scheme: The ECDSA signature scheme. By default, ECDSA NIST P-256 is used, with SHA256 for hashing. securesystemslib.exceptions.FormatError, if 'scheme' is improperly formatted or invalid (i.e., not one of the supported ECDSA signature schemes). None. A dictionary containing the ECDSA keys and other identifying information. Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'. """ # Does 'scheme' have the correct format? # This check will ensure 'scheme' is properly formatted and is a supported # ECDSA signature scheme. Raise 'securesystemslib.exceptions.FormatError' if # the check fails. formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) # Begin building the ECDSA key dictionary. ecdsa_key = {} keytype = 'ecdsa' public = None private = None # Generate the public and private ECDSA keys with one of the supported # libraries. public, private = ecdsa_keys.generate_public_and_private(scheme) # Generate the keyid of the Ed25519 key. 'key_value' corresponds to the # 'keyval' entry of the 'Ed25519KEY_SCHEMA' dictionary. The private key # information is not included in the generation of the 'keyid' identifier. # Convert any '\r\n' (e.g., Windows) newline characters to '\n' so that a # consistent keyid is generated. key_value = {'public': public.replace('\r\n', '\n'), 'private': ''} keyid = _get_keyid(keytype, scheme, key_value) # Build the 'ed25519_key' dictionary. Update 'key_value' with the Ed25519 # private key prior to adding 'key_value' to 'ed25519_key'. key_value['private'] = private ecdsa_key['keytype'] = keytype ecdsa_key['scheme'] = scheme ecdsa_key['keyid'] = keyid ecdsa_key['keyval'] = key_value # Add "keyid_hash_algorithms" so that equal ECDSA keys with different keyids # can be associated using supported keyid_hash_algorithms. ecdsa_key['keyid_hash_algorithms'] = settings.HASH_ALGORITHMS return ecdsa_key def generate_ed25519_key(scheme='ed25519'): """ Generate public and private ED25519 keys, both of length 32-bytes, although they are hexlified to 64 bytes. In addition, a keyid identifier generated for the returned ED25519 object. The object returned conforms to 'securesystemslib.formats.ED25519KEY_SCHEMA' and has the form: {'keytype': 'ed25519', 'scheme': 'ed25519', 'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', 'keyval': {'public': '9ccf3f02b17f82febf5dd3bab878b767d8408...', 'private': 'ab310eae0e229a0eceee3947b6e0205dfab3...'}} >>> ed25519_key = generate_ed25519_key() >>> securesystemslib.formats.ED25519KEY_SCHEMA.matches(ed25519_key) True >>> len(ed25519_key['keyval']['public']) 64 >>> len(ed25519_key['keyval']['private']) 64 scheme: The signature scheme used by the generated Ed25519 key. None. The ED25519 keys are generated by calling either the optimized pure Python implementation of ed25519, or the ed25519 routines provided by 'pynacl'. A dictionary containing the ED25519 keys and other identifying information. Conforms to 'securesystemslib.formats.ED25519KEY_SCHEMA'. """ # Are the arguments properly formatted? If not, raise an # 'securesystemslib.exceptions.FormatError' exceptions. formats.ED25519_SIG_SCHEMA.check_match(scheme) # Begin building the Ed25519 key dictionary. ed25519_key = {} keytype = 'ed25519' public = None private = None # Generate the public and private Ed25519 key with the 'pynacl' library. # Unlike in the verification of Ed25519 signatures, do not fall back to the # optimized, pure python implementation provided by PyCA. Ed25519 should # always be generated with a backend like libsodium to prevent side-channel # attacks. public, private = ed25519_keys.generate_public_and_private() # Generate the keyid of the Ed25519 key. 'key_value' corresponds to the # 'keyval' entry of the 'Ed25519KEY_SCHEMA' dictionary. The private key # information is not included in the generation of the 'keyid' identifier. key_value = {'public': binascii.hexlify(public).decode(), 'private': ''} keyid = _get_keyid(keytype, scheme, key_value) # Build the 'ed25519_key' dictionary. Update 'key_value' with the Ed25519 # private key prior to adding 'key_value' to 'ed25519_key'. key_value['private'] = binascii.hexlify(private).decode() ed25519_key['keytype'] = keytype ed25519_key['scheme'] = scheme ed25519_key['keyid'] = keyid ed25519_key['keyid_hash_algorithms'] = settings.HASH_ALGORITHMS ed25519_key['keyval'] = key_value return ed25519_key def format_keyval_to_metadata(keytype, scheme, key_value, private=False): """ Return a dictionary conformant to 'securesystemslib.formats.KEY_SCHEMA'. If 'private' is True, include the private key. The dictionary returned has the form: {'keytype': keytype, 'scheme' : scheme, 'keyval': {'public': '...', 'private': '...'}} or if 'private' is False: {'keytype': keytype, 'scheme': scheme, 'keyval': {'public': '...', 'private': ''}} >>> ed25519_key = generate_ed25519_key() >>> key_val = ed25519_key['keyval'] >>> keytype = ed25519_key['keytype'] >>> scheme = ed25519_key['scheme'] >>> ed25519_metadata = \ format_keyval_to_metadata(keytype, scheme, key_val, private=True) >>> securesystemslib.formats.KEY_SCHEMA.matches(ed25519_metadata) True key_type: The 'rsa' or 'ed25519' strings. scheme: The signature scheme used by the key. key_value: A dictionary containing a private and public keys. 'key_value' is of the form: {'public': '...', 'private': '...'}}, conformant to 'securesystemslib.formats.KEYVAL_SCHEMA'. private: Indicates if the private key should be included in the dictionary returned. securesystemslib.exceptions.FormatError, if 'key_value' does not conform to 'securesystemslib.formats.KEYVAL_SCHEMA', or if the private key is not present in 'key_value' if requested by the caller via 'private'. None. A 'securesystemslib.formats.KEY_SCHEMA' dictionary. """ # Does 'keytype' have the correct format? # This check will ensure 'keytype' has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.KEYTYPE_SCHEMA.check_match(keytype) # Does 'scheme' have the correct format? formats.SCHEME_SCHEMA.check_match(scheme) # Does 'key_value' have the correct format? formats.KEYVAL_SCHEMA.check_match(key_value) if private is True: # If the caller requests (via the 'private' argument) to include a private # key in the returned dictionary, ensure the private key is actually # present in 'key_val' (a private key is optional for 'KEYVAL_SCHEMA' # dicts). if 'private' not in key_value: raise exceptions.FormatError('The required private key' ' is missing from: ' + repr(key_value)) else: return {'keytype': keytype, 'scheme': scheme, 'keyval': key_value} else: public_key_value = {'public': key_value['public']} return {'keytype': keytype, 'scheme': scheme, 'keyid_hash_algorithms': settings.HASH_ALGORITHMS, 'keyval': public_key_value} def format_metadata_to_key(key_metadata, default_keyid=None, keyid_hash_algorithms=None): """ Construct a key dictionary (e.g., securesystemslib.formats.RSAKEY_SCHEMA) according to the keytype of 'key_metadata'. The dict returned by this function has the exact format as the dict returned by one of the key generations functions, like generate_ed25519_key(). The dict returned has the form: {'keytype': keytype, 'scheme': scheme, 'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', 'keyval': {'public': '...', 'private': '...'}} For example, RSA key dictionaries in RSAKEY_SCHEMA format should be used by modules storing a collection of keys, such as with keydb.py. RSA keys as stored in metadata files use a different format, so this function should be called if an RSA key is extracted from one of these metadata files and need converting. The key generation functions create an entirely new key and return it in the format appropriate for 'keydb.py'. >>> ed25519_key = generate_ed25519_key() >>> key_val = ed25519_key['keyval'] >>> keytype = ed25519_key['keytype'] >>> scheme = ed25519_key['scheme'] >>> ed25519_metadata = \ format_keyval_to_metadata(keytype, scheme, key_val, private=True) >>> ed25519_key_2, junk = format_metadata_to_key(ed25519_metadata) >>> securesystemslib.formats.ED25519KEY_SCHEMA.matches(ed25519_key_2) True >>> ed25519_key == ed25519_key_2 True key_metadata: The key dictionary as stored in Metadata files, conforming to 'securesystemslib.formats.KEY_SCHEMA'. It has the form: {'keytype': '...', 'scheme': scheme, 'keyval': {'public': '...', 'private': '...'}} default_keyid: A default keyid associated with the key metadata. If this is not provided, the keyid will be calculated by _get_keyid using the default hash algorithm. If provided, the default keyid can be any string. keyid_hash_algorithms: An optional list of hash algorithms to use when generating keyids. Defaults to securesystemslib.settings.HASH_ALGORITHMS. securesystemslib.exceptions.FormatError, if 'key_metadata' does not conform to 'securesystemslib.formats.KEY_SCHEMA'. None. In the case of an RSA key, a dictionary conformant to 'securesystemslib.formats.RSAKEY_SCHEMA'. """ # Does 'key_metadata' have the correct format? # This check will ensure 'key_metadata' has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.KEY_SCHEMA.check_match(key_metadata) # Construct the dictionary to be returned. key_dict = {} keytype = key_metadata['keytype'] scheme = key_metadata['scheme'] key_value = key_metadata['keyval'] # Convert 'key_value' to 'securesystemslib.formats.KEY_SCHEMA' and generate # its hash The hash is in hexdigest form. if default_keyid is None: default_keyid = _get_keyid(keytype, scheme, key_value) keyids = set() keyids.add(default_keyid) if keyid_hash_algorithms is None: keyid_hash_algorithms = settings.HASH_ALGORITHMS for hash_algorithm in keyid_hash_algorithms: keyid = _get_keyid(keytype, scheme, key_value, hash_algorithm) keyids.add(keyid) # All the required key values gathered. Build 'key_dict'. # 'keyid_hash_algorithms' key_dict['keytype'] = keytype key_dict['scheme'] = scheme key_dict['keyid'] = default_keyid key_dict['keyid_hash_algorithms'] = keyid_hash_algorithms key_dict['keyval'] = key_value return key_dict, keyids def _get_keyid(keytype, scheme, key_value, hash_algorithm = 'sha256'): """Return the keyid of 'key_value'.""" # 'keyid' will be generated from an object conformant to KEY_SCHEMA, # which is the format Metadata files (e.g., root.json) store keys. # 'format_keyval_to_metadata()' returns the object needed by _get_keyid(). key_meta = format_keyval_to_metadata(keytype, scheme, key_value, private=False) # Convert the key to JSON Canonical format, suitable for adding # to digest objects. key_update_data = formats.encode_canonical(key_meta) # Create a digest object and call update(), using the JSON # canonical format of 'rskey_meta' as the update data. digest_object = digest(hash_algorithm) digest_object.update(key_update_data.encode('utf-8')) # 'keyid' becomes the hexadecimal representation of the hash. keyid = digest_object.hexdigest() return keyid def create_signature(key_dict, data): """ Return a signature dictionary of the form: {'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', 'sig': '...'}. The signing process will use the private key in key_dict['keyval']['private'] and 'data' to generate the signature. The following signature schemes are supported: 'RSASSA-PSS' RFC3447 - RSASSA-PSS http://www.ietf.org/rfc/rfc3447. 'ed25519' ed25519 - high-speed high security signatures http://ed25519.cr.yp.to/ Which signature to generate is determined by the key type of 'key_dict' and the available cryptography library specified in 'settings'. >>> ed25519_key = generate_ed25519_key() >>> data = 'The quick brown fox jumps over the lazy dog' >>> signature = create_signature(ed25519_key, data) >>> securesystemslib.formats.SIGNATURE_SCHEMA.matches(signature) True >>> len(signature['sig']) 128 >>> rsa_key = generate_rsa_key(2048) >>> signature = create_signature(rsa_key, data) >>> securesystemslib.formats.SIGNATURE_SCHEMA.matches(signature) True >>> ecdsa_key = generate_ecdsa_key() >>> signature = create_signature(ecdsa_key, data) >>> securesystemslib.formats.SIGNATURE_SCHEMA.matches(signature) True key_dict: A dictionary containing the keys. An example RSA key dict has the form: {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', 'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} The public and private keys are strings in PEM format. data: Data to be signed. This should be a bytes object; data should be encoded/serialized before it is passed here. The same value can be be passed into securesystemslib.verify_signature() (along with the public key) to later verify the signature. securesystemslib.exceptions.FormatError, if 'key_dict' is improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if 'key_dict' specifies an unsupported key type or signing scheme. TypeError, if 'key_dict' contains an invalid keytype. The cryptography library specified in 'settings' is called to perform the actual signing routine. A signature dictionary conformant to 'securesystemslib_format.SIGNATURE_SCHEMA'. """ # Does 'key_dict' have the correct format? # This check will ensure 'key_dict' has the appropriate number of objects # and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. # The key type of 'key_dict' must be either 'rsa' or 'ed25519'. formats.ANYKEY_SCHEMA.check_match(key_dict) # Signing the 'data' object requires a private key. Signing schemes that are # currently supported are: 'ed25519', 'ecdsa-sha2-nistp256', # 'ecdsa-sha2-nistp384' and rsa schemes defined in # `securesystemslib.keys.RSA_SIGNATURE_SCHEMES`. # RSASSA-PSS and RSA-PKCS1v15 keys and signatures can be generated and # verified by rsa_keys.py, and Ed25519 keys by PyNaCl and PyCA's # optimized, pure python implementation of Ed25519. signature = {} keytype = key_dict['keytype'] scheme = key_dict['scheme'] public = key_dict['keyval']['public'] private = key_dict['keyval']['private'] keyid = key_dict['keyid'] sig = None if keytype == 'rsa': if scheme in RSA_SIGNATURE_SCHEMES: private = private.replace('\r\n', '\n') sig, scheme = rsa_keys.create_rsa_signature(private, data, scheme) else: raise exceptions.UnsupportedAlgorithmError('Unsupported' ' RSA signature scheme specified: ' + repr(scheme)) elif keytype == 'ed25519': public = binascii.unhexlify(public.encode('utf-8')) private = binascii.unhexlify(private.encode('utf-8')) sig, scheme = ed25519_keys.create_signature(public, private, data, scheme) # Continue to support keytypes of ecdsa-sha2-nistp256 and ecdsa-sha2-nistp384 # for backwards compatibility with older securesystemslib releases elif keytype in ['ecdsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384']: sig, scheme = ecdsa_keys.create_signature(public, private, data, scheme) # 'securesystemslib.formats.ANYKEY_SCHEMA' should have detected invalid key # types. This is a defensive check against an invalid key type. else: # pragma: no cover raise TypeError('Invalid key type.') # Build the signature dictionary to be returned. # The hexadecimal representation of 'sig' is stored in the signature. signature['keyid'] = keyid signature['sig'] = binascii.hexlify(sig).decode() return signature def verify_signature(key_dict, signature, data): """ Determine whether the private key belonging to 'key_dict' produced 'signature'. verify_signature() will use the public key found in 'key_dict', the 'sig' objects contained in 'signature', and 'data' to complete the verification. >>> ed25519_key = generate_ed25519_key() >>> data = 'The quick brown fox jumps over the lazy dog' >>> signature = create_signature(ed25519_key, data) >>> verify_signature(ed25519_key, signature, data) True >>> verify_signature(ed25519_key, signature, 'bad_data') False >>> rsa_key = generate_rsa_key() >>> signature = create_signature(rsa_key, data) >>> verify_signature(rsa_key, signature, data) True >>> verify_signature(rsa_key, signature, 'bad_data') False >>> ecdsa_key = generate_ecdsa_key() >>> signature = create_signature(ecdsa_key, data) >>> verify_signature(ecdsa_key, signature, data) True >>> verify_signature(ecdsa_key, signature, 'bad_data') False key_dict: A dictionary containing the keys and other identifying information. If 'key_dict' is an RSA key, it has the form: {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', 'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} The public and private keys are strings in PEM format. signature: The signature dictionary produced by one of the key generation functions. 'signature' has the form: {'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', 'sig': sig}. Conformant to 'securesystemslib.formats.SIGNATURE_SCHEMA'. data: Data that the signature is expected to be over. This should be a bytes object; data should be encoded/serialized before it is passed here.) This is the same value that can be passed into securesystemslib.create_signature() in order to create the signature. securesystemslib.exceptions.FormatError, raised if either 'key_dict' or 'signature' are improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if 'key_dict' or 'signature' specifies an unsupported algorithm. securesystemslib.exceptions.CryptoError, if the KEYID in the given 'key_dict' does not match the KEYID in 'signature'. The cryptography library specified in 'settings' called to do the actual verification. Boolean. True if the signature is valid, False otherwise. """ # Does 'key_dict' have the correct format? # This check will ensure 'key_dict' has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.ANYKEY_SCHEMA.check_match(key_dict) # Does 'signature' have the correct format? formats.SIGNATURE_SCHEMA.check_match(signature) # Verify that the KEYID in 'key_dict' matches the KEYID listed in the # 'signature'. if key_dict['keyid'] != signature['keyid']: raise exceptions.CryptoError('The KEYID (' ' ' + repr(key_dict['keyid']) + ' ) in the given key does not match' ' the KEYID ( ' + repr(signature['keyid']) + ' ) in the signature.') else: logger.debug('The KEYIDs of key_dict and the signature match.') # Using the public key belonging to 'key_dict' # (i.e., rsakey_dict['keyval']['public']), verify whether 'signature' # was produced by key_dict's corresponding private key # key_dict['keyval']['private']. sig = signature['sig'] sig = binascii.unhexlify(sig.encode('utf-8')) public = key_dict['keyval']['public'] keytype = key_dict['keytype'] scheme = key_dict['scheme'] valid_signature = False if keytype == 'rsa': if scheme in RSA_SIGNATURE_SCHEMES: valid_signature = rsa_keys.verify_rsa_signature(sig, scheme, public, data) else: raise exceptions.UnsupportedAlgorithmError('Unsupported' ' signature scheme is specified: ' + repr(scheme)) elif keytype == 'ed25519': if scheme == 'ed25519': public = binascii.unhexlify(public.encode('utf-8')) valid_signature = ed25519_keys.verify_signature(public, scheme, sig, data) else: raise exceptions.UnsupportedAlgorithmError('Unsupported' ' signature scheme is specified: ' + repr(scheme)) elif keytype in ['ecdsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384']: if scheme in ['ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384']: valid_signature = ecdsa_keys.verify_signature(public, scheme, sig, data) else: raise exceptions.UnsupportedAlgorithmError('Unsupported' ' signature scheme is specified: ' + repr(scheme)) # 'securesystemslib.formats.ANYKEY_SCHEMA' should have detected invalid key # types. This is a defensive check against an invalid key type. else: # pragma: no cover raise TypeError('Unsupported key type.') return valid_signature def import_rsakey_from_private_pem(pem, scheme='rsassa-pss-sha256', password=None): """ Import the private RSA key stored in 'pem', and generate its public key (which will also be included in the returned rsakey object). In addition, a keyid identifier for the RSA key is generated. The object returned conforms to 'securesystemslib.formats.RSAKEY_SCHEMA' and has the form: {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', 'keyid': keyid, 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} The private key is a string in PEM format. >>> rsa_key = generate_rsa_key() >>> scheme = rsa_key['scheme'] >>> private = rsa_key['keyval']['private'] >>> passphrase = 'secret' >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) >>> rsa_key2 = import_rsakey_from_private_pem(encrypted_pem, scheme, passphrase) >>> securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key) True >>> securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key2) True pem: A string in PEM format. The private key is extracted and returned in an rsakey object. scheme: The signature scheme used by the imported key. password: (optional) The password, or passphrase, to decrypt the private part of the RSA key if it is encrypted. 'password' is not used directly as the encryption key, a stronger encryption key is derived from it. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if 'pem' specifies an unsupported key type. None. A dictionary containing the RSA keys and other identifying information. Conforms to 'securesystemslib.formats.RSAKEY_SCHEMA'. """ # Does 'pem' have the correct format? # This check will ensure 'pem' conforms to # 'securesystemslib.formats.PEMRSA_SCHEMA'. formats.PEMRSA_SCHEMA.check_match(pem) # Is 'scheme' properly formatted? formats.RSA_SCHEME_SCHEMA.check_match(scheme) if password is not None: formats.PASSWORD_SCHEMA.check_match(password) else: logger.debug('The password/passphrase is unset. The PEM is expected' ' to be unencrypted.') # Begin building the RSA key dictionary. rsakey_dict = {} keytype = 'rsa' public = None private = None # Generate the public and private RSA keys. The pyca/cryptography library # performs the actual crypto operations. public, private = \ rsa_keys.create_rsa_public_and_private_from_pem(pem, password) public = extract_pem(public, private_pem=False) private = extract_pem(private, private_pem=True) # Generate the keyid of the RSA key. 'key_value' corresponds to the # 'keyval' entry of the 'RSAKEY_SCHEMA' dictionary. The private key # information is not included in the generation of the 'keyid' identifier. # Convert any '\r\n' (e.g., Windows) newline characters to '\n' so that a # consistent keyid is generated. key_value = {'public': public.replace('\r\n', '\n'), 'private': ''} keyid = _get_keyid(keytype, scheme, key_value) # Build the 'rsakey_dict' dictionary. Update 'key_value' with the RSA # private key prior to adding 'key_value' to 'rsakey_dict'. key_value['private'] = private rsakey_dict['keytype'] = keytype rsakey_dict['scheme'] = scheme rsakey_dict['keyid'] = keyid rsakey_dict['keyval'] = key_value return rsakey_dict def import_rsakey_from_public_pem(pem, scheme='rsassa-pss-sha256'): """ Generate an RSA key object from 'pem'. In addition, a keyid identifier for the RSA key is generated. The object returned conforms to 'securesystemslib.formats.RSAKEY_SCHEMA' and has the form: {'keytype': 'rsa', 'keyid': keyid, 'keyval': {'public': '-----BEGIN PUBLIC KEY----- ...', 'private': ''}} The public portion of the RSA key is a string in PEM format. >>> rsa_key = generate_rsa_key() >>> public = rsa_key['keyval']['public'] >>> rsa_key['keyval']['private'] = '' >>> rsa_key2 = import_rsakey_from_public_pem(public) >>> securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key) True >>> securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key2) True pem: A string in PEM format (it should contain a public RSA key). securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. Only the public portion of the PEM is extracted. Leading or trailing whitespace is not included in the PEM string stored in the rsakey object returned. A dictionary containing the RSA keys and other identifying information. Conforms to 'securesystemslib.formats.RSAKEY_SCHEMA'. """ # Does 'pem' have the correct format? # This check will ensure arguments has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMRSA_SCHEMA.check_match(pem) # Does 'scheme' have the correct format? formats.RSA_SCHEME_SCHEMA.check_match(scheme) # Ensure the PEM string has a public header and footer. Although a simple # validation of 'pem' is performed here, a fully valid PEM string is needed # later to successfully verify signatures. Performing stricter validation of # PEMs are left to the external libraries that use 'pem'. if is_pem_public(pem): public_pem = extract_pem(pem, private_pem=False) else: raise exceptions.FormatError('Invalid public pem: ' + repr(pem)) # Begin building the RSA key dictionary. rsakey_dict = {} keytype = 'rsa' # Generate the keyid of the RSA key. 'key_value' corresponds to the # 'keyval' entry of the 'RSAKEY_SCHEMA' dictionary. The private key # information is not included in the generation of the 'keyid' identifier. # Convert any '\r\n' (e.g., Windows) newline characters to '\n' so that a # consistent keyid is generated. key_value = {'public': public_pem.replace('\r\n', '\n'), 'private': ''} keyid = _get_keyid(keytype, scheme, key_value) rsakey_dict['keytype'] = keytype rsakey_dict['scheme'] = scheme rsakey_dict['keyid'] = keyid rsakey_dict['keyval'] = key_value # Add "keyid_hash_algorithms" so that equal RSA keys with different keyids # can be associated using supported keyid_hash_algorithms. rsakey_dict['keyid_hash_algorithms'] = settings.HASH_ALGORITHMS return rsakey_dict def import_rsakey_from_pem(pem, scheme='rsassa-pss-sha256'): """ Import either a public or private PEM. In contrast to the other explicit import functions (import_rsakey_from_public_pem and import_rsakey_from_private_pem), this function is useful for when it is not known whether 'pem' is private or public. pem: A string in PEM format. scheme: The signature scheme used by the imported key. securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. None. A dictionary containing the RSA keys and other identifying information. Conforms to 'securesystemslib.formats.RSAKEY_SCHEMA'. """ # Does 'pem' have the correct format? # This check will ensure arguments has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMRSA_SCHEMA.check_match(pem) # Is 'scheme' properly formatted? formats.RSA_SCHEME_SCHEMA.check_match(scheme) public_pem = '' # Ensure the PEM string has a public or private header and footer. Although # a simple validation of 'pem' is performed here, a fully valid PEM string is # needed later to successfully verify signatures. Performing stricter # validation of PEMs are left to the external libraries that use 'pem'. if is_pem_public(pem): public_pem = extract_pem(pem, private_pem=False) elif is_pem_private(pem): # Return an rsakey object (RSAKEY_SCHEMA) with the private key included. return import_rsakey_from_private_pem(pem, scheme, password=None) else: raise exceptions.FormatError('PEM contains neither a' ' public nor private key: ' + repr(pem)) # Begin building the RSA key dictionary. rsakey_dict = {} keytype = 'rsa' # Generate the keyid of the RSA key. 'key_value' corresponds to the 'keyval' # entry of the 'RSAKEY_SCHEMA' dictionary. The private key information is # not included in the generation of the 'keyid' identifier. If a PEM is # found to contain a private key, the generated rsakey object should be # returned above. The following key object is for the case of a PEM with # only a public key. Convert any '\r\n' (e.g., Windows) newline characters # to '\n' so that a consistent keyid is generated. key_value = {'public': public_pem.replace('\r\n', '\n'), 'private': ''} keyid = _get_keyid(keytype, scheme, key_value) rsakey_dict['keytype'] = keytype rsakey_dict['scheme'] = scheme rsakey_dict['keyid'] = keyid rsakey_dict['keyval'] = key_value # Add "keyid_hash_algorithms" so that equal RSA keys with # different keyids can be associated using supported keyid_hash_algorithms. rsakey_dict['keyid_hash_algorithms'] = settings.HASH_ALGORITHMS return rsakey_dict def extract_pem(pem, private_pem=False): """ Extract only the portion of the pem that includes the header and footer, with any leading and trailing characters removed. The string returned has the following form: '-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----' or '-----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY-----' Note: This function assumes "pem" is a valid pem in the following format: pem header + key material + key footer. Crypto libraries (e.g., pyca's cryptography) that parse the pem returned by this function are expected to fully validate the pem. pem: A string in PEM format. private_pem: Boolean that indicates whether 'pem' is a private PEM. Private PEMs are not shown in exception messages. securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. Only the public and private portion of the PEM is extracted. Leading or trailing whitespace is not included in the returned PEM string. A PEM string (excluding leading and trailing newline characters). That is: pem header + key material + pem footer. """ if private_pem: pem_header = '-----BEGIN RSA PRIVATE KEY-----' pem_footer = '-----END RSA PRIVATE KEY-----' else: pem_header = '-----BEGIN PUBLIC KEY-----' pem_footer = '-----END PUBLIC KEY-----' header_start = 0 footer_start = 0 # Raise error message if the expected header or footer is not found in 'pem'. try: header_start = pem.index(pem_header) except ValueError: # Be careful not to print private key material in exception message. if not private_pem: raise exceptions.FormatError('Required PEM' ' header ' + repr(pem_header) + '\n not found in PEM' ' string: ' + repr(pem)) else: raise exceptions.FormatError('Required PEM' ' header ' + repr(pem_header) + '\n not found in private PEM string.') try: # Search for 'pem_footer' after the PEM header. footer_start = pem.index(pem_footer, header_start + len(pem_header)) except ValueError: # Be careful not to print private key material in exception message. if not private_pem: raise exceptions.FormatError('Required PEM' ' footer ' + repr(pem_footer) + '\n not found in PEM' ' string ' + repr(pem)) else: raise exceptions.FormatError('Required PEM' ' footer ' + repr(pem_footer) + '\n not found in private PEM string.') # Extract only the public portion of 'pem'. Leading or trailing whitespace # is excluded. pem = pem[header_start:footer_start + len(pem_footer)] return pem def encrypt_key(key_object, password): """ Return a string containing 'key_object' in encrypted form. Encrypted strings may be safely saved to a file. The corresponding decrypt_key() function can be applied to the encrypted string to restore the original key object. 'key_object' is a key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA). This function relies on the rsa_keys.py module to perform the actual encryption. Encrypted keys use AES-256-CTR-Mode, and passwords are strengthened with PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in 'securesystemslib.settings.PBKDF2_ITERATIONS' by the user). http://en.wikipedia.org/wiki/Advanced_Encryption_Standard http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29 https://en.wikipedia.org/wiki/PBKDF2 >>> ed25519_key = generate_ed25519_key() >>> password = 'secret' >>> encrypted_key = encrypt_key(ed25519_key, password).encode('utf-8') >>> securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key) True key_object: A key (containing also the private key portion) of the form 'securesystemslib.formats.ANYKEY_SCHEMA' password: The password, or passphrase, to encrypt the private part of the RSA key. 'password' is not used directly as the encryption key, a stronger encryption key is derived from it. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.CryptoError, if 'key_object' cannot be encrypted. None. An encrypted string of the form: 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA'. """ # Does 'key_object' have the correct format? # This check will ensure 'key_object' has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.ANYKEY_SCHEMA.check_match(key_object) # Does 'password' have the correct format? formats.PASSWORD_SCHEMA.check_match(password) # Encrypted string of 'key_object'. The encrypted string may be safely saved # to a file and stored offline. encrypted_key = None # Generate an encrypted string of 'key_object' using AES-256-CTR-Mode, where # 'password' is strengthened with PBKDF2-HMAC-SHA256. encrypted_key = rsa_keys.encrypt_key(key_object, password) return encrypted_key def decrypt_key(encrypted_key, passphrase): """ Return a string containing 'encrypted_key' in non-encrypted form. The decrypt_key() function can be applied to the encrypted string to restore the original key object, a key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA). This function calls rsa_keys.py to perform the actual decryption. Encrypted keys use AES-256-CTR-Mode and passwords are strengthened with PBKDF2-HMAC-SHA256 (100K iterations be default, but may be overriden in 'settings.py' by the user). http://en.wikipedia.org/wiki/Advanced_Encryption_Standard http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29 https://en.wikipedia.org/wiki/PBKDF2 >>> ed25519_key = generate_ed25519_key() >>> password = 'secret' >>> encrypted_key = encrypt_key(ed25519_key, password) >>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), password) >>> securesystemslib.formats.ANYKEY_SCHEMA.matches(decrypted_key) True >>> decrypted_key == ed25519_key True encrypted_key: An encrypted key (additional data is also included, such as salt, number of password iterations used for the derived encryption key, etc) of the form 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA'. 'encrypted_key' should have been generated with encrypt_key(). password: The password, or passphrase, to decrypt 'encrypted_key'. 'password' is not used directly as the encryption key, a stronger encryption key is derived from it. The supported general-purpose module takes care of re-deriving the encryption key. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.CryptoError, if 'encrypted_key' cannot be decrypted. None. A key object of the form: 'securesystemslib.formats.ANYKEY_SCHEMA' (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA). """ # Does 'encrypted_key' have the correct format? # This check ensures 'encrypted_key' has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.ENCRYPTEDKEY_SCHEMA.check_match(encrypted_key) # Does 'passphrase' have the correct format? formats.PASSWORD_SCHEMA.check_match(passphrase) # Store and return the decrypted key object. key_object = None # Decrypt 'encrypted_key' so that the original key object is restored. # encrypt_key() generates an encrypted string of the key object using # AES-256-CTR-Mode, where 'password' is strengthened with PBKDF2-HMAC-SHA256. key_object = rsa_keys.decrypt_key(encrypted_key, passphrase) # The corresponding encrypt_key() encrypts and stores key objects in # non-metadata format (i.e., original format of key object argument to # encrypt_key()) prior to returning. return key_object def create_rsa_encrypted_pem(private_key, passphrase): """ Return a string in PEM format (TraditionalOpenSSL), where the private part of the RSA key is encrypted using the best available encryption for a given key's backend. This is a curated (by cryptography.io) encryption choice and the algorithm may change over time. c.f. cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/ #cryptography.hazmat.primitives.serialization.BestAvailableEncryption >>> rsa_key = generate_rsa_key() >>> private = rsa_key['keyval']['private'] >>> passphrase = 'secret' >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem) True private_key: The private key string in PEM format. passphrase: The passphrase, or password, to encrypt the private part of the RSA key. 'passphrase' is not used directly as the encryption key, a stronger encryption key is derived from it. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.CryptoError, if an RSA key in encrypted PEM format cannot be created. TypeError, 'private_key' is unset. None. A string in PEM format, where the private RSA key is encrypted. Conforms to 'securesystemslib.formats.PEMRSA_SCHEMA'. """ # Does 'private_key' have the correct format? # This check will ensure 'private_key' has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMRSA_SCHEMA.check_match(private_key) # Does 'passphrase' have the correct format? formats.PASSWORD_SCHEMA.check_match(passphrase) encrypted_pem = None # Generate the public and private RSA keys. A 2048-bit minimum is enforced by # create_rsa_encrypted_pem() via a # securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(). encrypted_pem = rsa_keys.create_rsa_encrypted_pem(private_key, passphrase) return encrypted_pem def is_pem_public(pem): """ Checks if a passed PEM formatted string is a PUBLIC key, by looking for the following pattern: '-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----' >>> rsa_key = generate_rsa_key() >>> public = rsa_key['keyval']['public'] >>> private = rsa_key['keyval']['private'] >>> is_pem_public(public) True >>> is_pem_public(private) False pem: A string in PEM format. securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. None True if 'pem' is public and false otherwise. """ # Do the arguments have the correct format? # This check will ensure arguments have the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMRSA_SCHEMA.check_match(pem) pem_header = '-----BEGIN PUBLIC KEY-----' pem_footer = '-----END PUBLIC KEY-----' try: header_start = pem.index(pem_header) pem.index(pem_footer, header_start + len(pem_header)) except ValueError: return False return True def is_pem_private(pem, keytype='rsa'): """ Checks if a passed PEM formatted string is a PRIVATE key, by looking for the following patterns: '-----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY-----' '-----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY-----' >>> rsa_key = generate_rsa_key() >>> private = rsa_key['keyval']['private'] >>> public = rsa_key['keyval']['public'] >>> is_pem_private(private) True >>> is_pem_private(public) False pem: A string in PEM format. securesystemslib.exceptions.FormatError, if any of the arguments are improperly formatted. None True if 'pem' is private and false otherwise. """ # Do the arguments have the correct format? # This check will ensure arguments have the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMRSA_SCHEMA.check_match(pem) formats.NAME_SCHEMA.check_match(keytype) if keytype == 'rsa': pem_header = '-----BEGIN RSA PRIVATE KEY-----' pem_footer = '-----END RSA PRIVATE KEY-----' elif keytype == 'ec': pem_header = '-----BEGIN EC PRIVATE KEY-----' pem_footer = '-----END EC PRIVATE KEY-----' else: raise exceptions.FormatError('Unsupported key' ' type: ' + repr(keytype) + '. Supported keytypes: ["rsa", "ec"]') try: header_start = pem.index(pem_header) pem.index(pem_footer, header_start + len(pem_header)) except ValueError: return False return True def import_ed25519key_from_private_json(json_str, password=None): if password is not None: # This check will not fail, because a mal-formatted passed password fails # above and an entered password will always be a string (see get_password) # However, we include it in case PASSWORD_SCHEMA or get_password changes. formats.PASSWORD_SCHEMA.check_match(password) # Decrypt the loaded key file, calling the 'cryptography' library to # generate the derived encryption key from 'password'. Raise # 'securesystemslib.exceptions.CryptoError' if the decryption fails. key_object = decrypt_key(json_str.decode('utf-8'), password) else: logger.debug('No password was given. Attempting to import an' ' unencrypted file.') try: key_object = util.load_json_string(json_str.decode('utf-8')) # If the JSON could not be decoded, it is very likely, but not necessarily, # due to a non-empty password. except exceptions.Error: raise exceptions.CryptoError('Malformed Ed25519 key JSON, ' 'possibly due to encryption, ' 'but no password provided?') # Raise an exception if an unexpected key type is imported. if key_object['keytype'] != 'ed25519': message = 'Invalid key type loaded: ' + repr(key_object['keytype']) raise exceptions.FormatError(message) # Add "keyid_hash_algorithms" so that equal ed25519 keys with # different keyids can be associated using supported keyid_hash_algorithms. key_object['keyid_hash_algorithms'] = settings.HASH_ALGORITHMS return key_object def import_ecdsakey_from_private_pem(pem, scheme='ecdsa-sha2-nistp256', password=None): """ Import the private ECDSA key stored in 'pem', and generate its public key (which will also be included in the returned ECDSA key object). In addition, a keyid identifier for the ECDSA key is generated. The object returned conforms to: {'keytype': 'ecdsa', 'scheme': 'ecdsa-sha2-nistp256', 'keyid': keyid, 'keyval': {'public': '-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----', 'private': '-----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY-----'}} The private key is a string in PEM format. >>> ecdsa_key = generate_ecdsa_key() >>> private_pem = ecdsa_key['keyval']['private'] >>> ecdsa_key = import_ecdsakey_from_private_pem(private_pem) >>> securesystemslib.formats.ECDSAKEY_SCHEMA.matches(ecdsa_key) True pem: A string in PEM format. The private key is extracted and returned in an ecdsakey object. scheme: The signature scheme used by the imported key. password: (optional) The password, or passphrase, to decrypt the private part of the ECDSA key if it is encrypted. 'password' is not used directly as the encryption key, a stronger encryption key is derived from it. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if 'pem' specifies an unsupported key type. None. A dictionary containing the ECDSA keys and other identifying information. Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'. """ # Does 'pem' have the correct format? # This check will ensure 'pem' conforms to # 'securesystemslib.formats.ECDSARSA_SCHEMA'. formats.PEMECDSA_SCHEMA.check_match(pem) # Is 'scheme' properly formatted? formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) if password is not None: formats.PASSWORD_SCHEMA.check_match(password) else: logger.debug('The password/passphrase is unset. The PEM is expected' ' to be unencrypted.') # Begin building the ECDSA key dictionary. ecdsakey_dict = {} keytype = 'ecdsa' public = None private = None public, private = \ ecdsa_keys.create_ecdsa_public_and_private_from_pem(pem, password) # Generate the keyid of the ECDSA key. 'key_value' corresponds to the # 'keyval' entry of the 'ECDSAKEY_SCHEMA' dictionary. The private key # information is not included in the generation of the 'keyid' identifier. # Convert any '\r\n' (e.g., Windows) newline characters to '\n' so that a # consistent keyid is generated. key_value = {'public': public.replace('\r\n', '\n'), 'private': ''} keyid = _get_keyid(keytype, scheme, key_value) # Build the 'ecdsakey_dict' dictionary. Update 'key_value' with the ECDSA # private key prior to adding 'key_value' to 'ecdsakey_dict'. key_value['private'] = private ecdsakey_dict['keytype'] = keytype ecdsakey_dict['scheme'] = scheme ecdsakey_dict['keyid'] = keyid ecdsakey_dict['keyval'] = key_value # Add "keyid_hash_algorithms" so equal ECDSA keys with # different keyids can be associated using supported keyid_hash_algorithms ecdsakey_dict['keyid_hash_algorithms'] = settings.HASH_ALGORITHMS return ecdsakey_dict def import_ecdsakey_from_public_pem(pem, scheme='ecdsa-sha2-nistp256'): """ Generate an ECDSA key object from 'pem'. In addition, a keyid identifier for the ECDSA key is generated. The object returned conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA' and has the form: {'keytype': 'ecdsa', 'scheme': 'ecdsa-sha2-nistp256', 'keyid': keyid, 'keyval': {'public': '-----BEGIN PUBLIC KEY----- ...', 'private': ''}} The public portion of the ECDSA key is a string in PEM format. >>> ecdsa_key = generate_ecdsa_key() >>> public = ecdsa_key['keyval']['public'] >>> ecdsa_key['keyval']['private'] = '' >>> scheme = ecdsa_key['scheme'] >>> ecdsa_key2 = import_ecdsakey_from_public_pem(public, scheme) >>> securesystemslib.formats.ECDSAKEY_SCHEMA.matches(ecdsa_key) True >>> securesystemslib.formats.ECDSAKEY_SCHEMA.matches(ecdsa_key2) True pem: A string in PEM format (it should contain a public ECDSA key). scheme: The signature scheme used by the imported key. securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. Only the public portion of the PEM is extracted. Leading or trailing whitespace is not included in the PEM string stored in the rsakey object returned. A dictionary containing the ECDSA keys and other identifying information. Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'. """ # Does 'pem' have the correct format? # This check will ensure arguments has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMECDSA_SCHEMA.check_match(pem) # Is 'scheme' properly formatted? formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) # Ensure the PEM string has a public header and footer. Although a simple # validation of 'pem' is performed here, a fully valid PEM string is needed # later to successfully verify signatures. Performing stricter validation of # PEMs are left to the external libraries that use 'pem'. if is_pem_public(pem): public_pem = extract_pem(pem, private_pem=False) else: raise exceptions.FormatError('Invalid public' ' pem: ' + repr(pem)) # Begin building the ECDSA key dictionary. ecdsakey_dict = {} keytype = 'ecdsa' # Generate the keyid of the ECDSA key. 'key_value' corresponds to the # 'keyval' entry of the 'ECDSAKEY_SCHEMA' dictionary. The private key # information is not included in the generation of the 'keyid' identifier. # Convert any '\r\n' (e.g., Windows) newline characters to '\n' so that a # consistent keyid is generated. key_value = {'public': public_pem.replace('\r\n', '\n'), 'private': ''} keyid = _get_keyid(keytype, scheme, key_value) ecdsakey_dict['keytype'] = keytype ecdsakey_dict['scheme'] = scheme ecdsakey_dict['keyid'] = keyid ecdsakey_dict['keyval'] = key_value # Add "keyid_hash_algorithms" so that equal ECDSA keys with different keyids # can be associated using supported keyid_hash_algorithms. ecdsakey_dict['keyid_hash_algorithms'] = settings.HASH_ALGORITHMS return ecdsakey_dict def import_ecdsakey_from_pem(pem, scheme='ecdsa-sha2-nistp256'): """ Import either a public or private ECDSA PEM. In contrast to the other explicit import functions (import_ecdsakey_from_public_pem and import_ecdsakey_from_private_pem), this function is useful for when it is not known whether 'pem' is private or public. pem: A string in PEM format. scheme: The signature scheme used by the imported key. securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. None. A dictionary containing the ECDSA keys and other identifying information. Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'. """ # Does 'pem' have the correct format? # This check will ensure arguments has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMECDSA_SCHEMA.check_match(pem) # Is 'scheme' properly formatted? formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) public_pem = '' # Ensure the PEM string has a public or private header and footer. Although # a simple validation of 'pem' is performed here, a fully valid PEM string is # needed later to successfully verify signatures. Performing stricter # validation of PEMs are left to the external libraries that use 'pem'. if is_pem_public(pem): public_pem = extract_pem(pem, private_pem=False) elif is_pem_private(pem, 'ec'): # Return an ecdsakey object (ECDSAKEY_SCHEMA) with the private key included. return import_ecdsakey_from_private_pem(pem, password=None) else: raise exceptions.FormatError('PEM contains neither a public' ' nor private key: ' + repr(pem)) # Begin building the ECDSA key dictionary. ecdsakey_dict = {} keytype = 'ecdsa' # Generate the keyid of the ECDSA key. 'key_value' corresponds to the # 'keyval' entry of the 'ECDSAKEY_SCHEMA' dictionary. The private key # information is not included in the generation of the 'keyid' identifier. # If a PEM is found to contain a private key, the generated rsakey object # should be returned above. The following key object is for the case of a # PEM with only a public key. Convert any '\r\n' (e.g., Windows) newline # characters to '\n' so that a consistent keyid is generated. key_value = {'public': public_pem.replace('\r\n', '\n'), 'private': ''} keyid = _get_keyid(keytype, scheme, key_value) ecdsakey_dict['keytype'] = keytype ecdsakey_dict['scheme'] = scheme ecdsakey_dict['keyid'] = keyid ecdsakey_dict['keyval'] = key_value return ecdsakey_dict if __name__ == '__main__': # The interactive sessions of the documentation strings can # be tested by running 'keys.py' as a standalone module: # $ python keys.py import doctest doctest.testmod() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/process.py0000644000076500000240000002002600000000000022732 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ process.py Trishank Karthik Kuppusamy Lukas Puehringer September 25, 2018 See LICENSE for licensing information. Provide a common interface for Python's subprocess module to: - require the Py3 subprocess backport `subprocess32` on Python2, - namespace subprocess constants (DEVNULL, PIPE) and - provide a custom `subprocess.run` wrapper - provide a special `run_duplicate_streams` function """ import os import sys import io import tempfile import logging import time import shlex import six if six.PY2: import subprocess32 as subprocess # pragma: no cover pylint: disable=import-error else: # pragma: no cover import subprocess from securesystemslib import formats from securesystemslib import settings DEVNULL = subprocess.DEVNULL PIPE = subprocess.PIPE log = logging.getLogger(__name__) def _default_timeout(): """Helper to use securesystemslib.settings.SUBPROCESS_TIMEOUT as default argument, and still be able to modify it after the function definitions are evaluated. """ return settings.SUBPROCESS_TIMEOUT def run(cmd, check=True, timeout=_default_timeout(), **kwargs): """ Provide wrapper for `subprocess.run` (see https://github.com/python/cpython/blob/3.5/Lib/subprocess.py#L352-L399) where: * `timeout` has a default (securesystemslib.settings.SUBPROCESS_TIMEOUT), * `check` is `True` by default, * there is only one positional argument, i.e. `cmd` that can be either a str (will be split with shlex) or a list of str and * instead of raising a ValueError if both `input` and `stdin` are passed, `stdin` is ignored. cmd: The command and its arguments. (list of str, or str) Splits a string specifying a command and its argument into a list of substrings, if necessary. check: (default True) "If check is true, and the process exits with a non-zero exit code, a CalledProcessError exception will be raised. Attributes of that exception hold the arguments, the exit code, and stdout and stderr if they were captured." timeout: (default see securesystemslib.settings.SUBPROCESS_TIMEOUT) "The timeout argument is passed to Popen.communicate(). If the timeout expires, the child process will be killed and waited for. The TimeoutExpired exception will be re-raised after the child process has terminated." **kwargs: See subprocess.run and Frequently Used Arguments to Popen constructor for available kwargs. https://docs.python.org/3.5/library/subprocess.html#subprocess.run https://docs.python.org/3.5/library/subprocess.html#frequently-used-arguments securesystemslib.exceptions.FormatError: If the `cmd` is a list and does not match securesystemslib.formats.LIST_OF_ANY_STRING_SCHEMA. OSError: If the given command is not present or non-executable. subprocess.TimeoutExpired: If the process does not terminate after timeout seconds. Default is `settings.SUBPROCESS_TIMEOUT` The side effects of executing the given command in this environment. A subprocess.CompletedProcess instance. """ # Make list of command passed as string for convenience if isinstance(cmd, six.string_types): cmd = shlex.split(cmd) else: formats.LIST_OF_ANY_STRING_SCHEMA.check_match(cmd) # NOTE: The CPython implementation would raise a ValueError here, we just # don't pass on `stdin` if the user passes `input` and `stdin` # https://github.com/python/cpython/blob/3.5/Lib/subprocess.py#L378-L381 if kwargs.get("input") is not None and "stdin" in kwargs: log.debug("stdin and input arguments may not both be used. " "Ignoring passed stdin: " + str(kwargs["stdin"])) del kwargs["stdin"] return subprocess.run(cmd, check=check, timeout=timeout, **kwargs) def run_duplicate_streams(cmd, timeout=_default_timeout()): """ Provide a function that executes a command in a subprocess and, upon termination, returns its exit code and the contents of what was printed to its standard streams. * Might behave unexpectedly with interactive commands. * Might not duplicate output in real time, if the command buffers it (see e.g. `print("foo")` vs. `print("foo", flush=True)` in Python 3). cmd: The command and its arguments. (list of str, or str) Splits a string specifying a command and its argument into a list of substrings, if necessary. timeout: (default see settings.SUBPROCESS_TIMEOUT) If the timeout expires, the child process will be killed and waited for and then subprocess.TimeoutExpired will be raised. securesystemslib.exceptions.FormatError: If the `cmd` is a list and does not match securesystemslib.formats.LIST_OF_ANY_STRING_SCHEMA. OSError: If the given command is not present or non-executable. subprocess.TimeoutExpired: If the process does not terminate after timeout seconds. Default is `settings.SUBPROCESS_TIMEOUT` The side effects of executing the given command in this environment. A tuple of command's exit code, standard output and standard error contents. """ if isinstance(cmd, six.string_types): cmd = shlex.split(cmd) else: formats.LIST_OF_ANY_STRING_SCHEMA.check_match(cmd) # Use temporary files as targets for child process standard stream redirects # They seem to work better (i.e. do not hang) than pipes, when using # interactive commands like `vi`. stdout_fd, stdout_name = tempfile.mkstemp() stderr_fd, stderr_name = tempfile.mkstemp() try: with io.open(stdout_name, "r") as stdout_reader, \ os.fdopen(stdout_fd, "w") as stdout_writer, \ io.open(stderr_name, "r") as stderr_reader, \ os.fdopen(stderr_fd, "w") as stderr_writer: # Store stream results in mutable dict to update it inside nested helper _std = {"out": "", "err": ""} def _duplicate_streams(): """Helper to read from child process standard streams, write their contents to parent process standard streams, and build up return values for outer function. """ # Read until EOF but at most `io.DEFAULT_BUFFER_SIZE` bytes per call. # Reading and writing in reasonably sized chunks prevents us from # subverting a timeout, due to being busy for too long or indefinitely. stdout_part = stdout_reader.read(io.DEFAULT_BUFFER_SIZE) stderr_part = stderr_reader.read(io.DEFAULT_BUFFER_SIZE) sys.stdout.write(stdout_part) sys.stderr.write(stderr_part) sys.stdout.flush() sys.stderr.flush() _std["out"] += stdout_part _std["err"] += stderr_part # Start child process, writing its standard streams to temporary files proc = subprocess.Popen(cmd, stdout=stdout_writer, stderr=stderr_writer, universal_newlines=True) proc_start_time = time.time() # Duplicate streams until the process exits (or times out) while proc.poll() is None: # Time out as Python's `subprocess` would do it if (timeout is not None and time.time() > proc_start_time + timeout): proc.kill() proc.wait() raise subprocess.TimeoutExpired(cmd, timeout) _duplicate_streams() # Read/write once more to grab everything that the process wrote between # our last read in the loop and exiting, i.e. breaking the loop. _duplicate_streams() finally: # The work is done or was interrupted, the temp files can be removed os.remove(stdout_name) os.remove(stderr_name) # Return process exit code and captured streams return proc.poll(), _std["out"], _std["err"] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/rsa_keys.py0000755000076500000240000012256400000000000023111 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ rsa_keys.py Vladimir Diaz June 3, 2015. See LICENSE for licensing information. The goal of this module is to support public-key and general-purpose cryptography through the pyca/cryptography (available as 'cryptography' on pypi) library. The RSA-related functions provided include: generate_rsa_public_and_private() create_rsa_signature() verify_rsa_signature() create_rsa_encrypted_pem() create_rsa_public_and_private_from_pem() The general-purpose functions include: encrypt_key() decrypt_key() pyca/cryptography performs the actual cryptographic operations and the functions listed above can be viewed as the easy-to-use public interface. https://pypi.python.org/pypi/cryptography/ https://github.com/pyca/cryptography https://en.wikipedia.org/wiki/RSA_(algorithm) https://en.wikipedia.org/wiki/Advanced_Encryption_Standard https://en.wikipedia.org/wiki/PBKDF http://en.wikipedia.org/wiki/Scrypt securesystemslib key files are encrypted with the AES-256-CTR-Mode symmetric key algorithm. User passwords are strengthened with PBKDF2, currently set to 100,000 passphrase iterations. The previous evpy implementation used 1,000 iterations. PEM-encrypted RSA key files use the Triple Data Encryption Algorithm (3DES), and Cipher-block chaining (CBC) for the mode of operation. Password-Based Key Derivation Function 1 (PBKF1) + MD5. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import os import binascii import json CRYPTO = True NO_CRYPTO_MSG = 'RSA key support requires the cryptography library' try: # Import pyca/cryptography routines needed to generate and load cryptographic # keys in PEM format. from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.hazmat.backends import default_backend # Import Exception classes need to catch pyca/cryptography exceptions. from cryptography.exceptions import ( InvalidSignature, UnsupportedAlgorithm) # 'cryptography.hazmat.primitives.asymmetric' (i.e., pyca/cryptography's # public-key cryptography modules) supports algorithms like the Digital # Signature Algorithm (DSA) and the ECDSA (Elliptic Curve Digital Signature # Algorithm) encryption system. The 'rsa' module module is needed here to # generate RSA keys and PS from cryptography.hazmat.primitives.asymmetric import rsa # pyca/cryptography requires hash objects to generate PKCS#1 PSS # signatures (i.e., padding.PSS). The 'hmac' module is needed to verify # ciphertexts in encrypted key files. from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hmac # RSA's probabilistic signature scheme with appendix (RSASSA-PSS). # PKCS#1 v1.5 is available for compatibility with existing applications, but # RSASSA-PSS is encouraged for newer applications. RSASSA-PSS generates # a random salt to ensure the signature generated is probabilistic rather than # deterministic (e.g., PKCS#1 v1.5). # http://en.wikipedia.org/wiki/RSA-PSS#Schemes # https://tools.ietf.org/html/rfc3447#section-8.1 # The 'padding' module is needed for PSS signatures. from cryptography.hazmat.primitives.asymmetric import padding # Import pyca/cryptography's Key Derivation Function (KDF) module. # 'securesystemslib.keys.py' needs this module to derive a secret key according # to the Password-Based Key Derivation Function 2 specification. The derived # key is used as the symmetric key to encrypt securesystemslib key information. # PKCS#5 v2.0 PBKDF2 specification: http://tools.ietf.org/html/rfc2898#section-5.2 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC # pyca/cryptography's AES implementation available in 'ciphers.Cipher. and # 'ciphers.algorithms'. AES is a symmetric key algorithm that operates on # fixed block sizes of 128-bits. # https://en.wikipedia.org/wiki/Advanced_Encryption_Standard from cryptography.hazmat.primitives.ciphers import Cipher, algorithms # The mode of operation is presently set to CTR (CounTeR Mode) for symmetric # block encryption (AES-256, where the symmetric key is 256 bits). 'modes' can # be used as an argument to 'ciphers.Cipher' to specify the mode of operation # for the block cipher. The initial random block, or initialization vector # (IV), can be set to begin the process of incrementing the 128-bit blocks and # allowing the AES algorithm to perform cipher block operations on them. from cryptography.hazmat.primitives.ciphers import modes except ImportError: CRYPTO = False from securesystemslib import exceptions from securesystemslib import formats from securesystemslib import settings from securesystemslib import util from securesystemslib.hash import digest_from_rsa_scheme # Recommended RSA key sizes: # http://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm#table1 # According to the document above, revised May 6, 2003, RSA keys of size 3072 # provide security through 2031 and beyond. _DEFAULT_RSA_KEY_BITS = 3072 # The delimiter symbol used to separate the different sections of encrypted # files (i.e., salt, iterations, hmac, IV, ciphertext). This delimiter is # arbitrarily chosen and should not occur in the hexadecimal representations of # the fields it is separating. _ENCRYPTION_DELIMITER = '@@@@' # AES key size. Default key size = 32 bytes = AES-256. _AES_KEY_SIZE = 32 # Default salt size, in bytes. A 128-bit salt (i.e., a random sequence of data # to protect against attacks that use precomputed rainbow tables to crack # password hashes) is generated for PBKDF2. _SALT_SIZE = 16 # Default PBKDF2 passphrase iterations. The current "good enough" number # of passphrase iterations. We recommend that important keys, such as root, # be kept offline. 'settings.PBKDF2_ITERATIONS' should increase as CPU # speeds increase, set here at 100,000 iterations by default (in 2013). # Repository maintainers may opt to modify the default setting according to # their security needs and computational restrictions. A strong user password # is still important. Modifying the number of iterations will result in a new # derived key+PBDKF2 combination if the key is loaded and re-saved, overriding # any previous iteration setting used by the old '.key'. # https://en.wikipedia.org/wiki/PBKDF2 _PBKDF2_ITERATIONS = settings.PBKDF2_ITERATIONS def generate_rsa_public_and_private(bits=_DEFAULT_RSA_KEY_BITS): """ Generate public and private RSA keys with modulus length 'bits'. The public and private keys returned conform to 'securesystemslib.formats.PEMRSA_SCHEMA' and have the form: '-----BEGIN RSA PUBLIC KEY----- ...' or '-----BEGIN RSA PRIVATE KEY----- ...' The public and private keys are returned as strings in PEM format. 'generate_rsa_public_and_private()' enforces a minimum key size of 2048 bits. If 'bits' is unspecified, a 3072-bit RSA key is generated, which is the key size recommended by TUF. >>> public, private = generate_rsa_public_and_private(2048) >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(public) True >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(private) True bits: The key size, or key length, of the RSA key. 'bits' must be 2048, or greater. 'bits' defaults to 3072 if not specified. securesystemslib.exceptions.FormatError, if 'bits' does not contain the correct format. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. The RSA keys are generated from pyca/cryptography's rsa.generate_private_key() function. A (public, private) tuple containing the RSA keys in PEM format. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # Does 'bits' have the correct format? # This check will ensure 'bits' conforms to # 'securesystemslib.formats.RSAKEYBITS_SCHEMA'. 'bits' must be an integer # object, with a minimum value of 2048. Raise # 'securesystemslib.exceptions.FormatError' if the check fails. formats.RSAKEYBITS_SCHEMA.check_match(bits) # Generate the public and private RSA keys. The pyca/cryptography 'rsa' # module performs the actual key generation. The 'bits' argument is used, # and a 2048-bit minimum is enforced by # securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(). private_key = rsa.generate_private_key(public_exponent=65537, key_size=bits, backend=default_backend()) # Extract the public & private halves of the RSA key and generate their # PEM-formatted representations. Return the key pair as a (public, private) # tuple, where each RSA is a string in PEM format. private_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption()) # Need to generate the public pem from the private key before serialization # to PEM. public_key = private_key.public_key() public_pem = public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) return public_pem.decode('utf-8'), private_pem.decode('utf-8') def create_rsa_signature(private_key, data, scheme='rsassa-pss-sha256'): """ Generate a 'scheme' signature. The signature, and the signature scheme used, is returned as a (signature, scheme) tuple. The signing process will use 'private_key' to generate the signature of 'data'. RFC3447 - RSASSA-PSS http://www.ietf.org/rfc/rfc3447.txt >>> public, private = generate_rsa_public_and_private(2048) >>> data = 'The quick brown fox jumps over the lazy dog'.encode('utf-8') >>> scheme = 'rsassa-pss-sha256' >>> signature, scheme = create_rsa_signature(private, data, scheme) >>> securesystemslib.formats.NAME_SCHEMA.matches(scheme) True >>> scheme == 'rsassa-pss-sha256' True >>> securesystemslib.formats.PYCACRYPTOSIGNATURE_SCHEMA.matches(signature) True private_key: The private RSA key, a string in PEM format. data: Data (string) used by create_rsa_signature() to generate the signature. scheme: The signature scheme used to generate the signature. securesystemslib.exceptions.FormatError, if 'private_key' is improperly formatted. ValueError, if 'private_key' is unset. securesystemslib.exceptions.CryptoError, if the signature cannot be generated. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. pyca/cryptography's 'RSAPrivateKey.signer()' called to generate the signature. A (signature, scheme) tuple, where the signature is a string and the scheme is one of the supported RSA signature schemes. For example: 'rsassa-pss-sha256'. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # Does the arguments have the correct format? # If not, raise 'securesystemslib.exceptions.FormatError' if any of the # checks fail. formats.PEMRSA_SCHEMA.check_match(private_key) formats.DATA_SCHEMA.check_match(data) formats.RSA_SCHEME_SCHEMA.check_match(scheme) # Signing 'data' requires a private key. Currently supported RSA signature # schemes are defined in `securesystemslib.keys.RSA_SIGNATURE_SCHEMES`. signature = None # Verify the signature, but only if the private key has been set. The # private key is a NULL string if unset. Although it may be clearer to # explicitly check that 'private_key' is not '', we can/should check for a # value and not compare identities with the 'is' keyword. Up to this point # 'private_key' has variable size and can be an empty string. if not len(private_key): raise ValueError('The required private key is unset.') try: # 'private_key' (in PEM format) must first be converted to a # pyca/cryptography private key object before a signature can be # generated. private_key_object = load_pem_private_key(private_key.encode('utf-8'), password=None, backend=default_backend()) digest_obj = digest_from_rsa_scheme(scheme, 'pyca_crypto') if scheme.startswith('rsassa-pss'): # Generate an RSSA-PSS signature. Raise # 'securesystemslib.exceptions.CryptoError' for any of the expected # exceptions raised by pyca/cryptography. signature = private_key_object.sign( data, padding.PSS(mgf=padding.MGF1(digest_obj.algorithm), salt_length=digest_obj.algorithm.digest_size), digest_obj.algorithm) elif scheme.startswith('rsa-pkcs1v15'): # Generate an RSA-PKCS1v15 signature. Raise # 'securesystemslib.exceptions.CryptoError' for any of the expected # exceptions raised by pyca/cryptography. signature = private_key_object.sign(data, padding.PKCS1v15(), digest_obj.algorithm) # The RSA_SCHEME_SCHEMA.check_match() above should have validated 'scheme'. # This is a defensive check check.. else: # pragma: no cover raise exceptions.UnsupportedAlgorithmError('Unsupported' ' signature scheme is specified: ' + repr(scheme)) # If the PEM data could not be decrypted, or if its structure could not # be decoded successfully. except ValueError: raise exceptions.CryptoError('The private key' ' (in PEM format) could not be deserialized.') # 'TypeError' is raised if a password was given and the private key was # not encrypted, or if the key was encrypted but no password was # supplied. Note: A passphrase or password is not used when generating # 'private_key', since it should not be encrypted. except TypeError: raise exceptions.CryptoError('The private key was' ' unexpectedly encrypted.') # 'cryptography.exceptions.UnsupportedAlgorithm' is raised if the # serialized key is of a type that is not supported by the backend, or if # the key is encrypted with a symmetric cipher that is not supported by # the backend. except UnsupportedAlgorithm: # pragma: no cover raise exceptions.CryptoError('The private key is' ' encrypted with an unsupported algorithm.') return signature, scheme def verify_rsa_signature(signature, signature_scheme, public_key, data): """ Determine whether the corresponding private key of 'public_key' produced 'signature'. verify_signature() will use the public key, signature scheme, and 'data' to complete the verification. >>> public, private = generate_rsa_public_and_private(2048) >>> data = b'The quick brown fox jumps over the lazy dog' >>> scheme = 'rsassa-pss-sha256' >>> signature, scheme = create_rsa_signature(private, data, scheme) >>> verify_rsa_signature(signature, scheme, public, data) True >>> verify_rsa_signature(signature, scheme, public, b'bad_data') False signature: A signature, as a string. This is the signature returned by create_rsa_signature(). signature_scheme: A string that indicates the signature scheme used to generate 'signature'. Currently supported RSA signature schemes are defined in `securesystemslib.keys.RSA_SIGNATURE_SCHEMES`. public_key: The RSA public key, a string in PEM format. data: Data used by securesystemslib.keys.create_signature() to generate 'signature'. 'data' (a string) is needed here to verify 'signature'. securesystemslib.exceptions.FormatError, if 'signature', 'signature_scheme', 'public_key', or 'data' are improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if the signature scheme used by 'signature' is not one supported by securesystemslib.keys.create_signature(). securesystemslib.exceptions.CryptoError, if the private key cannot be decoded or its key type is unsupported. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. pyca/cryptography's RSAPublicKey.verifier() called to do the actual verification. Boolean. True if the signature is valid, False otherwise. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # Does 'public_key' have the correct format? # This check will ensure 'public_key' conforms to # 'securesystemslib.formats.PEMRSA_SCHEMA'. Raise # 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMRSA_SCHEMA.check_match(public_key) # Does 'signature_scheme' have the correct format? formats.RSA_SCHEME_SCHEMA.check_match(signature_scheme) # Does 'signature' have the correct format? formats.PYCACRYPTOSIGNATURE_SCHEMA.check_match(signature) # What about 'data'? formats.DATA_SCHEMA.check_match(data) # Verify the RSASSA-PSS signature with pyca/cryptography. try: public_key_object = serialization.load_pem_public_key( public_key.encode('utf-8'), backend=default_backend()) digest_obj = digest_from_rsa_scheme(signature_scheme, 'pyca_crypto') # verify() raises 'cryptography.exceptions.InvalidSignature' if the # signature is invalid. 'salt_length' is set to the digest size of the # hashing algorithm. try: if signature_scheme.startswith('rsassa-pss'): public_key_object.verify(signature, data, padding.PSS(mgf=padding.MGF1(digest_obj.algorithm), salt_length=digest_obj.algorithm.digest_size), digest_obj.algorithm) elif signature_scheme.startswith('rsa-pkcs1v15'): public_key_object.verify(signature, data, padding.PKCS1v15(), digest_obj.algorithm) # The RSA_SCHEME_SCHEMA.check_match() above should have validated 'scheme'. # This is a defensive check check.. else: # pragma: no cover raise exceptions.UnsupportedAlgorithmError('Unsupported' ' signature scheme is specified: ' + repr(signature_scheme)) return True except InvalidSignature: return False # Raised by load_pem_public_key(). except (ValueError, UnsupportedAlgorithm) as e: raise exceptions.CryptoError('The PEM could not be' ' decoded successfully, or contained an unsupported key type: ' + str(e)) def create_rsa_encrypted_pem(private_key, passphrase): """ Return a string in PEM format (TraditionalOpenSSL), where the private part of the RSA key is encrypted using the best available encryption for a given key's backend. This is a curated (by cryptography.io) encryption choice and the algorithm may change over time. c.f. cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/ #cryptography.hazmat.primitives.serialization.BestAvailableEncryption >>> public, private = generate_rsa_public_and_private(2048) >>> passphrase = 'secret' >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem) True private_key: The private key string in PEM format. passphrase: The passphrase, or password, to encrypt the private part of the RSA key. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.CryptoError, if the passed RSA key cannot be deserialized by pyca cryptography. ValueError, if 'private_key' is unset. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. A string in PEM format (TraditionalOpenSSL), where the private RSA key is encrypted. Conforms to 'securesystemslib.formats.PEMRSA_SCHEMA'. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # This check will ensure 'private_key' has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMRSA_SCHEMA.check_match(private_key) # Does 'passphrase' have the correct format? formats.PASSWORD_SCHEMA.check_match(passphrase) # 'private_key' may still be a NULL string after the # 'securesystemslib.formats.PEMRSA_SCHEMA' so we need an additional check if len(private_key): try: private_key = load_pem_private_key(private_key.encode('utf-8'), password=None, backend=default_backend()) except ValueError: raise exceptions.CryptoError('The private key' ' (in PEM format) could not be deserialized.') else: raise ValueError('The required private key is unset.') encrypted_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.BestAvailableEncryption( passphrase.encode('utf-8'))) return encrypted_pem.decode() def create_rsa_public_and_private_from_pem(pem, passphrase=None): """ Generate public and private RSA keys from an optionally encrypted PEM. The public and private keys returned conform to 'securesystemslib.formats.PEMRSA_SCHEMA' and have the form: '-----BEGIN RSA PUBLIC KEY----- ... -----END RSA PUBLIC KEY-----' and '-----BEGIN RSA PRIVATE KEY----- ...-----END RSA PRIVATE KEY-----' The public and private keys are returned as strings in PEM format. In case the private key part of 'pem' is encrypted pyca/cryptography's load_pem_private_key() method is passed passphrase. In the default case here, pyca/cryptography will decrypt with a PBKDF1+MD5 strengthened'passphrase', and 3DES with CBC mode for encryption/decryption. Alternatively, key data may be encrypted with AES-CTR-Mode and the passphrase strengthened with PBKDF2+SHA256, although this method is used only with securesystemslib encrypted key files. >>> public, private = generate_rsa_public_and_private(2048) >>> passphrase = 'secret' >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) >>> returned_public, returned_private = \ create_rsa_public_and_private_from_pem(encrypted_pem, passphrase) >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_public) True >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_private) True >>> public == returned_public True >>> private == returned_private True pem: A byte string in PEM format, where the private key can be encrypted. It has the form: '-----BEGIN RSA PRIVATE KEY-----\n Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC ...' passphrase: (optional) The passphrase, or password, to decrypt the private part of the RSA key. 'passphrase' is not directly used as the encryption key, instead it is used to derive a stronger symmetric key. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.CryptoError, if the public and private RSA keys cannot be generated from 'pem', or exported in PEM format. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. pyca/cryptography's 'serialization.load_pem_private_key()' called to perform the actual conversion from an encrypted RSA private key to PEM format. A (public, private) tuple containing the RSA keys in PEM format. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # Does 'encryped_pem' have the correct format? # This check will ensure 'pem' has the appropriate number # of objects and object types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.PEMRSA_SCHEMA.check_match(pem) # If passed, does 'passphrase' have the correct format? if passphrase is not None: formats.PASSWORD_SCHEMA.check_match(passphrase) passphrase = passphrase.encode('utf-8') # Generate a pyca/cryptography key object from 'pem'. The generated # pyca/cryptography key contains the required export methods needed to # generate the PEM-formatted representations of the public and private RSA # key. try: private_key = load_pem_private_key(pem.encode('utf-8'), passphrase, backend=default_backend()) # pyca/cryptography's expected exceptions for 'load_pem_private_key()': # ValueError: If the PEM data could not be decrypted. # (possibly because the passphrase is wrong)." # TypeError: If a password was given and the private key was not encrypted. # Or if the key was encrypted but no password was supplied. # UnsupportedAlgorithm: If the private key (or if the key is encrypted with # an unsupported symmetric cipher) is not supported by the backend. except (ValueError, TypeError, UnsupportedAlgorithm) as e: # Raise 'securesystemslib.exceptions.CryptoError' and pyca/cryptography's # exception message. Avoid propogating pyca/cryptography's exception trace # to avoid revealing sensitive error. raise exceptions.CryptoError('RSA (public, private) tuple' ' cannot be generated from the encrypted PEM string: ' + str(e)) # Export the public and private halves of the pyca/cryptography RSA key # object. The (public, private) tuple returned contains the public and # private RSA keys in PEM format, as strings. # Extract the public & private halves of the RSA key and generate their # PEM-formatted representations. Return the key pair as a (public, private) # tuple, where each RSA is a string in PEM format. private_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption()) # Need to generate the public key from the private one before serializing # to PEM format. public_key = private_key.public_key() public_pem = public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) return public_pem.decode(), private_pem.decode() def encrypt_key(key_object, password): """ Return a string containing 'key_object' in encrypted form. Encrypted strings may be safely saved to a file. The corresponding decrypt_key() function can be applied to the encrypted string to restore the original key object. 'key_object' is a securesystemslib key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA). This function calls the pyca/cryptography library to perform the encryption and derive a suitable encryption key. Whereas an encrypted PEM file uses the Triple Data Encryption Algorithm (3DES), the Cipher-block chaining (CBC) mode of operation, and the Password Based Key Derivation Function 1 (PBKF1) + MD5 to strengthen 'password', encrypted securesystemslib keys use AES-256-CTR-Mode and passwords strengthened with PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in 'settings.PBKDF2_ITERATIONS' by the user). http://en.wikipedia.org/wiki/Advanced_Encryption_Standard http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29 https://en.wikipedia.org/wiki/PBKDF2 >>> ed25519_key = {'keytype': 'ed25519', \ 'scheme': 'ed25519', \ 'keyid': \ 'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \ 'keyval': {'public': \ '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \ 'private': \ '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}} >>> passphrase = 'secret' >>> encrypted_key = encrypt_key(ed25519_key, passphrase) >>> securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key.encode('utf-8')) True key_object: The securesystemslib key object that should contain the private portion of the ED25519 key. password: The password, or passphrase, to encrypt the private part of the RSA key. 'password' is not used directly as the encryption key, a stronger encryption key is derived from it. securesystemslib.exceptions.FormatError, if any of the arguments are improperly formatted or 'key_object' does not contain the private portion of the key. securesystemslib.exceptions.CryptoError, if an Ed25519 key in encrypted securesystemslib format cannot be created. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. pyca/Cryptography cryptographic operations called to perform the actual encryption of 'key_object'. 'password' used to derive a suitable encryption key. An encrypted string in 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA' format. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # Do the arguments have the correct format? # Ensure the arguments have the appropriate number of objects and object # types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.ANYKEY_SCHEMA.check_match(key_object) # Does 'password' have the correct format? formats.PASSWORD_SCHEMA.check_match(password) # Ensure the private portion of the key is included in 'key_object'. if 'private' not in key_object['keyval'] or not key_object['keyval']['private']: raise exceptions.FormatError('Key object does not contain a private part.') # Derive a key (i.e., an appropriate encryption key and not the # user's password) from the given 'password'. Strengthen 'password' with # PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in # 'settings.PBKDF2_ITERATIONS' by the user). salt, iterations, derived_key = _generate_derived_key(password) # Store the derived key info in a dictionary, the object expected # by the non-public _encrypt() routine. derived_key_information = {'salt': salt, 'iterations': iterations, 'derived_key': derived_key} # Convert the key object to json string format and encrypt it with the # derived key. encrypted_key = _encrypt(json.dumps(key_object), derived_key_information) return encrypted_key def decrypt_key(encrypted_key, password): """ Return a string containing 'encrypted_key' in non-encrypted form. The decrypt_key() function can be applied to the encrypted string to restore the original key object, a securesystemslib key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA). This function calls the appropriate cryptography module (i.e., rsa_keys.py) to perform the decryption. Encrypted securesystemslib keys use AES-256-CTR-Mode and passwords strengthened with PBKDF2-HMAC-SHA256 (100K iterations be default, but may be overriden in 'settings.py' by the user). http://en.wikipedia.org/wiki/Advanced_Encryption_Standard http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29 https://en.wikipedia.org/wiki/PBKDF2 >>> ed25519_key = {'keytype': 'ed25519', \ 'scheme': 'ed25519', \ 'keyid': \ 'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \ 'keyval': {'public': \ '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \ 'private': \ '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}} >>> passphrase = 'secret' >>> encrypted_key = encrypt_key(ed25519_key, passphrase) >>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), passphrase) >>> securesystemslib.formats.ED25519KEY_SCHEMA.matches(decrypted_key) True >>> decrypted_key == ed25519_key True encrypted_key: An encrypted securesystemslib key (additional data is also included, such as salt, number of password iterations used for the derived encryption key, etc) of the form 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA'. 'encrypted_key' should have been generated with encrypted_key(). password: The password, or passphrase, to encrypt the private part of the RSA key. 'password' is not used directly as the encryption key, a stronger encryption key is derived from it. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.CryptoError, if a securesystemslib key cannot be decrypted from 'encrypted_key'. securesystemslib.exceptions.Error, if a valid securesystemslib key object is not found in 'encrypted_key'. securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography module is not available. The pyca/cryptography is library called to perform the actual decryption of 'encrypted_key'. The key derivation data stored in 'encrypted_key' is used to re-derive the encryption/decryption key. The decrypted key object in 'securesystemslib.formats.ANYKEY_SCHEMA' format. """ if not CRYPTO: # pragma: no cover raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) # Do the arguments have the correct format? # Ensure the arguments have the appropriate number of objects and object # types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if the check fails. formats.ENCRYPTEDKEY_SCHEMA.check_match(encrypted_key) # Does 'password' have the correct format? formats.PASSWORD_SCHEMA.check_match(password) # Decrypt 'encrypted_key', using 'password' (and additional key derivation # data like salts and password iterations) to re-derive the decryption key. json_data = _decrypt(encrypted_key, password) # Raise 'securesystemslib.exceptions.Error' if 'json_data' cannot be # deserialized to a valid 'securesystemslib.formats.ANYKEY_SCHEMA' key # object. key_object = util.load_json_string(json_data.decode()) return key_object def _generate_derived_key(password, salt=None, iterations=None): """ Generate a derived key by feeding 'password' to the Password-Based Key Derivation Function (PBKDF2). pyca/cryptography's PBKDF2 implementation is used in this module. 'salt' may be specified so that a previous derived key may be regenerated, otherwise '_SALT_SIZE' is used by default. 'iterations' is the number of SHA-256 iterations to perform, otherwise '_PBKDF2_ITERATIONS' is used by default. """ # Use pyca/cryptography's default backend (e.g., openSSL, CommonCrypto, etc.) # The default backend is not fixed and can be changed by pyca/cryptography # over time. backend = default_backend() # If 'salt' and 'iterations' are unspecified, a new derived key is generated. # If specified, a deterministic key is derived according to the given # 'salt' and 'iterrations' values. if salt is None: salt = os.urandom(_SALT_SIZE) if iterations is None: iterations = _PBKDF2_ITERATIONS # Derive an AES key with PBKDF2. The 'length' is the desired key length of # the derived key. pbkdf_object = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=iterations, backend=backend) derived_key = pbkdf_object.derive(password.encode('utf-8')) return salt, iterations, derived_key def _encrypt(key_data, derived_key_information): """ Encrypt 'key_data' using the Advanced Encryption Standard (AES-256) algorithm. 'derived_key_information' should contain a key strengthened by PBKDF2. The key size is 256 bits and AES's mode of operation is set to CTR (CounTeR Mode). The HMAC of the ciphertext is generated to ensure the ciphertext has not been modified. 'key_data' is the JSON string representation of the key. In the case of RSA keys, this format would be 'securesystemslib.formats.RSAKEY_SCHEMA': {'keytype': 'rsa', 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} 'derived_key_information' is a dictionary of the form: {'salt': '...', 'derived_key': '...', 'iterations': '...'} 'securesystemslib.exceptions.CryptoError' raised if the encryption fails. """ # Generate a random Initialization Vector (IV). Follow the provably secure # encrypt-then-MAC approach, which affords the ability to verify ciphertext # without needing to decrypt it and preventing an attacker from feeding the # block cipher malicious data. Modes like GCM provide both encryption and # authentication, whereas CTR only provides encryption. # Generate a random 128-bit IV. Random bits of data is needed for salts and # initialization vectors suitable for the encryption algorithms used in # 'rsa_keys.py'. iv = os.urandom(16) # Construct an AES-CTR Cipher object with the given key and a randomly # generated IV. symmetric_key = derived_key_information['derived_key'] encryptor = Cipher(algorithms.AES(symmetric_key), modes.CTR(iv), backend=default_backend()).encryptor() # Encrypt the plaintext and get the associated ciphertext. # Do we need to check for any exceptions? ciphertext = encryptor.update(key_data.encode('utf-8')) + encryptor.finalize() # Generate the hmac of the ciphertext to ensure it has not been modified. # The decryption routine may verify a ciphertext without having to perform # a decryption operation. symmetric_key = derived_key_information['derived_key'] salt = derived_key_information['salt'] hmac_object = hmac.HMAC(symmetric_key, hashes.SHA256(), backend=default_backend()) hmac_object.update(ciphertext) hmac_value = binascii.hexlify(hmac_object.finalize()) # Store the number of PBKDF2 iterations used to derive the symmetric key so # that the decryption routine can regenerate the symmetric key successfully. # The PBKDF2 iterations are allowed to vary for the keys loaded and saved. iterations = derived_key_information['iterations'] # Return the salt, iterations, hmac, initialization vector, and ciphertext # as a single string. These five values are delimited by # '_ENCRYPTION_DELIMITER' to make extraction easier. This delimiter is # arbitrarily chosen and should not occur in the hexadecimal representations # of the fields it is separating. return binascii.hexlify(salt).decode() + _ENCRYPTION_DELIMITER + \ str(iterations) + _ENCRYPTION_DELIMITER + \ hmac_value.decode() + _ENCRYPTION_DELIMITER + \ binascii.hexlify(iv).decode() + _ENCRYPTION_DELIMITER + \ binascii.hexlify(ciphertext).decode() def _decrypt(file_contents, password): """ The corresponding decryption routine for _encrypt(). 'securesystemslib.exceptions.CryptoError' raised if the decryption fails. """ # Extract the salt, iterations, hmac, initialization vector, and ciphertext # from 'file_contents'. These five values are delimited by # '_ENCRYPTION_DELIMITER'. This delimiter is arbitrarily chosen and should # not occur in the hexadecimal representations of the fields it is # separating. Raise 'securesystemslib.exceptions.CryptoError', if # 'file_contents' does not contains the expected data layout. try: salt, iterations, read_hmac, iv, ciphertext = \ file_contents.split(_ENCRYPTION_DELIMITER) except ValueError: raise exceptions.CryptoError('Invalid encrypted file.') # Ensure we have the expected raw data for the delimited cryptographic data. salt = binascii.unhexlify(salt.encode('utf-8')) iterations = int(iterations) iv = binascii.unhexlify(iv.encode('utf-8')) ciphertext = binascii.unhexlify(ciphertext.encode('utf-8')) # Generate derived key from 'password'. The salt and iterations are # specified so that the expected derived key is regenerated correctly. # Discard the old "salt" and "iterations" values, as we only need the old # derived key. junk_old_salt, junk_old_iterations, symmetric_key = \ _generate_derived_key(password, salt, iterations) # Verify the hmac to ensure the ciphertext is valid and has not been altered. # See the encryption routine for why we use the encrypt-then-MAC approach. # The decryption routine may verify a ciphertext without having to perform # a decryption operation. generated_hmac_object = hmac.HMAC(symmetric_key, hashes.SHA256(), backend=default_backend()) generated_hmac_object.update(ciphertext) generated_hmac = binascii.hexlify(generated_hmac_object.finalize()) if not util.digests_are_equal(generated_hmac.decode(), read_hmac): raise exceptions.CryptoError('Decryption failed.') # Construct a Cipher object, with the key and iv. decryptor = Cipher(algorithms.AES(symmetric_key), modes.CTR(iv), backend=default_backend()).decryptor() # Decryption gets us the authenticated plaintext. plaintext = decryptor.update(ciphertext) + decryptor.finalize() return plaintext if __name__ == '__main__': # The interactive sessions of the documentation strings can be tested by # running 'rsa_keys.py' as a standalone module: # $ python rsa_keys.py import doctest doctest.testmod() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/schema.py0000755000076500000240000007025000000000000022523 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ schema.py Geremy Condra Vladimir Diaz Refactored April 30, 2012 (previously named checkjson.py). -Vlad See LICENSE for licensing information. Provide a variety of classes that compare objects based on their format and determine if they match. These classes, or schemas, do not simply check the type of the objects being compared, but inspect additional aspects of the objects like names and the number of items included. For example: >>> good = {'first': 'Marty', 'last': 'McFly'} >>> bad = {'sdfsfd': 'Biff', 'last': 'Tannen'} >>> bad = {'sdfsfd': 'Biff', 'last': 'Tannen'} >>> schema = Object(first=AnyString(), last=AnyString()) >>> schema.matches(good) True >>> schema.matches(bad) False In the process of determining if the two objects matched the template, securesystemslib.schema.Object() inspected the named keys of both dictionaries. In the case of the 'bad' dict, a 'first' dict key could not be found. As a result, 'bad' was flagged a mismatch. 'schema.py' provides additional schemas for testing objects based on other criteria. See 'securesystemslib.formats.py' and the rest of this module for extensive examples. Anything related to the checking of securesystemslib objects and their formats can be found in 'formats.py'. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import re import sys import six from securesystemslib import exceptions class Schema: """ A schema matches a set of possible Python objects, of types that are encodable in JSON. 'Schema' is the base class for the other classes defined in this module. All derived classes should implement check_match(). """ def matches(self, object): """ Return True if 'object' matches this schema, False if it doesn't. If the caller wishes to signal an error on a failed match, check_match() should be called, which will raise a 'exceptions.FormatError' exception. """ try: self.check_match(object) except exceptions.FormatError: return False else: return True def check_match(self, object): """ Abstract method. Classes that inherit from 'Schema' must implement check_match(). If 'object' matches the schema, check_match() should simply return. If 'object' does not match the schema, 'exceptions.FormatError' should be raised. """ raise NotImplementedError() class Any(Schema): """ Matches any single object. Whereas other schemas explicitly state the required type of its argument, Any() does not. It simply does a 'pass' when 'check_match()' is called and at the point where the schema is instantiated. Supported methods include: matches(): returns a Boolean result. check_match(): passed >>> schema = Any() >>> schema.matches('A String') True >>> schema.matches([1, 'list']) True """ def __init__(self): pass def check_match(self, object): pass class String(Schema): """ Matches a particular string. The argument object must be a string and be equal to a specific string value. At instantiation, the string is set and any future comparisons are checked against this internal string value. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = String('Hi') >>> schema.matches('Hi') True >>> schema.matches('Not hi') False """ def __init__(self, string): if not isinstance(string, six.string_types): raise exceptions.FormatError('Expected a string but' ' got ' + repr(string)) self._string = string def check_match(self, object): if self._string != object: raise exceptions.FormatError( 'Expected ' + repr(self._string) + ' got ' + repr(object)) class AnyString(Schema): """ Matches any string, but not a non-string object. This schema can be viewed as the Any() schema applied to Strings, but an additional check is performed to ensure only strings are considered. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = AnyString() >>> schema.matches('') True >>> schema.matches('a string') True >>> schema.matches(['a']) False >>> schema.matches(3) False >>> schema.matches(u'a unicode string') True >>> schema.matches({}) False """ def __init__(self): pass def check_match(self, object): if not isinstance(object, six.string_types): raise exceptions.FormatError('Expected a string' ' but got ' + repr(object)) class AnyNonemptyString(AnyString): """ Matches any string with one or more characters. This schema can be viewed as the Any() schema applied to Strings, but an additional check is performed to ensure only strings are considered and that said strings have at least one character. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = AnyNonemptyString() >>> schema.matches('') False >>> schema.matches('a string') True >>> schema.matches(['a']) False >>> schema.matches(3) False >>> schema.matches(u'a unicode string') True >>> schema.matches({}) False """ def check_match(self, object): AnyString.check_match(self, object) if object == "": raise exceptions.FormatError('Expected a string' ' with at least one character but got ' + repr(object)) class AnyBytes(Schema): """ Matches any byte string, but not a non-byte object. This schema can be viewed as the Any() schema applied to byte strings, but an additional check is performed to ensure only strings are considered. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = AnyBytes() >>> schema.matches(b'') True >>> schema.matches(b'a string') True >>> schema.matches(['a']) False >>> schema.matches(3) False >>> schema.matches({}) False """ def __init__(self): pass def check_match(self, object): if not isinstance(object, six.binary_type): raise exceptions.FormatError('Expected a byte string' ' but got ' + repr(object)) class LengthString(Schema): """ Matches any string of a specified length. The argument object must be a string. At instantiation, the string length is set and any future comparisons are checked against this internal string value length. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = LengthString(5) >>> schema.matches('Hello') True >>> schema.matches('Hi') False """ def __init__(self, length): if isinstance(length, bool) or not isinstance(length, six.integer_types): # We need to check for bool as a special case, since bool # is for historical reasons a subtype of int. raise exceptions.FormatError( 'Got ' + repr(length) + ' instead of an integer.') self._string_length = length def check_match(self, object): if not isinstance(object, six.string_types): raise exceptions.FormatError('Expected a string but' ' got ' + repr(object)) if len(object) != self._string_length: raise exceptions.FormatError('Expected a string of' ' length ' + repr(self._string_length)) class LengthBytes(Schema): """ Matches any Bytes of a specified length. The argument object must be either a str() in Python 2, or bytes() in Python 3. At instantiation, the bytes length is set and any future comparisons are checked against this internal bytes value length. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = LengthBytes(5) >>> schema.matches(b'Hello') True >>> schema.matches(b'Hi') False """ def __init__(self, length): if isinstance(length, bool) or not isinstance(length, six.integer_types): # We need to check for bool as a special case, since bool # is for historical reasons a subtype of int. raise exceptions.FormatError( 'Got ' + repr(length) + ' instead of an integer.') self._bytes_length = length def check_match(self, object): if not isinstance(object, six.binary_type): raise exceptions.FormatError('Expected a byte but' ' got ' + repr(object)) if len(object) != self._bytes_length: raise exceptions.FormatError('Expected a byte of' ' length ' + repr(self._bytes_length)) class OneOf(Schema): """ Matches an object that matches any one of several schemas. OneOf() returns a result as soon as one of its recognized sub-schemas is encountered in the object argument. When OneOf() is instantiated, its supported sub-schemas are specified by a sequence type (e.g., a list, tuple, etc.). A mismatch is returned after checking all sub-schemas and not finding a supported type. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = OneOf([ListOf(Integer()), String('Hello'), String('bye')]) >>> schema.matches(3) False >>> schema.matches('bye') True >>> schema.matches([]) True >>> schema.matches([1,2]) True >>> schema.matches(['Hi']) False """ def __init__(self, alternatives): # Ensure each item of the list contains the expected object type. if not isinstance(alternatives, list): raise exceptions.FormatError('Expected a list but' ' got ' + repr(alternatives)) for alternative in alternatives: if not isinstance(alternative, Schema): raise exceptions.FormatError('List contains an' ' invalid item ' + repr(alternative)) self._alternatives = alternatives def check_match(self, object): # Simply return as soon as we find a match. # Raise 'exceptions.FormatError' if no matches are found. for alternative in self._alternatives: if alternative.matches(object): return raise exceptions.FormatError('Object did not match a' ' recognized alternative.') class AllOf(Schema): """ Matches the intersection of a list of schemas. The object being tested must match all of the required sub-schemas. Unlike OneOf(), which can return a result as soon as a match is found in one of its supported sub-schemas, AllOf() must verify each sub-schema before returning a result. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = AllOf([Any(), AnyString(), String('a')]) >>> schema.matches('b') False >>> schema.matches('a') True """ def __init__(self, required_schemas): # Ensure each item of the list contains the expected object type. if not isinstance(required_schemas, list): raise exceptions.FormatError('Expected a list but' ' got' + repr(required_schemas)) for schema in required_schemas: if not isinstance(schema, Schema): raise exceptions.FormatError('List contains an' ' invalid item ' + repr(schema)) self._required_schemas = required_schemas[:] def check_match(self, object): for required_schema in self._required_schemas: required_schema.check_match(object) class Boolean(Schema): """ Matches a boolean. The object argument must be one of True or False. All other types are flagged as mismatches. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = Boolean() >>> schema.matches(True) and schema.matches(False) True >>> schema.matches(11) False """ def __init__(self): pass def check_match(self, object): if not isinstance(object, bool): raise exceptions.FormatError( 'Got ' + repr(object) + ' instead of a boolean.') class ListOf(Schema): """ Matches a homogeneous list of some sub-schema. That is, all the sub-schema must be of the same type. The object argument must be a sequence type (e.g., a list, tuple, etc.). When ListOf() is instantiated, a minimum and maximum count can be specified for the homogeneous sub-schema list. If min_count is set to 'n', the object argument sequence must contain 'n' items. See ListOf()'s __init__ method for the expected arguments. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = ListOf(RegularExpression('(?:..)*')) >>> schema.matches('hi') False >>> schema.matches([]) True >>> schema.matches({}) False >>> schema.matches(['Hi', 'this', 'list', 'is', 'full', 'of', 'even', 'strs']) True >>> schema.matches(['This', 'one', 'is not']) False >>> schema = ListOf(Integer(), min_count=3, max_count=10) >>> schema.matches([3]*2) False >>> schema.matches([3]*3) True >>> schema.matches([3]*10) True >>> schema.matches([3]*11) False """ def __init__(self, schema, min_count=0, max_count=sys.maxsize, list_name='list'): """ Create a new ListOf schema. schema: The pattern to match. min_count: The minimum number of sub-schema in 'schema'. max_count: The maximum number of sub-schema in 'schema'. list_name: A string identifier for the ListOf object. """ if not isinstance(schema, Schema): message = 'Expected Schema type but got '+repr(schema) raise exceptions.FormatError(message) self._schema = schema self._min_count = min_count self._max_count = max_count self._list_name = list_name def check_match(self, object): if not isinstance(object, (list, tuple)): raise exceptions.FormatError( 'Expected object of type {} but got type {}'.format( self._list_name, type(object).__name__)) # Check if all the items in the 'object' list # match 'schema'. for item in object: try: self._schema.check_match(item) except exceptions.FormatError as e: raise exceptions.FormatError( str(e) + ' in ' + repr(self._list_name)) # Raise exception if the number of items in the list is # not within the expected range. if not (self._min_count <= len(object) <= self._max_count): raise exceptions.FormatError( 'Length of ' + repr(self._list_name) + ' out of range.') class Integer(Schema): """ Matches an integer. A range can be specified. For example, only integers between 8 and 42 can be set as a requirement. The object argument is also checked against a Boolean type, since booleans have historically been considered a sub-type of integer. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = Integer() >>> schema.matches(99) True >>> schema.matches(False) False >>> schema.matches('a string') False >>> Integer(lo=10, hi=30).matches(25) True >>> Integer(lo=10, hi=30).matches(5) False """ def __init__(self, lo = -2147483648, hi = 2147483647): """ Create a new Integer schema. lo: The minimum value the int object argument can be. hi: The maximum value the int object argument can be. """ self._lo = lo self._hi = hi def check_match(self, object): if isinstance(object, bool) or not isinstance(object, six.integer_types): # We need to check for bool as a special case, since bool # is for historical reasons a subtype of int. raise exceptions.FormatError( 'Got ' + repr(object) + ' instead of an integer.') elif not (self._lo <= object <= self._hi): int_range = '[' + repr(self._lo) + ', ' + repr(self._hi) + '].' raise exceptions.FormatError( repr(object) + ' not in range ' + int_range) class DictOf(Schema): """ Matches a mapping from items matching a particular key-schema to items matching a value-schema (i.e., the object being checked must be a dict). Note that in JSON, keys must be strings. In the example below, the keys of the dict must be one of the letters contained in 'aeiou' and the value must be a structure containing any two strings. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = DictOf(RegularExpression(r'[aeiou]+'), Struct([AnyString(), AnyString()])) >>> schema.matches('') False >>> schema.matches({}) True >>> schema.matches({'a': ['x', 'y'], 'e' : ['', '']}) True >>> schema.matches({'a': ['x', 3], 'e' : ['', '']}) False >>> schema.matches({'a': ['x', 'y'], 'e' : ['', ''], 'd' : ['a', 'b']}) False """ def __init__(self, key_schema, value_schema): """ Create a new DictOf schema. key_schema: The dictionary's key. value_schema: The dictionary's value. """ if not isinstance(key_schema, Schema): raise exceptions.FormatError('Expected Schema but' ' got ' + repr(key_schema)) if not isinstance(value_schema, Schema): raise exceptions.FormatError('Expected Schema but' ' got ' + repr(value_schema)) self._key_schema = key_schema self._value_schema = value_schema def check_match(self, object): if not isinstance(object, dict): raise exceptions.FormatError('Expected a dict but' ' got ' + repr(object)) for key, value in six.iteritems(object): self._key_schema.check_match(key) self._value_schema.check_match(value) class Optional(Schema): """ Provide a way for the Object() schema to accept optional dictionary keys. The Object() schema outlines how a dictionary should look, such as the names for dict keys and the object type of the dict values. Optional()'s intended use is as a sub-schema to Object(). Object() flags an object as a mismatch if a required key is not encountered, however, dictionary keys labeled Optional() are not required to appear in the object's list of required keys. If an Optional() key IS found, Optional()'s sub-schemas are then verified. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = Object(k1=String('X'), k2=Optional(String('Y'))) >>> schema.matches({'k1': 'X', 'k2': 'Y'}) True >>> schema.matches({'k1': 'X', 'k2': 'Z'}) False >>> schema.matches({'k1': 'X'}) True """ def __init__(self, schema): if not isinstance(schema, Schema): raise exceptions.FormatError('Expected Schema, but' ' got ' + repr(schema)) self._schema = schema def check_match(self, object): self._schema.check_match(object) class Object(Schema): """ Matches a dict from specified keys to key-specific types. Unrecognized keys are allowed. The Object() schema outlines how a dictionary should look, such as the names for dict keys and the object type of the dict values. See schema.Optional() to learn how Object() incorporates optional sub-schemas. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = Object(a=AnyString(), bc=Struct([Integer(), Integer()])) >>> schema.matches({'a':'ZYYY', 'bc':[5,9]}) True >>> schema.matches({'a':'ZYYY', 'bc':[5,9], 'xx':5}) True >>> schema.matches({'a':'ZYYY', 'bc':[5,9,3]}) False >>> schema.matches({'a':'ZYYY'}) False """ def __init__(self, object_name='object', **required): """ Create a new Object schema. object_name: A string identifier for the object argument. A variable number of keyword arguments is accepted. """ # Ensure valid arguments. for key, schema in six.iteritems(required): if not isinstance(schema, Schema): raise exceptions.FormatError('Expected Schema but' ' got ' + repr(schema)) self._object_name = object_name self._required = list(required.items()) def check_match(self, object): if not isinstance(object, dict): raise exceptions.FormatError( 'Wanted a ' + repr(self._object_name) + '.') # (key, schema) = (a, AnyString()) = (a=AnyString()) for key, schema in self._required: # Check if 'object' has all the required dict keys. If not one of the # required keys, check if it is an Optional(). try: item = object[key] except KeyError: # If not an Optional schema, raise an exception. if not isinstance(schema, Optional): raise exceptions.FormatError( 'Missing key ' + repr(key) + ' in ' + repr(self._object_name)) # Check that 'object's schema matches Object()'s schema for this # particular 'key'. else: try: schema.check_match(item) except exceptions.FormatError as e: raise exceptions.FormatError( str(e) + ' in ' + self._object_name + '.' + key) class Struct(Schema): """ Matches a non-homogeneous list of items. The sub-schemas are allowed to vary. The object argument must be a sequence type (e.g., a list, tuple, etc.). There is also an option to specify that additional schemas not explicitly defined at instantiation are allowed. See __init__() for the complete list of arguments accepted. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = Struct([ListOf(AnyString()), AnyString(), String('X')]) >>> schema.matches(False) False >>> schema.matches('Foo') False >>> schema.matches([[], 'Q', 'X']) True >>> schema.matches([[], 'Q', 'D']) False >>> schema.matches([[3], 'Q', 'X']) False >>> schema.matches([[], 'Q', 'X', 'Y']) False >>> schema = Struct([String('X')], allow_more=True) >>> schema.matches([]) False >>> schema.matches(['X']) True >>> schema.matches(['X', 'Y']) True >>> schema.matches(['X', ['Y', 'Z']]) True >>> schema.matches([['X']]) False >>> schema = Struct([String('X'), Integer()], [Integer()]) >>> schema.matches([]) False >>> schema.matches({}) False >>> schema.matches(['X']) False >>> schema.matches(['X', 3]) True >>> schema.matches(['X', 3, 9]) True >>> schema.matches(['X', 3, 9, 11]) False >>> schema.matches(['X', 3, 'A']) False """ def __init__(self, sub_schemas, optional_schemas=None, allow_more=False, struct_name='list'): """ Create a new Struct schema. sub_schemas: The sub-schemas recognized. optional_schemas: Optional list. If none is given, it will be "[]". allow_more: Specifies that an optional list of types is allowed. struct_name: A string identifier for the Struct object. """ if optional_schemas is None: optional_schemas = [] # Ensure each item of the list contains the expected object type. if not isinstance(sub_schemas, (list, tuple)): raise exceptions.FormatError( 'Expected Schema but got ' + repr(sub_schemas)) for schema in sub_schemas: if not isinstance(schema, Schema): raise exceptions.FormatError('Expected Schema but' ' got ' + repr(schema)) self._sub_schemas = sub_schemas + optional_schemas self._min = len(sub_schemas) self._allow_more = allow_more self._struct_name = struct_name def check_match(self, object): if not isinstance(object, (list, tuple)): raise exceptions.FormatError( 'Expected ' + repr(self._struct_name) + '; but got ' + repr(object)) elif len(object) < self._min: raise exceptions.FormatError( 'Too few fields in ' + self._struct_name) elif len(object) > len(self._sub_schemas) and not self._allow_more: raise exceptions.FormatError( 'Too many fields in ' + self._struct_name) # Iterate through the items of 'object', checking against each schema in # the list of schemas allowed (i.e., the sub-schemas and also any optional # schemas. The lenth of 'object' must be less than the length of the # required schemas + the optional schemas. However, 'object' is allowed to # be only as large as the length of the required schemas. In the while # loop below, we check against these two cases. index = 0 while index < len(object) and index < len(self._sub_schemas): item = object[index] schema = self._sub_schemas[index] schema.check_match(item) index = index + 1 class RegularExpression(Schema): """ Matches any string that matches a given regular expression. The RE pattern set when RegularExpression is instantiated must not be None. See __init__() for a complete list of accepted arguments. Supported methods include: matches(): returns a Boolean result. check_match(): raises 'exceptions.FormatError' on a mismatch. >>> schema = RegularExpression('h.*d') >>> schema.matches('hello world') True >>> schema.matches('Hello World') False >>> schema.matches('hello world!') False >>> schema.matches([33, 'Hello']) False """ def __init__(self, pattern=None, modifiers=0, re_object=None, re_name=None): """ Create a new regular expression schema. pattern: The pattern to match, or None if re_object is provided. modifiers: Flags to use when compiling the pattern. re_object: A compiled regular expression object. re_name: Identifier for the regular expression object. """ if not isinstance(pattern, six.string_types): if pattern is not None: raise exceptions.FormatError( repr(pattern) + ' is not a string.') if re_object is None: if pattern is None: raise exceptions.FormatError( 'Cannot compare against an unset regular expression') if not pattern.endswith('$'): pattern += '$' re_object = re.compile(pattern, modifiers) self._re_object = re_object if re_name is None: if pattern is not None: re_name = 'pattern /' + pattern + '/' else: re_name = 'pattern' self._re_name = re_name def check_match(self, object): if not isinstance(object, six.string_types) or not self._re_object.match(object): raise exceptions.FormatError( repr(object) + ' did not match ' + repr(self._re_name)) if __name__ == '__main__': # The interactive sessions of the documentation strings can # be tested by running schema.py as a standalone module. # python -B schema.py. import doctest doctest.testmod() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/settings.py0000755000076500000240000000355200000000000023124 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ settings.py Vladimir Diaz December 7, 2016 See LICENSE for licensing information. Store all crypto-related settings used by securesystemslib. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals # Set a directory that should be used for all temporary files. If this # is None, then the system default will be used. The system default # will also be used if a directory path set here is invalid or # unusable. temporary_directory = None # The current "good enough" number of PBKDF2 passphrase iterations. We # recommend that important keys, such as root, be kept offline. # 'toto.settings.PBKDF2_ITERATIONS' should increase as CPU speeds increase, set # here at 100,000 iterations by default (in 2013). The repository maintainer # may opt to modify the default setting according to their security needs and # computational restrictions. A strong user password is still important. # Modifying the number of iterations will result in a new derived key+PBDKF2 # combination if the key is loaded and re-saved, overriding any previous # iteration setting used in the old '' key file. # https://en.wikipedia.org/wiki/PBKDF2 PBKDF2_ITERATIONS = 100000 # The algorithm(s) in HASH_ALGORITHMS are used to generate key IDs. HASH_ALGORITHMS = ['sha256', 'sha512'] # Used in securesystemslib.process, to raise a subprocess.TimeoutExpired if # a started subprocess does not terminate before the here specified seconds SUBPROCESS_TIMEOUT = 3 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/signer.py0000644000076500000240000000777000000000000022556 0ustar00lukpstaff00000000000000"""Signer interface and example interface implementations. The goal of this module is to provide a signing interface supporting multiple signing implementations and a couple of example implementations. """ import abc import securesystemslib.keys as sslib_keys from typing import Dict class Signature: """A container class containing information about a signature. Contains a signature and the keyid uniquely identifying the key used to generate the signature. Provides utility methods to easily create an object from a dictionary and return the dictionary representation of the object. Attributes: keyid: HEX string used as a unique identifier of the key. signature: HEX string representing the signature. """ def __init__(self, keyid: str, sig: str): self.keyid = keyid self.signature = sig @classmethod def from_dict(cls, signature_dict: Dict) -> "Signature": """Creates a Signature object from its JSON/dict representation. Arguments: signature_dict: A dict containing a valid keyid and a signature. Note that the fields in it should be named "keyid" and "sig" respectively. Raises: KeyError: If any of the "keyid" and "sig" fields are missing from the signature_dict. Returns: A "Signature" instance. """ return cls(signature_dict["keyid"], signature_dict["sig"]) def to_dict(self) -> Dict: """Returns the JSON-serializable dictionary representation of self.""" return { "keyid": self.keyid, "sig": self.signature } class Signer: """Signer interface created to support multiple signing implementations.""" __metaclass__ = abc.ABCMeta @abc.abstractmethod def sign(payload: bytes) -> "Signature": """Signs a given payload by the key assigned to the Signer instance. Arguments: payload: The bytes to be signed. Returns: Returns a "Signature" class instance. """ raise NotImplementedError # pragma: no cover class SSlibSigner(Signer): """A securesystemslib signer implementation. Provides a sign method to generate a cryptographic signature with a securesystemslib-style rsa, ed25519 or ecdsa private key on the instance. The signature scheme is determined by the key and must be one of: - rsa(ssa-pss|pkcs1v15)-(md5|sha1|sha224|sha256|sha384|sha512) (12 schemes) - ed25519 - ecdsa-sha2-nistp256 See "securesystemslib.interface" for functions to generate and load keys. Attributes: key_dict: A securesystemslib-style key dictionary, which includes a keyid, key type, signature scheme, and the public and private key values, e.g.:: { "keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyid": "f30a0870d026980100c0573bd557394f8c1bbd6...", "keyval": { "public": "-----BEGIN RSA PUBLIC KEY----- ...", "private": "-----BEGIN RSA PRIVATE KEY----- ..." } } The public and private keys are strings in PEM format. """ def __init__(self, key_dict: Dict): self.key_dict = key_dict def sign(self, payload: bytes) -> "Signature": """Signs a given payload by the key assigned to the SSlibSigner instance. Arguments: payload: The bytes to be signed. Raises: securesystemslib.exceptions.FormatError: Key argument is malformed. securesystemslib.exceptions.CryptoError, \ securesystemslib.exceptions.UnsupportedAlgorithmError: Signing errors. Returns: Returns a "Signature" class instance. """ sig_dict = sslib_keys.create_signature(self.key_dict, payload) return Signature(**sig_dict) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/storage.py0000644000076500000240000001650600000000000022730 0ustar00lukpstaff00000000000000""" storage.py Joshua Lock April 9, 2020 See LICENSE for licensing information. Provides an interface for filesystem interactions, StorageBackendInterface. """ from __future__ import absolute_import from __future__ import unicode_literals import abc import errno import logging import os import shutil import six from securesystemslib import exceptions logger = logging.getLogger(__name__) if six.PY2: FileNotFoundError = OSError # pragma: no cover class StorageBackendInterface(): """ Defines an interface for abstract storage operations which can be implemented for a variety of storage solutions, such as remote and local filesystems. """ __metaclass__ = abc.ABCMeta @abc.abstractmethod def get(self, filepath): """ A context manager for 'with' statements that is used for retrieving files from a storage backend and cleans up the files upon exit. with storage_backend.get('/path/to/file') as file_object: # operations # file is now closed filepath: The full path of the file to be retrieved. securesystemslib.exceptions.StorageError, if the file does not exist or is no accessible. A ContextManager object that emits a file-like object for the file at 'filepath'. """ raise NotImplementedError # pragma: no cover @abc.abstractmethod def put(self, fileobj, filepath): """ Store a file-like object in the storage backend. The file-like object is read from the beginning, not its current offset (if any). fileobj: The file-like object to be stored. filepath: The full path to the location where 'fileobj' will be stored. securesystemslib.exceptions.StorageError, if the file can not be stored. None """ raise NotImplementedError # pragma: no cover @abc.abstractmethod def remove(self, filepath): """ Remove the file at 'filepath' from the storage. filepath: The full path to the file. securesystemslib.exceptions.StorageError, if the file can not be removed. None """ raise NotImplementedError # pragma: no cover @abc.abstractmethod def getsize(self, filepath): """ Retrieve the size, in bytes, of the file at 'filepath'. filepath: The full path to the file. securesystemslib.exceptions.StorageError, if the file does not exist or is not accessible. The size in bytes of the file at 'filepath'. """ raise NotImplementedError # pragma: no cover @abc.abstractmethod def create_folder(self, filepath): """ Create a folder at filepath and ensure all intermediate components of the path exist. Passing an empty string for filepath does nothing and does not raise an exception. filepath: The full path of the folder to be created. securesystemslib.exceptions.StorageError, if the folder can not be created. None """ raise NotImplementedError # pragma: no cover @abc.abstractmethod def list_folder(self, filepath): """ List the contents of the folder at 'filepath'. filepath: The full path of the folder to be listed. securesystemslib.exceptions.StorageError, if the file does not exist or is not accessible. A list containing the names of the files in the folder. May be an empty list. """ raise NotImplementedError # pragma: no cover class FilesystemBackend(StorageBackendInterface): """ A concrete implementation of StorageBackendInterface which interacts with local filesystems using Python standard library functions. """ # As FilesystemBackend is effectively a stateless wrapper around various # standard library operations, we only ever need a single instance of it. # That single instance is safe to be (re-)used by all callers. Therefore # implement the singleton pattern to avoid uneccesarily creating multiple # objects. _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = object.__new__(cls, *args, **kwargs) return cls._instance class GetFile(object): # Implementing get() as a function with the @contextmanager decorator # doesn't allow us to cleanly capture exceptions thrown by the underlying # implementation and bubble up our generic # securesystemslib.exceptions.StorageError, therefore we implement get as # a class and also assign the class to the 'get' attribute of the parent # FilesystemBackend class. def __init__(self, filepath): self.filepath = filepath def __enter__(self): try: self.file_object = open(self.filepath, 'rb') return self.file_object except (FileNotFoundError, IOError): raise exceptions.StorageError( "Can't open %s" % self.filepath) def __exit__(self, exc_type, exc_val, traceback): self.file_object.close() # Map our class ContextManager implementation to the function expected of the # securesystemslib.storage.StorageBackendInterface.get definition get = GetFile def put(self, fileobj, filepath): # If we are passed an open file, seek to the beginning such that we are # copying the entire contents if not fileobj.closed: fileobj.seek(0) try: with open(filepath, 'wb') as destination_file: shutil.copyfileobj(fileobj, destination_file) # Force the destination file to be written to disk from Python's internal # and the operating system's buffers. os.fsync() should follow flush(). destination_file.flush() os.fsync(destination_file.fileno()) except (OSError, IOError): raise exceptions.StorageError( "Can't write file %s" % filepath) def remove(self, filepath): try: os.remove(filepath) except (FileNotFoundError, PermissionError, OSError): # pragma: no cover raise exceptions.StorageError( "Can't remove file %s" % filepath) def getsize(self, filepath): try: return os.path.getsize(filepath) except OSError: raise exceptions.StorageError( "Can't access file %s" % filepath) def create_folder(self, filepath): try: os.makedirs(filepath) except OSError as e: # 'OSError' raised if the leaf directory already exists or cannot be # created. Check for case where 'filepath' has already been created and # silently ignore. if e.errno == errno.EEXIST: pass elif e.errno == errno.ENOENT and not filepath: raise exceptions.StorageError( "Can't create a folder with an empty filepath!") else: raise exceptions.StorageError( "Can't create folder at %s" % filepath) def list_folder(self, filepath): try: return os.listdir(filepath) except FileNotFoundError: raise exceptions.StorageError( "Can't list folder at %s" % filepath) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/unittest_toolbox.py0000755000076500000240000001005700000000000024707 0ustar00lukpstaff00000000000000""" unittest_toolbox.py Konstantin Andrianov. March 26, 2012. See LICENSE for licensing information. Provides an array of various methods for unit testing. Use it instead of actual unittest module. This module builds on unittest module. Specifically, Modified_TestCase is a derived class from unittest.TestCase. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import os import shutil import unittest import tempfile import random import string class Modified_TestCase(unittest.TestCase): """ Provide additional test-setup methods to make testing of module's methods-under-test as independent as possible. If you want to modify setUp()/tearDown() do: class Your_Test_Class(modified_TestCase): def setUp(): your setup modification your setup modification ... modified_TestCase.setUp(self) make_temp_directory(self, directory=None): Creates and returns an absolute path of a temporary directory. make_temp_file(self, suffix='.txt', directory=None): Creates and returns an absolute path of an empty temp file. make_temp_data_file(self, suffix='', directory=None, data = junk_data): Returns an absolute path of a temp file containing some data. random_path(self, length = 7): Generate a 'random' path consisting of n-length strings of random chars. Static Methods: -------------- Following methods are static because they technically don't operate on any instances of the class, what they do is: they modify class variables (dictionaries) that are shared among all instances of the class. So it is possible to call them without instantiating the class. random_string(length=7): Generate a 'length' long string of random characters. """ def setUp(self): self._cleanup = [] def tearDown(self): for cleanup_function in self._cleanup: # Perform clean up by executing clean-up functions. try: # OSError will occur if the directory was already removed. cleanup_function() except OSError: pass def make_temp_directory(self, directory=None): """Creates and returns an absolute path of a directory.""" prefix = self.__class__.__name__+'_' temp_directory = tempfile.mkdtemp(prefix=prefix, dir=directory) def _destroy_temp_directory(): shutil.rmtree(temp_directory) self._cleanup.append(_destroy_temp_directory) return temp_directory def make_temp_file(self, suffix='.txt', directory=None): """Creates and returns an absolute path of an empty file.""" prefix='tmp_file_'+self.__class__.__name__+'_' temp_file = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=directory) def _destroy_temp_file(): os.unlink(temp_file[1]) self._cleanup.append(_destroy_temp_file) return temp_file[1] def make_temp_data_file(self, suffix='', directory=None, data = 'junk data'): """Returns an absolute path of a temp file containing data.""" temp_file_path = self.make_temp_file(suffix=suffix, directory=directory) temp_file = open(temp_file_path, 'wt') temp_file.write(data) temp_file.close() return temp_file_path def random_path(self, length = 7): """Generate a 'random' path consisting of random n-length strings.""" rand_path = '/'+self.random_string(length) for i in range(2): rand_path = os.path.join(rand_path, self.random_string(length)) return rand_path @staticmethod def random_string(length=15): """Generate a random string of specified length.""" rand_str = '' for letter in range(length): rand_str += random.choice('abcdefABCDEF' +string.digits) return rand_str ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/securesystemslib/util.py0000755000076500000240000003202300000000000022234 0ustar00lukpstaff00000000000000""" util.py Konstantin Andrianov March 24, 2012. Derived from original util.py written by Geremy Condra. See LICENSE for licensing information. Provides utility services. This module supplies utility functions such as: get_file_details() that computes the length and hash of a file, import_json that tries to import a working json module, load_json_* functions, etc. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import os import logging from securesystemslib import exceptions from securesystemslib import formats from securesystemslib.hash import digest_fileobject from securesystemslib.storage import FilesystemBackend logger = logging.getLogger(__name__) def get_file_details(filepath, hash_algorithms=['sha256'], storage_backend=None): """ To get file's length and hash information. The hash is computed using the sha256 algorithm. This function is used in the signerlib.py and updater.py modules. filepath: Absolute file path of a file. hash_algorithms: A list of hash algorithms with which the file's hash should be computed. Defaults to ['sha256'] storage_backend: An object which implements securesystemslib.storage.StorageBackendInterface. When no object is passed a FilesystemBackend will be instantiated and used. securesystemslib.exceptions.FormatError: If hash of the file does not match HASHDICT_SCHEMA. securesystemslib.exceptions.Error: If 'filepath' does not exist. A tuple (length, hashes) describing 'filepath'. """ # Making sure that the format of 'filepath' is a path string. # 'securesystemslib.exceptions.FormatError' is raised on incorrect format. formats.PATH_SCHEMA.check_match(filepath) formats.HASHALGORITHMS_SCHEMA.check_match(hash_algorithms) if storage_backend is None: storage_backend = FilesystemBackend() file_length = get_file_length(filepath, storage_backend) file_hashes = get_file_hashes(filepath, hash_algorithms, storage_backend) return file_length, file_hashes def get_file_hashes(filepath, hash_algorithms=['sha256'], storage_backend=None): """ Compute hash(es) of the file at filepath using each of the specified hash algorithms. If no algorithms are specified, then the hash is computed using the SHA-256 algorithm. filepath: Absolute file path of a file. hash_algorithms: A list of hash algorithms with which the file's hash should be computed. Defaults to ['sha256'] storage_backend: An object which implements securesystemslib.storage.StorageBackendInterface. When no object is passed a FilesystemBackend will be instantiated and used. securesystemslib.exceptions.FormatError: If hash of the file does not match HASHDICT_SCHEMA. securesystemslib.exceptions.Error: If 'filepath' does not exist. A dictionary conforming to securesystemslib.formats.HASHDICT_SCHEMA containing information about the hashes of the file at "filepath". """ # Making sure that the format of 'filepath' is a path string. # 'securesystemslib.exceptions.FormatError' is raised on incorrect format. formats.PATH_SCHEMA.check_match(filepath) formats.HASHALGORITHMS_SCHEMA.check_match(hash_algorithms) if storage_backend is None: storage_backend = FilesystemBackend() file_hashes = {} with storage_backend.get(filepath) as fileobj: # Obtaining hash of the file. for algorithm in hash_algorithms: digest_object = digest_fileobject(fileobj, algorithm) file_hashes.update({algorithm: digest_object.hexdigest()}) # Performing a format check to ensure 'file_hash' corresponds HASHDICT_SCHEMA. # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. formats.HASHDICT_SCHEMA.check_match(file_hashes) return file_hashes def get_file_length(filepath, storage_backend=None): """ To get file's length information. filepath: Absolute file path of a file. storage_backend: An object which implements securesystemslib.storage.StorageBackendInterface. When no object is passed a FilesystemBackend will be instantiated and used. securesystemslib.exceptions.Error: If 'filepath' does not exist. The length, in bytes, of the file at 'filepath'. """ # Making sure that the format of 'filepath' is a path string. # 'securesystemslib.exceptions.FormatError' is raised on incorrect format. formats.PATH_SCHEMA.check_match(filepath) if storage_backend is None: storage_backend = FilesystemBackend() return storage_backend.getsize(filepath) def persist_temp_file(temp_file, persist_path, storage_backend=None, should_close=True): """ Copies 'temp_file' (a file like object) to a newly created non-temp file at 'persist_path'. temp_file: File object to persist, typically a file object returned by one of the interfaces in the tempfile module of the standard library. persist_path: File path to create the persistent file in. storage_backend: An object which implements securesystemslib.storage.StorageBackendInterface. When no object is passed a FilesystemBackend will be instantiated and used. should_close: A boolean indicating whether the file should be closed after it has been persisted. Default is True, the file is closed. None. None. """ if storage_backend is None: storage_backend = FilesystemBackend() storage_backend.put(temp_file, persist_path) if should_close: temp_file.close() def ensure_parent_dir(filename, storage_backend=None): """ To ensure existence of the parent directory of 'filename'. If the parent directory of 'name' does not exist, create it. Example: If 'filename' is '/a/b/c/d.txt', and only the directory '/a/b/' exists, then directory '/a/b/c/d/' will be created. filename: A path string. storage_backend: An object which implements securesystemslib.storage.StorageBackendInterface. When no object is passed a FilesystemBackend will be instantiated and used. securesystemslib.exceptions.FormatError: If 'filename' is improperly formatted. A directory is created whenever the parent directory of 'filename' does not exist. None. """ # Ensure 'filename' corresponds to 'PATH_SCHEMA'. # Raise 'securesystemslib.exceptions.FormatError' on a mismatch. formats.PATH_SCHEMA.check_match(filename) if storage_backend is None: storage_backend = FilesystemBackend() # Split 'filename' into head and tail, check if head exists. directory = os.path.split(filename)[0] # Check for cases where filename is without directory like 'file.txt' # and as a result directory is an empty string if directory: storage_backend.create_folder(directory) def file_in_confined_directories(filepath, confined_directories): """ Check if the directory containing 'filepath' is in the list/tuple of 'confined_directories'. filepath: A string representing the path of a file. The following example path strings are viewed as files and not directories: 'a/b/c', 'a/b/c.txt'. confined_directories: A list, or a tuple, of directory strings. securesystemslib.exceptions.FormatError: On incorrect format of the input. Boolean. True, if path is either the empty string or in 'confined_paths'; False, otherwise. """ # Do the arguments have the correct format? # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. formats.PATH_SCHEMA.check_match(filepath) formats.NAMES_SCHEMA.check_match(confined_directories) for confined_directory in confined_directories: # The empty string (arbitrarily chosen) signifies the client is confined # to all directories and subdirectories. No need to check 'filepath'. if confined_directory == '': return True # Normalized paths needed, to account for up-level references, etc. # callers have the option of setting the list of directories in # 'confined_directories'. filepath = os.path.normpath(filepath) confined_directory = os.path.normpath(confined_directory) # A caller may restrict himself to specific directories on the # remote repository. The list of paths in 'confined_path', not including # each path's subdirectories, are the only directories the client will # download targets from. if os.path.dirname(filepath) == confined_directory: return True return False _json_module = None def import_json(): """ Tries to import json module. We used to fall back to the simplejson module, but we have dropped support for that module. We are keeping this interface intact for backwards compatibility. None. ImportError: on failure to import the json module. None. json module """ global _json_module if _json_module is not None: return _json_module else: # TODO: Drop Python < 2.6 case handling try: module = __import__('json') # The 'json' module is available in Python > 2.6, and thus this exception # should not occur in all supported Python installations (> 2.6). except ImportError: #pragma: no cover raise ImportError('Could not import the json module') else: _json_module = module return module json = import_json() def load_json_string(data): """ Deserialize 'data' (JSON string) to a Python object. data: A JSON string. securesystemslib.exceptions.Error, if 'data' cannot be deserialized to a Python object. None. Deserialized object. For example, a dictionary. """ deserialized_object = None try: deserialized_object = json.loads(data) except TypeError: message = 'Invalid JSON string: ' + repr(data) raise exceptions.Error(message) except ValueError: message = 'Cannot deserialize to a Python object: ' + repr(data) raise exceptions.Error(message) else: return deserialized_object def load_json_file(filepath, storage_backend=None): """ Deserialize a JSON object from a file containing the object. filepath: Absolute path of JSON file. storage_backend: An object which implements securesystemslib.storage.StorageBackendInterface. When no object is passed a FilesystemBackend will be instantiated and used. securesystemslib.exceptions.FormatError: If 'filepath' is improperly formatted. securesystemslib.exceptions.Error: If 'filepath' cannot be deserialized to a Python object. IOError in case of runtime IO exceptions. None. Deserialized object. For example, a dictionary. """ # Making sure that the format of 'filepath' is a path string. # securesystemslib.exceptions.FormatError is raised on incorrect format. formats.PATH_SCHEMA.check_match(filepath) if storage_backend is None: storage_backend = FilesystemBackend() deserialized_object = None with storage_backend.get(filepath) as file_obj: raw_data = file_obj.read().decode('utf-8') try: deserialized_object = json.loads(raw_data) except (ValueError, TypeError): raise exceptions.Error('Cannot deserialize to a' ' Python object: ' + filepath) else: return deserialized_object def digests_are_equal(digest1, digest2): """ While protecting against timing attacks, compare the hexadecimal arguments and determine if they are equal. digest1: The first hexadecimal string value to compare. digest2: The second hexadecimal string value to compare. securesystemslib.exceptions.FormatError: If the arguments are improperly formatted. None. Return True if 'digest1' is equal to 'digest2', False otherwise. """ # Ensure the arguments have the appropriate number of objects and object # types, and that all dict keys are properly named. # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. formats.HEX_SCHEMA.check_match(digest1) formats.HEX_SCHEMA.check_match(digest2) if len(digest1) != len(digest2): return False are_equal = True for element in range(len(digest1)): if digest1[element] != digest2[element]: are_equal = False return are_equal ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.8779738 securesystemslib-0.20.0/securesystemslib.egg-info/0000755000076500000240000000000000000000000022374 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341428.0 securesystemslib-0.20.0/securesystemslib.egg-info/PKG-INFO0000644000076500000240000004130500000000000023474 0ustar00lukpstaff00000000000000Metadata-Version: 2.1 Name: securesystemslib Version: 0.20.0 Summary: A library that provides cryptographic and general-purpose routines for Secure Systems Lab projects at NYU Home-page: https://github.com/secure-systems-lab/securesystemslib Author: https://www.updateframework.com Author-email: theupdateframework@googlegroups.com License: MIT Project-URL: Source, https://github.com/secure-systems-lab/securesystemslib Project-URL: Issues, https://github.com/secure-systems-lab/securesystemslib/issues Description: Secure Systems Library ---------------------- .. image:: https://github.com/secure-systems-lab/securesystemslib/workflows/Run%20Securesystemslib%20tests/badge.svg :target: https://github.com/secure-systems-lab/securesystemslib/actions?query=workflow%3A%22Run+Securesystemslib+tests%22+branch%3Amaster .. image:: https://api.dependabot.com/badges/status?host=github&repo=secure-systems-lab/securesystemslib :target: https://api.dependabot.com/badges/status?host=github&repo=secure-systems-lab/securesystemslib A library that provides cryptographic and general-purpose functions for Secure Systems Lab projects at NYU. The routines are general enough to be usable by other projects. Overview ++++++++ securesystemslib supports public-key and general-purpose cryptography, such as `ECDSA `_, `Ed25519 `_, `RSA `_, SHA256, SHA512, etc. Most of the cryptographic operations are performed by the `cryptography `_ and `PyNaCl `_ libraries, but verification of Ed25519 signatures can be done in pure Python. The `cryptography` library is used to generate keys and signatures with the ECDSA and RSA algorithms, and perform general-purpose cryptography such as encrypting keys. The PyNaCl library is used to generate Ed25519 keys and signatures. PyNaCl is a Python binding to the Networking and Cryptography Library. For key storage, RSA keys may be stored in PEM or JSON format, and Ed25519 keys in JSON format. Generating, importing, and loading cryptographic key files can be done with functions available in securesystemslib. securesystemslib also provides an interface to the `GNU Privacy Guard (GPG) `_ command line tool, with functions to create RSA and DSA signatures using private keys in a local gpg keychain; to export the corresponding public keys in a *pythonic* format; and to verify the created signatures using the exported keys. The latter does not require the gpg command line tool to be installed, instead the `cryptography` library is used. Installation ++++++++++++ :: $ pip install securesystemslib The default installation only supports Ed25519 keys and signatures (in pure Python). Support for RSA, ECDSA, and E25519 via the `cryptography` and `PyNaCl` libraries is available by installing the `crypto` and `pynacl` extras: :: $ pip install securesystemslib[crypto] $ pip install securesystemslib[pynacl] Usage ++++++++++++ Create RSA Keys ~~~~~~~~~~~~~~~ Note: In the instructions below, lines that start with *>>>* denote commands that should be entered by the reader, *#* begins the start of a comment, and text without prepended symbols is the output of a command. :: >>> from securesystemslib.interface import * # The following function creates an RSA key pair, where the private key is # saved to "rsa_key1" and the public key to "rsa_key1.pub" (both saved to # the current working directory). A full directory path may be specified # instead of saving keys to the current working directory. If specified # directories do not exist, they will be created. >>> generate_and_write_rsa_keypair( password="password", filepath="rsa_key1", bits=2048) # If the key length is unspecified, it defaults to 3072 bits. A length of # less than 2048 bits raises an exception. A similar function is available # to supply a password on the prompt. If an empty password is entered, the # private key is saved unencrypted. >>> generate_and_write_rsa_keypair_with_prompt("rsa_key2") enter password to encrypt private key file '/path/to/rsa_key2' (leave empty if key should not be encrypted): Confirm: The following four key files should now exist: 1. rsa_key1 2. rsa_key1.pub 3. rsa_key2 4. rsa_key2.pub Import RSA Keys ~~~~~~~~~~~~~~~ :: # Continuing from the previous section . . . # Import an existing public key. >>> public_rsa_key1 = import_rsa_publickey_from_file("rsa_key1.pub") # Import an existing private key. If your private key is encrypted, # which it should be, you either have to pass a 'password' or enter one # on the prompt. >>> private_rsa_key1 = import_rsa_privatekey_from_file("rsa_key1", password="some passphrase") # OR: >>> private_rsa_key1 = import_rsa_privatekey_from_file("rsa_key1", prompt=True) enter password to decrypt private key file '/path/to/rsa_key1' (leave empty if key not encrypted): **import_rsa_privatekey_from_file()** raises a *securesystemslib.exceptions.CryptoError* exception if the key / password is invalid: :: securesystemslib.exceptions.CryptoError: RSA (public, private) tuple cannot be generated from the encrypted PEM string: Bad decrypt. Incorrect password? Note: The specific message provided by the exception might differ depending on which cryptography library is used. Create and Import Ed25519 Keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # Continuing from the previous section . . . # The same generation and import functions as for rsa keys exist for ed25519 >>> generate_and_write_ed25519_keypair_with_prompt('ed25519_key') enter password to encrypt private key file '/path/to/ed25519_key' (leave empty if key should not be encrypted): Confirm: # Import the Ed25519 public key just created . . . >>> public_ed25519_key = import_ed25519_publickey_from_file('ed25519_key.pub') # and its corresponding private key. >>> private_ed25519_key = import_ed25519_privatekey_from_file('ed25519_key', prompt=True) enter password to decrypt private key file '/path/to/ed25519_key' (leave empty if key should not be encrypted): Create and Import ECDSA Keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # The same generation and import functions as for rsa and ed25519 keys # exist for ecdsa >>> generate_and_write_ecdsa_keypair_with_prompt('ecdsa_key') enter password to decrypt private key file '/path/to/ecdsa_key' (leave empty if key should not be encrypted): >>> public_ecdsa_key = import_ecdsa_publickey_from_file('ecdsa_key.pub') >>> private_ecdsa_key = import_ecdsa_privatekey_from_file('ecdsa_key', prompt=True) enter password to decrypt private key file '/path/to/ecdsa_key' (leave empty if key should not be encrypted): Generate ECDSA, Ed25519, and RSA Signatures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note: Users may also access the crypto functions directly to perform cryptographic operations. :: >>> from securesystemslib.keys import * >>> data = b'The quick brown fox jumps over the lazy dog' >>> ed25519_key = generate_ed25519_key() >>> signature = create_signature(ed25519_key, data) >>> rsa_key = generate_rsa_key(2048) >>> signature = create_signature(rsa_key, data) >>> ecdsa_key = generate_ecdsa_key() >>> signature = create_signature(ecdsa_key, data) Verify ECDSA, Ed25519, and RSA Signatures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # Continuing from the previous sections . . . >>> data = b'The quick brown fox jumps over the lazy dog' >>> ed25519_key = generate_ed25519_key() >>> signature = create_signature(ed25519_key, data) >>> verify_signature(ed25519_key, signature, data) True >>> verify_signature(ed25519_key, signature, 'bad_data') False >>> rsa_key = generate_rsa_key() >>> signature = create_signature(rsa_key, data) >>> verify_signature(rsa_key, signature, data) True >>> ecdsa_key = generate_ecdsa_key() >>> signature = create_signature(ecdsa_key, data) >>> verify_signature(ecdsa_key, signature, data) True Miscellaneous functions ~~~~~~~~~~~~~~~~~~~~~~~ **create_rsa_encrypted_pem()** :: # Continuing from the previous sections . . . >>> rsa_key = generate_rsa_key() >>> private = rsa_key['keyval']['private'] >>> passphrase = 'secret' >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) **import_rsakey_from_public_pem()** :: >>> rsa_key = generate_rsa_key() >>> public = rsa_key['keyval']['public'] >>> rsa_key2 = import_rsakey_from_public_pem(public) **import_rsakey_from_pem()** :: >>> rsa_key = generate_rsa_key() >>> public = rsa_key['keyval']['public'] >>> private = rsa_key['keyval']['private'] >>> rsa_key2 = import_rsakey_from_pem(public) >>> rsa_key3 = import_rsakey_from_pem(private) **extract_pem()** :: >>> rsa_key = generate_rsa_key() >>> private_pem = extract_pem(rsakey['keyval']['private'], private_pem=True) >>> public_pem = extract_pem(rsakey['keyval']['public'], private_pem=False) **encrypt_key()** :: >>> ed25519_key = generate_ed25519_key() >>> password = 'secret' >>> encrypted_key = encrypt_key(ed25519_key, password) **decrypt_key()** :: >>> ed25519_key = generate_ed25519_key() >>> password = 'secret' >>> encrypted_key = encrypt_key(ed25519_key, password) >>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), password) >>> decrypted_key == ed25519_key True **create_rsa_encrypted_pem()** :: >>> rsa_key = generate_rsa_key() >>> private = rsa_key['keyval']['private'] >>> passphrase = 'secret' >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) **is_pem_public()** :: >>> rsa_key = generate_rsa_key() >>> public = rsa_key['keyval']['public'] >>> private = rsa_key['keyval']['private'] >>> is_pem_public(public) True >>> is_pem_public(private) False **is_pem_private()** :: >>> rsa_key = generate_rsa_key() >>> private = rsa_key['keyval']['private'] >>> public = rsa_key['keyval']['public'] >>> is_pem_private(private) True >>> is_pem_private(public) False **import_ecdsakey_from_private_pem()** :: >>> ecdsa_key = generate_ecdsa_key() >>> private_pem = ecdsa_key['keyval']['private'] >>> ecdsa_key2 = import_ecdsakey_from_private_pem(private_pem) **import_ecdsakey_from_public_pem()** :: >>> ecdsa_key = generate_ecdsa_key() >>> public = ecdsa_key['keyval']['public'] >>> ecdsa_key2 = import_ecdsakey_from_public_pem(public) **import_ecdsakey_from_pem()** :: >>> ecdsa_key = generate_ecdsa_key() >>> private_pem = ecdsa_key['keyval']['private'] >>> ecdsa_key2 = import_ecdsakey_from_pem(private_pem) >>> public_pem = ecdsa_key['keyval']['public'] >>> ecdsa_key2 = import_ecdsakey_from_pem(public_pem) GnuPG interface ~~~~~~~~~~~~~~~ Signature creation and public key export requires installation of the `gpg` or `gpg2` command line tool, which may be downloaded from `https://gnupg.org/download `_. It is also needed to generate the supported RSA or DSA signing keys (see `gpg` man pages for detailed instructions). Sample keys are available in a test keyring at `tests/gpg_keyrings/rsa`, which may be passed to the signing and export functions using the `homedir` argument (if not passed the default keyring is used). The GPG client to use can be also specified with the help of environment variable `GNUPG`. :: >>> import securesystemslib.gpg.functions as gpg >>> data = b"The quick brown fox jumps over the lazy dog" >>> signing_key_id = "8465A1E2E0FB2B40ADB2478E18FB3F537E0C8A17" >>> keyring = "tests/gpg_keyrings/rsa" >>> signature = gpg.create_signature(data, signing_key_id, homedir=keyring) >>> public_key = gpg.export_pubkey(non_default_signing_key, homedir=keyring) >>> gpg.verify_signature(signature, public_key, data) True Testing ++++++++++++ Testing is done with `tox `_, which can be installed with pip: :: $ pip install tox Secure Systems Library supports multiple versions of Python. For that reason, the project is tested against multiple virtual environments with different Python versions. If you run :: $ tox this will run all tests creating virtual environments for all python versions described in the *tox.ini* file. If you want to run the tests against specific python version, for example Python 3.7, you will use: :: $ tox -e py37 Keywords: cryptography,keys,signatures,rsa,ed25519,ecdsa Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Security Classifier: Topic :: Software Development Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4 Description-Content-Type: text/x-rst Provides-Extra: colors Provides-Extra: crypto Provides-Extra: pynacl ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341428.0 securesystemslib-0.20.0/securesystemslib.egg-info/SOURCES.txt0000644000076500000240000000570400000000000024266 0ustar00lukpstaff00000000000000LICENSE MANIFEST.in README.rst requirements-dev.txt requirements-min.txt requirements-pinned.txt requirements-test.txt requirements.txt setup.py tox.ini securesystemslib/__init__.py securesystemslib/ecdsa_keys.py securesystemslib/ed25519_keys.py securesystemslib/exceptions.py securesystemslib/formats.py securesystemslib/hash.py securesystemslib/interface.py securesystemslib/keys.py securesystemslib/process.py securesystemslib/rsa_keys.py securesystemslib/schema.py securesystemslib/settings.py securesystemslib/signer.py securesystemslib/storage.py securesystemslib/unittest_toolbox.py securesystemslib/util.py securesystemslib.egg-info/PKG-INFO securesystemslib.egg-info/SOURCES.txt securesystemslib.egg-info/dependency_links.txt securesystemslib.egg-info/requires.txt securesystemslib.egg-info/top_level.txt securesystemslib/_vendor/__init__.py securesystemslib/_vendor/ssl_match_hostname.py securesystemslib/_vendor/ed25519/__init__.py securesystemslib/_vendor/ed25519/ed25519.py securesystemslib/_vendor/ed25519/science.py securesystemslib/_vendor/ed25519/test_ed25519.py securesystemslib/gpg/__init__.py securesystemslib/gpg/common.py securesystemslib/gpg/constants.py securesystemslib/gpg/dsa.py securesystemslib/gpg/eddsa.py securesystemslib/gpg/exceptions.py securesystemslib/gpg/functions.py securesystemslib/gpg/handlers.py securesystemslib/gpg/rsa.py securesystemslib/gpg/util.py tests/__init__.py tests/aggregate_tests.py tests/check_public_interfaces.py tests/check_public_interfaces_gpg.py tests/test_ecdsa_keys.py tests/test_ed25519_keys.py tests/test_exceptions.py tests/test_formats.py tests/test_gpg.py tests/test_hash.py tests/test_interface.py tests/test_keys.py tests/test_process.py tests/test_rsa_keys.py tests/test_schema.py tests/test_signer.py tests/test_storage.py tests/test_util.py tests/data/keystore/ecdsa_key tests/data/keystore/ecdsa_key.pub tests/data/keystore/ed25519_key tests/data/keystore/ed25519_key.pub tests/data/keystore/no_key tests/data/keystore/rsa_key tests/data/keystore/rsa_key.pub tests/gpg_keyrings/dsa/C242A830DAAF1C2BEF604A9EF033A3A3E267B3B1.pem tests/gpg_keyrings/dsa/C242A830DAAF1C2BEF604A9EF033A3A3E267B3B1.ssh tests/gpg_keyrings/dsa/pubring.gpg tests/gpg_keyrings/dsa/random_seed tests/gpg_keyrings/dsa/secring.gpg tests/gpg_keyrings/dsa/trustdb.gpg tests/gpg_keyrings/eddsa/pubring.gpg tests/gpg_keyrings/eddsa/pubring.kbx tests/gpg_keyrings/eddsa/pubring.kbx~ tests/gpg_keyrings/eddsa/short.sig tests/gpg_keyrings/eddsa/trustdb.gpg tests/gpg_keyrings/eddsa/openpgp-revocs.d/4E630F84838BF6F7447B830B22692F5FEA9E2DD2.rev tests/gpg_keyrings/eddsa/private-keys-v1.d/672E9A973B33784B4579F316820434E5631C086A.key tests/gpg_keyrings/rsa/7B3ABB26B97B655AB9296BD15B0BD02E1C768C43.ssh tests/gpg_keyrings/rsa/8288EF560ED3795F9DF2C0DB56193089B285DA58.ssh tests/gpg_keyrings/rsa/8465A1E2E0FB2B40ADB2478E18FB3F537E0C8A17.ssh tests/gpg_keyrings/rsa/pubring.gpg tests/gpg_keyrings/rsa/random_seed tests/gpg_keyrings/rsa/secring.gpg tests/gpg_keyrings/rsa/trustdb.gpg././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341428.0 securesystemslib-0.20.0/securesystemslib.egg-info/dependency_links.txt0000644000076500000240000000000100000000000026442 0ustar00lukpstaff00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341428.0 securesystemslib-0.20.0/securesystemslib.egg-info/requires.txt0000644000076500000240000000031700000000000024775 0ustar00lukpstaff00000000000000six>=1.11.0 [:python_version < "3"] subprocess32 [colors] colorama>=0.3.9 [crypto:python_version < "3"] cryptography<3.4,>=3.3.2 [crypto:python_version >= "3"] cryptography>=3.3.2 [pynacl] pynacl>1.2.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341428.0 securesystemslib-0.20.0/securesystemslib.egg-info/top_level.txt0000644000076500000240000000002100000000000025117 0ustar00lukpstaff00000000000000securesystemslib ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.9414253 securesystemslib-0.20.0/setup.cfg0000644000076500000240000000004600000000000017116 0ustar00lukpstaff00000000000000[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/setup.py0000644000076500000240000000726100000000000017015 0ustar00lukpstaff00000000000000#! /usr/bin/env python """ setup.py Vladimir Diaz December 7, 2016. See LICENSE for licensing information. BUILD SOURCE DISTRIBUTION The following shell command generates a securesystemslib source archive that can be distributed to other users. The packaged source is saved to the 'dist' folder in the current directory. $ python setup.py sdist INSTALLATION OPTIONS pip - installing and managing Python packages (recommended): # Installing from Python Package Index (https://pypi.python.org/pypi). $ pip install securesystemslib # Installing from local source archive. $ pip install # Or from the root directory of the unpacked archive. $ pip install . Alternate installation options: Navigate to the root directory of the unpacked archive and run one of the following shell commands: Install to the global site-packages directory. $ python setup.py install Install to the user site-packages directory. $ python setup.py install --user Install to a chosen directory. $ python setup.py install --home= Note: The last two installation options may require modification of Python's search path (i.e., 'sys.path') or updating an OS environment variable. For example, installing to the user site-packages directory might result in the installation of scripts to '~/.local/bin'. The user may then be required to update his $PATH variable: $ export PATH=$PATH:~/.local/bin """ from setuptools import setup from setuptools import find_packages with open('README.rst') as file_object: long_description = file_object.read() setup( name = 'securesystemslib', version = '0.20.0', description = 'A library that provides cryptographic and general-purpose' ' routines for Secure Systems Lab projects at NYU', license = 'MIT', long_description = long_description, long_description_content_type = 'text/x-rst', author = 'https://www.updateframework.com', author_email = 'theupdateframework@googlegroups.com', url = 'https://github.com/secure-systems-lab/securesystemslib', keywords = 'cryptography, keys, signatures, rsa, ed25519, ecdsa', classifiers = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Operating System :: POSIX', 'Operating System :: POSIX :: Linux', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Security', 'Topic :: Software Development' ], project_urls = { 'Source': 'https://github.com/secure-systems-lab/securesystemslib', 'Issues': 'https://github.com/secure-systems-lab/securesystemslib/issues', }, python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4", install_requires = ['six>=1.11.0', 'subprocess32; python_version < "3"'], extras_require = { 'colors': ['colorama>=0.3.9'], 'crypto:python_version < "3"': ['cryptography>=3.3.2,<3.4'], 'crypto:python_version >= "3"': ['cryptography>=3.3.2'], 'pynacl': ['pynacl>1.2.0']}, tests_require = 'mock; python_version < "3.3"', packages = find_packages(exclude=['tests', 'debian']), scripts = [] ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.9138136 securesystemslib-0.20.0/tests/0000755000076500000240000000000000000000000016437 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/__init__.py0000755000076500000240000000000100000000000020542 0ustar00lukpstaff00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/aggregate_tests.py0000755000076500000240000000206100000000000022163 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ aggregate_tests.py Konstantin Andrianov. Zane Fisher. January 26, 2013. August 2013. Modified previous behavior that explicitly imported individual unit tests. -Zane Fisher See LICENSE for licensing information. Run all the unit tests from every .py file beginning with "test_" in 'securesystemslib/tests'. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import sys import unittest if __name__ == '__main__': suite = unittest.TestLoader().discover("tests", top_level_dir=".") all_tests_passed = unittest.TextTestRunner( verbosity=1, buffer=True).run(suite).wasSuccessful() if not all_tests_passed: sys.exit(1) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/check_public_interfaces.py0000644000076500000240000003021300000000000023626 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ check_public_interfaces.py Joshua Lock January 6, 2020. See LICENSE for licensing information. Public facing modules (e.g. interface.py and keys.py) must be importable, even if the optional dependencies are not installed. Each public facing function should always be callable and present meaningful user-feedback if an optional dependency that is required for that function is not installed. This test purposefully only checks the public functions with a native dependency, to avoid duplicated tests. NOTE: the filename is purposefully check_ rather than test_ so that test discovery doesn't find this unittest and the tests within are only run when explicitly invoked. """ from __future__ import print_function from __future__ import absolute_import import inspect import json import os import shutil import sys import tempfile import unittest if sys.version_info >= (3, 3): import unittest.mock as mock else: import mock import securesystemslib.exceptions import securesystemslib.gpg.constants import securesystemslib.gpg.functions import securesystemslib.gpg.util import securesystemslib.interface import securesystemslib.keys class TestPublicInterfaces(unittest.TestCase): @classmethod def setUpClass(cls): cls.temp_dir = tempfile.mkdtemp(dir=os.getcwd()) @classmethod def tearDownClass(cls): shutil.rmtree(cls.temp_dir) def test_interface(self): with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.interface._generate_and_write_rsa_keypair(password='pw') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.interface.generate_and_write_rsa_keypair('pw') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.interface.generate_and_write_rsa_keypair('pw') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): # Mock entry on prompt which is presented before lower-level functions # raise UnsupportedLibraryError with mock.patch("securesystemslib.interface.get_password", return_value=""): securesystemslib.interface.generate_and_write_rsa_keypair_with_prompt() with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.interface.generate_and_write_unencrypted_rsa_keypair() with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): path = os.path.join(self.temp_dir, 'rsa_key') with open(path, 'a'): securesystemslib.interface.import_rsa_privatekey_from_file( path) with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.interface._generate_and_write_ed25519_keypair( password='pw') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.interface.generate_and_write_ed25519_keypair('pw') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): # Mock entry on prompt which is presented before lower-level functions # raise UnsupportedLibraryError with mock.patch("securesystemslib.interface.get_password", return_value=""): securesystemslib.interface.generate_and_write_ed25519_keypair_with_prompt() with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.interface.generate_and_write_unencrypted_ed25519_keypair() with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): path = os.path.join(self.temp_dir, 'ed25519_priv.json') with open(path, 'a') as f: f.write('{}') securesystemslib.interface.import_ed25519_privatekey_from_file( path, 'pw') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.interface._generate_and_write_ecdsa_keypair( password='pw') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.interface.generate_and_write_ecdsa_keypair('pw') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): # Mock entry on prompt which is presented before lower-level functions # raise UnsupportedLibraryError with mock.patch("securesystemslib.interface.get_password", return_value=""): securesystemslib.interface.generate_and_write_ecdsa_keypair_with_prompt() with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.interface.generate_and_write_unencrypted_ecdsa_keypair() with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): path = os.path.join(self.temp_dir, 'ecddsa.priv') with open(path, 'a') as f: f.write('{}') securesystemslib.interface.import_ecdsa_privatekey_from_file( path, password='pw') def test_keys(self): with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.generate_rsa_key() with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.generate_ecdsa_key() with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.generate_ed25519_key() data = 'foo' keydict = {'keytype': 'ed25519', 'scheme': 'ed25519', 'keyid': 'f00', 'keyval': {'private': 'f001', 'public': 'b00f'}} with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.create_signature(keydict, data) keydict['keytype'] = 'ecdsa' keydict['scheme'] = 'ecdsa-sha2-nistp256' with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.create_signature(keydict, data) keydict['keytype'] = 'rsa' keydict['scheme'] = 'rsassa-pss-sha256' with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.create_signature(keydict, data) keydict['keytype'] = 'ecdsa' keydict['scheme'] = 'ecdsa-sha2-nistp256' sig = {'keyid': 'f00', 'sig': 'cfbce8e23eef478975a4339036de2335002d57c7b1632dd01e526a3bc52a5b261508ad50b9e25f1b819d61017e7347e912db1af019bf47ee298cc58bbdef9703'} # NOTE: we don't test ed25519 keys as they can be verified in pure python with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.verify_signature(keydict, sig, data) keydict['keytype'] = 'rsa' keydict['scheme'] = 'rsassa-pss-sha256' with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.verify_signature(keydict, sig, data) priv = '-----BEGIN RSA PRIVATE KEY-----\nMIIG5AIBAAKCAYEA2WC/pM+6/NbOE/b+N9L+5BOa5sLHCF88okpiCJAZhtIEMw8O\n/EX4CjSy5Qilrmj7ZXmwRyPf7ksd6dbgxAJYk555lE2dywdvzsd31B+nKuAky8/K\nNjpfH4bn2sBKxbA9FFrBenpBkBrq0qDyK85VGJO7ieUdjQepiBQbqctU/PxmPJcE\neO0f1X4IjA+MQv6j/Wt+dnCQSFpCHgOEA0CBWByfRR+DIX74y8RYyKHgj+LpNv1A\nUD1K2vbNc/LrZWEIojCz+2QcXtz/g0kXX5DmRP3feGMC/S/r9bIjEdP55XP70LQU\ndaly64Y/nOlwWHhDNRjtu0lfdqxrK30/O8S8NC6A+nXrav1DzOufffd6wuRKiEqc\nEXZGitSyt/Bg5z70jIHgP6sZ69F0uORr3CaX/YAcQdjPzvSkJEvSj1/sSa+iKOPe\nixQx3VoEpdI3wWu7TQBmTOA3gi2XEZFYdThMGUA5Yv/qNHQVHBkEvOdtTRbWFX0m\npBHLTwBoMO+VJI6hAgMBAAECggGATAC5wOQomrJ4Bx76r4YEPLZmGHzNni2+Q3gC\nYsAPTMYtVbTUJnxIRzk5uz6UvzBRhZ9QdO8kImr9IH9SwvWXBrYICERDAXOuMfwn\n93DBwAnyk5gpOWCbVaiTdDZ7bjc6g91ffHU2ay4eIFrJkWto8Vjl30bOWDrvmXZ+\nXZWMN5AAJvseQzGVSc3xKxdckSf7KmXlJ4Af0kxMhbXw+DobfzUysrZb4OBGGOij\nqjJ/E4/gvqs5S1TC0WAtYXbzutR7zVGuZUFVK7Lk1fq8XcJP5wXCrIjxGnP6V97y\nWn1h64eD+7Gt4wQ+IGr0zKxhSYWI4ou+6QIV3kGlFv9ZRI22yym9MalG1Z1g2GP4\nrgcBZ6j87siSG2L5WoA62pxPPm+vfgEW3GYty1sYqVVQEQhy7GGHWT1kYcc0H7Sr\nALspSr3VbDJtylMQ+wl2IHs8qQ2GAW/utHwPyPzgY2wswi/6L8oYKBrEKK66gSlF\nPHek3uSbho2cPVW7RpG3NA5AHJBhAoHBAO48GEnmacBvMwHfhHex6XUX+VW0QxMl\n/8uNbAp4MEgdyqLw1TLUUAvEbV6qOwL3IWxAvJjXl/9zPtiBUiniZfUI7Rm0LMlv\n1jUlXfzuLwZtL8dHUDFBaZNWlY+eG5dniWkhzMnKqYYGbs9DDO741AKWUtM9UtBA\nm6g0AP6maa3RRAFQ+JtoVFuMYg6R4oE621pKI5ZJ1Zmz/L6H1xoj1QH0JPND1Mxa\nqYEj5SAKE+tj4dbsHjKeaPjk30qnlulQPQKBwQDpln8mJ3z7EXGCYMQrbtg94YuR\n/AVM5pZL9V1YNB8jiydg3j5tMjXWSxd+Hc3Kg1Ey0SjWGtPGD1RQQM+ZQgubRFHP\n7RwQwhxwxji5Azl5LoupsNueMGLQ0bBxSQWTx8zxc4z5oVBcZgD4Pm+5wi17L/77\nqM9Md2nw4ONbsxMiNol65dc/XUPuxaUpPAe2XlV4EGsyWDee6OhH288WhOAzpixS\nB1Ywc6f7LNLc065w2rjzogzyONAFkTP4kKe/2jUCgcEAxznuPe64RTs49roLN2XL\nDCcOVgO3jA3dCkasMV0tU0HGsdihEi7G+fA8XkwRqXstsi+5CEBTVkb0KW6MXYZ9\nKRtb3ID2a0ZhZnRnUxuEq+UnbYlPoMFJHvPrgvz/qe/l08t2TNJ0TiaXCDDUYgwo\nkDlR7mF8HbfJ9DH5GvvjqH42Vrt2C9CFq0GMxw5s0xF7WthhRk9cl3sTQ+qpkayh\nd07Kj70L+hFfayWveMm0usb+mBNBdadPtcUAjpfz9g0pAoHBALWdULDOpQrkThfr\nurp2TWUXlxfjFg/rfNIELRZmOAu/ptdXFLx7/IXoDpT9AUNChIB5RUHqy9tDke9v\n5LkpM7L+FIoQtfCFq+03AWVAD5Cb0vUV0DuXLU1kq8X424BCKaNVjzeL59pfaMOa\nb+3C/u+3qo3qe3rdoZ4qjDuA6RCBzLSkPY5DqozcWQTNasWtZNCcG2yiUGSae/da\n/RFqMJOX0P/aOnYjhmjxOeV+JDQUqxaqWVx/NaYOdpT9i5/MPQKBwGaMbFVt0+CR\nRT5Ts/ZS1qCmyoIepFMOI0SyU8h5+qk4dGutXCm1zjyyxwdJAjG1PYny5imsc795\nR7g7PLSUA+pkXWU8aoiCuCkY6IYz8JFLAw74mxZdLaFQUfBBtSqMz4B9YvUOysr1\nj7Og3AYXob4Me1+ueq59YLM9fEd4Tbw+aBg5T27jwZEmmNripamNFFb6RuPq6u6H\nMZW81M7ahgizqGQsRcOskA/uBC1w3N7o/lUYa3I+OY6EqA4KigIuGw==\n-----END RSA PRIVATE KEY-----\n' with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.import_rsakey_from_private_pem('') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.encrypt_key(keydict, 'foo') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.decrypt_key('enc', 'pw') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.create_rsa_encrypted_pem(priv, 'pw') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.import_ed25519key_from_private_json( ''.encode('utf-8'), '') with self.assertRaises( securesystemslib.exceptions.UnsupportedLibraryError): securesystemslib.keys.import_ecdsakey_from_private_pem(priv) def test_purepy_ed25519(self): data = b'The quick brown fox jumps over the lazy dog' pub = b'\xbe\xb7\\&\x82\x06UN\x96 check_public_interfaces_gpg.py Lukas Puehringer Feb 26, 2020. See LICENSE for licensing information. Check that the public facing 'gpg.functions' module remains importable if gnupg is not installed, and that each function presents meaningful user-feedback. Further check that gpg signature verification works even without gpg. NOTE: the filename is purposefully check_ rather than test_ so that test discovery doesn't find this test module and the test cases within are only run when explicitly invoked. """ import unittest from securesystemslib.gpg.constants import HAVE_GPG, NO_GPG_MSG from securesystemslib.gpg.util import get_version from securesystemslib.gpg.functions import ( create_signature, export_pubkey, export_pubkeys, verify_signature) from securesystemslib.exceptions import UnsupportedLibraryError class TestPublicInterfacesGPG(unittest.TestCase): @classmethod def setUpClass(cls): assert not HAVE_GPG, \ "please remove GnuPG from your environment to run this test case" def test_gpg_functions(self): """Signing, key export and util functions must raise on missing gpg. """ with self.assertRaises(UnsupportedLibraryError) as ctx: create_signature('bar') self.assertEqual(NO_GPG_MSG, str(ctx.exception)) with self.assertRaises(UnsupportedLibraryError) as ctx: export_pubkey('f00') self.assertEqual(NO_GPG_MSG, str(ctx.exception)) with self.assertRaises(UnsupportedLibraryError) as ctx: export_pubkeys(['f00']) self.assertEqual(NO_GPG_MSG, str(ctx.exception)) with self.assertRaises(UnsupportedLibraryError) as ctx: get_version() self.assertEqual(NO_GPG_MSG, str(ctx.exception)) def test_gpg_verify(self): """Signature verification does not require gpg to be installed on the host. To prove it, we run basic verification tests for rsa, dsa and eddsa with pre-generated/exported signatures and keys. More thorough testing is available in test_gpg.py """ data = b"deadbeef" key_signature_pairs = [ # RSA ({'method': 'pgp+rsa-pkcsv1.5', 'type': 'rsa', 'hashes': ['pgp+SHA2'], 'creation_time': 1519661780, 'keyid': 'c5a0abe6ec19d0d65f85e2c39be9df5131d924e9', 'keyval': {'private': '', 'public': {'e': '010001', 'n': 'c152fc1f1535a6d3c1e8c0dece7f0a1d09324466e10e4ea51d5d7223ab125c1743393eebca73ccb1022d44c379fae30ef63b263d0a793882a7332ef06f28a4b9ae777f5d2d8d289167e86c162df1b9a9e127acb26803688556ecb08492d071f06caf88cea95571354349d8ef131eff03b0d259fae30ebf8dac9ab5acd6f26f4770fe2f30fcd0a3c54f03463a3094aa6524e39027a625108f04e12475da248fb3b536df61b0f6e2954739b8828c61171f66f8e176823e1c887e65fa0aec081013b2a50ed60515f7e3b3291ca443e1222b9b625005dba045a7208188fb88d436d473f6340348953e891354c7a5734bf64e6274e196db3074a7ce3607960baacb1b'}}}, {'keyid': 'c5a0abe6ec19d0d65f85e2c39be9df5131d924e9', 'other_headers': '04000108001d162104c5a0abe6ec19d0d65f85e2c39be9df5131d924e905025e56444b', 'signature': 'bc4490901bd6edfe0ec49e0358c0a7ef37fc229824ca75dd4f163205745c78baaa2ca5cda79be259a5ac8323b4c1a1ee18fab0a8cc90eeafeb3eb1221d4bafb55510f34cf99e7ac121874f3c01152d6d8953c661c3e5147a387fffaee672318ed39c49fa02c80fa806956695f2fdfe0429a61639e7fb544f1531100eb02b7a140ffa284746fa1620e8461e4af5f93594f8aed6d34a33d51b265bae90ea8bedccb7497594003eb46516bddb1778a4fadd02cbb227e1931eeb5ef445fb9745f85cfbebfa169c3ae7d15e2ca75b15dd020877c9a968ff853993a06420d3c3ff158800014f21e558103cd4e7e84cf5e320ebf7c525e0eab9ab22ad4af02c7ad48b5e'}), # DSA ({'method': 'pgp+dsa-fips-180-2', 'type': 'dsa', 'hashes': ['pgp+SHA2'], 'creation_time': 1510870182, 'keyid': 'c242a830daaf1c2bef604a9ef033a3a3e267b3b1', 'keyval': {'private': '', 'public': {'y': '2dd50b2292441444581f9a0b7d8d7f88b573fc451f5e7207c324694232c22e171b508f6842ae9babc56fe4e586a22086188b4827b7aba8c7bff4a4ac9aa80c835420b1afba4ab4f1b1c0ef894437903a9f4c56ebef037804a99925c9a153b8a16c1562f297755aeaa20fa02ab32aa5366e052b6baa9a934356d4f5fc218785018dd12b2c8e6d605d2afb36cb06a9cced9ea1f5f82798d635de264ef0eb59590c4a4b2fdf2369a36f95614804c7aa5966ba9597404ba2d2c6881959112de52de4b6d4f1e2c8a59ddaadb08a59ac8334118f15aa01593e851024905ea6d884c3a545af6fdd03c8d2b54da1d35e710ef75a2b4775bb78c50b28d1e2fb48416dc941', 'p': 'fca3276cd78c20e3c73ae2398674046039f5d90f41e3ede9bc99f94000d145693522671fba481d22e0a9b31e695d198da5e62f4ffb4db5dc64076d0f2d7d03ce953fc7846a6d4e17a10bf1dcd17167f7aff761b59fa2180e7fcd2ca527c03c50c78665b5539bf2b45648b6d23f31f37999e6a7b4e0876ddad7ec783b8eec7e1fb14733e74b6b0b105cbdc5a7de8e094657f2146ce43a3177581cb022a4e2ce6678a3364a56e02090559a6dfd81d91ca3b7c6afd4fcfc66fd88339d217062462f51c5c91d6eccfafb32065be68e6b91ec837c59a51baebeca1c70fd3891c9bbb67f7d920f9153fc4d2ca03f88a27b70df1684709f99ad18707189b015441b2bfb', 'g': '7f7252ae1824baf2be5fc8f431a1978683a38d4a22cc2bcdc01ccd1f5eee47a964aa57639a618cfb1b10707b4d09ff11a448e83ba70123573f2d49a599f5313a74463e5bb3ca3d6172a00f02b01065ce312501e1797f7b57e606947c44bd839fde8d43269f1fb74af6cedf4db7fabf0b2357ed09d56381ac769ef5a8af1b4450e0c88b64ee1cab9fadeb31b7be6207b7e17008a33a7613831f70a123d59279dcbc2238f46eeaa8097795b7805f1b837ef3b8e807164e186fae9fa3ff510213096bf54040eac545a6a5b47c910e6cf7e306e1f46723f14b02cd9e0b0ff2a56c3b2604869431ab3263d61bf5068bee36c880c7bf2c746dcae5d0d7b2fff244ef43', 'q': '84779eeae0238d7a9a030a639bf01a0f9ef517a5d950599c19a4e54fbbf23219'}}}, {'keyid': 'c242a830daaf1c2bef604a9ef033a3a3e267b3b1', 'other_headers': '04001108001d162104c242a830daaf1c2bef604a9ef033a3a3e267b3b105025e5644d1', 'signature': '3044022009e95f952f64f559852fb6b321173f3cb142a5dbe0c84d709d55026ab945582802203144ee0f4c2cb70fa00ca6942c847208b96811271445ed85c75ebebdb609b174'}), # EDDSA ({'method': 'pgp+eddsa-ed25519', 'type': 'eddsa', 'hashes': ['pgp+SHA2'], 'creation_time': 1572269200, 'keyid': '4e630f84838bf6f7447b830b22692f5fea9e2dd2', 'keyval': {'private': '', 'public': {'q': '716e57b8c5d4397a4194f80bd43af2e07691db7ee58d2473ceb56cef1eda7569'}}}, {'keyid': '4e630f84838bf6f7447b830b22692f5fea9e2dd2', 'other_headers': '04001608001d1621044e630f84838bf6f7447b830b22692f5fea9e2dd205025e564505', 'signature': '70ba3fe785bccac105b837b6b27cc8d5ddd0159c3f640bbac026b744e0b10839bf4ea53e786074d32f9617389a4fe3356ec1c4a19045c5c02821563786e1d10d'}) ] for key, sig in key_signature_pairs: self.assertTrue(verify_signature(sig, key, data)) if __name__ == "__main__": unittest.main(verbosity=1, buffer=True) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.8501673 securesystemslib-0.20.0/tests/data/0000755000076500000240000000000000000000000017350 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.9183357 securesystemslib-0.20.0/tests/data/keystore/0000755000076500000240000000000000000000000021215 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/data/keystore/ecdsa_key0000644000076500000240000000260600000000000023073 0ustar00lukpstaff00000000000000b10c798b80a5362c57cf54bdbc6d37e3@@@@100000@@@@852bb8805566c96c13e0e89dd1c61702480656e818da1c9bc70514053aa37e95@@@@e68c557ba0bce8f5ad81f1273ff77e2a@@@@af16836050062e7ef32c868f0fee50ffe1ba7fddd58fb9fba807c4cfbf1d9d188786c86e0f110d4c26e11b99a8aae98f90091d6a2d8733f8aceab7719bdde92b9e761821283c4bc29af5803bd4822e95e024ec5f1d3de361bd109d40d3a4a88d28895a865903cf6dc7662c87e936fb7a9d18b92271183950197983d31e0ea6365c821238bd9910855e7f5ce6d053b591972028802bc865f9867efb9e50b0319a800190c4f7a820dafbe4c5aa0ff6243f6709137f52d4fceb62c66a9bd86ee649adbf93d50be5a9d7f7b4dc6dbfdec36482d00cc4552546a969e10b13085038c7f951d6b84856f73c56ddb13e1a41d84ff6add934d17809253f7e75abe6b774a658ea4f3ff6519e468acc5302478a3c2b44a6b5317d04f7f1e82c5eeeaaeaa2ef8cbb4b3bbef18a74105b7463f06651946b1da8582f6018f3c2dca1dfb04c04e8c4686ac168c378106ab82a777fd68d57b865014d6c2dd854375a70b6a6cb9ba098f79b44cfd593b99ee468615f6217d6df2d0eaccbb9b3d99cf7417d98930c2d1891eae9561ee08cca81077f5c198939d833eb0d98beff06cc217b7bb4b06af84d294c77a2e1082fc15292898aeaa8ab1676e2898a33234ab71e62a929b2135a8006ba362018e9c5dbea9c48e1758c0b59890124b3a1aa9dcf70483f4116631733cdd448e5c68b1e715e9d36f4b5970302700c5b882669ddc0d2c953d60cb568ba7eaced480fc51c4efbb5143ab4d9c76f9267e5d2bc71cd66c68a4e59d1322ef45403451f7dd49fc41c0c8bb34e997850e37f24f13f55f276490347131d8d11b6102b1fa2333fc7d5b0051f1c831f1d2cbd5a7d5b653832eebadc0f8c69e1f33ae36f98a199aa069b5c0a16e97d8de2dddc23dc54d3dad3././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/data/keystore/ecdsa_key.pub0000644000076500000240000000046400000000000023660 0ustar00lukpstaff00000000000000{"keytype": "ecdsa", "scheme": "ecdsa-sha2-nistp256", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsYJfSlYU3UlYbGOZfE/yOHkayWWq\nLPR/NeCa83szZmnJGc9wwCRPvJS87K+eDGIhhhKueTyrLqXQqmyHioQbOQ==\n-----END PUBLIC KEY-----\n"}}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/data/keystore/ed25519_key0000644000076500000240000000146600000000000023015 0ustar00lukpstaff00000000000000788b012147f9afe9c1a1512265eb63b0@@@@100000@@@@b9b5c42795411118e907cecdb46f9de8cfb22154664f65aa473df7542969ebfd@@@@1f50f6aa17bea38f7f3f3b94b35863c1@@@@bb498fa82340c0b2ab244e6032df2dfdeb7155fd614ec797ccc833b36f404d06177824b62b4242dc323e5e90ba9d009df0b62e9ad2a26ab1705687ed220065aa24af1cd5e531c9939ad8d6327cf8321fa28e271330d283c71d9c605b6c9a6579c63ecbd457f31fc5343088c8a30d1aebf5dcbffa4f5852b57f59e2683581fe426a55f23c61a432b435ec633111f7051bfbbe84994b7b85beaa2ed8cd515d3956ec8d241bc98cce855e7875c8d5b0ad80a42c1cf26038b82dfadf593753ed0c65418f1ddbfd754956599d20ab3463ccbf70eacf59902a551ab714d496fb9fb4fe6f23132b6461d25901ac8a92b1f440fec815322979d5082c463616a92a05481be27fd9bb0abd6ec0f10651675273e9e58360fb6062a367b82d909c9863515755cde651d803e9763bb0f6eee289e356e9bfb961ce62464559b1e4fa739786606381b40e1b286b8b7462f94dc32a281baa././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/data/keystore/ed25519_key.pub0000644000076500000240000000026400000000000023575 0ustar00lukpstaff00000000000000{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "50a5768a7a577483c28e57a6742b4d2170b9be628a961355ef127c45f2aefdc5"}}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/data/keystore/no_key0000644000076500000240000000000300000000000022415 0ustar00lukpstaff00000000000000{} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/data/keystore/rsa_key0000644000076500000240000000474200000000000022604 0ustar00lukpstaff00000000000000-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,6015A3049DBF1CD5 KYD7wtT4LYqb8GjyRru/4w7iX9AXzUEjMMCUNCtRsOppaqtk6W1JPEeVmoAPwVTv P19RUS6oF0Kp6R1SJGJOIBpNUzmvGdXzsWtjQJF9g5oNftrS8gpM25+lAViMy5W/ yxZ1nNsifG+fqRQ291gFrwYuLhHb5WYyOtVAf5FakEdNjMiD1F2Jgq7HdkA9Exxg dm/6yDmB7b1oJtP7qmJuQLn3UuzbZ/qBMA8WkFpgveyxbPCA+8AmoodlArXokByr iSXUAFTznPzUCNq/nPOuq8JjTGMEyfamD2czi40cRfG+Ix+Dq8HHRINeT51xY3uF +aY+2eTQaSb2WiJxPYw5leXQuIuIU0DwCan2yVUiMZhXv/VymqBj3126jJFJgCYH AZYpV6sHzO415NMceeIEfyJFyum0I59urAiL7xqv9w6aXSjtL/sb9vkwH0VpFgQt d6za1ol+teZJuHOvGADvcfkX0EdFsltb9/R0ALNVP4+NEOZxYV3Ilc0w4TioZsBo Dbim/5oZBH/ZcfSE2BURHpeoPCBKuEVIu7MjINbh7TNOWNybPZORpNiUrwwbEwxM aNLyfwJR7zx2sWuSknJIyDGTssUCpDV7IwZWMOFs3LgAM8/r45Im0IDU0zxidSwm UZFhSrIgsPSgnx2HbF6hmIQk5DnSqlRejhBVPd+oHMZwvcBm3NxfwEQ5xJ8UovZn QiF4ztgwXjI/RltVZR8fiqNdxrOS0KITZ8UC3DuM8yI45XhLH2aRyuXGxRmmMDZa 2J8Y/Rn1r0FTlWreV16hC9B8546Uxd131ifsJQJHozsJVbzjwRNIWgEuNwLK8t11 2C/Dpz/uS7qaC6jWiBxVCuux/t3pFhuz1sovfYbMdQDpSKXZnlEeulzgSV94rBSS yJvcv6XEliM+/V+A5o0FFIDcpUdAWe48ExexI7SoIKDaHYq/roVA5O513FL5HRqf Oy/i3DAtuadkp34A5f1vjFfDr5fw6FOB0BtwPNZRVgIxJYqMv2IDxVSq5MSSfTLf 7Ju1sUeXPLTd/r7HM8CURo0zYeaf+NNs9zqTm17IbRHiwPKBhsZExFdYvJx8Wa9I 6Z5qZsoeMSoKK2EjRCKVTbft4g8AgEaRsRv5e0v3+ns008vbx12os5RMDTncVO8A /zc+AX8Td8m2Vniz4PKR6lUEVnE972rcu5RVZb7nr1tufh6lxshCQ+guqBzaEg/i +ts+cGEQf7ZensqWnSffFMp1tKyxmYNuflU7BHQdZfiVx4ezIP1rVka5VNuRIZsv zPV6uu3OALgH4LUj3zJBD6nVX+E5qx2A/Be5nXRyTT6KDLWIFq0EaQN/kJUUT+v4 Qm+bntwLcCKsPLitH94e3e6IFHCrXobLEbfE5Y8yXdIB8mRI2IfI1IYOv1BtvF1T ukND/fN4gZY6fgZ4adUm+17VV0kjNOO3dkEJPgGgH2XsB4fwhj3NQRg/8RN4Xy0z BtNSGFiM3Z+Sp98fKmm8A0tjGmax1vFt5QygPjyFvor/I2SXMwgBqyTpvoQ8tTZp ZWw6fGprHx5qkpd2XHksG2o8lG70X8TnKQte9BUUfRE3c51vhILlPT0HWflmxMuT qivGLKSjrsinoCXd005UQPyLb22r31owpG70EuG4YM4XIgR3AeW3IlWeBL47o8pk JnSA6wxPb6Vfq3zbnCCk/1RGRZ/2Qch3xE2E5UNOsikbFAhNfRjtAUmiDdlhGEm3 BJMI10ntVNkNbX3bVkT084OIhgP7HDc9TnbTvSNYkZ0lF2fEaVRQebP0NKrWQa5b N+xRBbPQpqzb+hD15AVZt3D/jPCYsLHnpOMLIV/wnNnpzpgwEjqoYazld95J4xBk w3D4MyM/7GIKUNYH7VVr8l40PqNECxAV/gI0P1DuwRagGRsqjf2DknQADDVma1V5 lHxPgbfcGUPoxKLHxme6r/2qhhuLxF1GBKy0tfNmBupYYtRjKRXex76j0y1yvg/3 21i5Y63NtFpw/1rvgEeQ5WcProT4Xe9fr4v/mvxP9oYaATPs6VQzPFXzqkrMEel3 /EF2j2KsLF5HpiSlJmEfXU6zqYYAuk5p8fX7FZ2PCLaaxMZBR/2Qe12Ncjx15D0X yCa9x1vSZX0jZq0MciEpxs2vEKxqKwJpYHLM0+AmjMuhQQtTI40MOG8TVlBwSf2R RObEFSvAJqUz9x/Wxog48vNkOz4vB/ezjlOcWTh3upGlHowIwvKTGUCzjWZUNYOG cKfMoqmA5C6BFyt5EfPrTgx1JhHA7qYVN1/aVcXec4nkO5xIFBglJrFDxLfwXWu0 fvRsmA5XCPcnvqH7HfZotg2qUfteSHzaNOhT7xQHMEvDXB6rZDNuFSXC3n53JM9l -----END RSA PRIVATE KEY-----././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/data/keystore/rsa_key.pub0000644000076500000240000000116100000000000023361 0ustar00lukpstaff00000000000000-----BEGIN PUBLIC KEY----- MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsDqUoiFJZX+5gm5pyI1l Vc/N3yjJVOIl9GyiK0mRyzV3IzUQzhjq8nhk0eLfzXw2XwIAYOJC6dR/tGRG4JDx Jkez5FFH4zLosr/XzT7CG5zxJ3kKICLD1v9rZQr5ZgARQDOpkxzPz46rGnE0sHd7 MpnpPMScA1pMIzwM1RoPS4ntZipI1cl9M7HMQ6mkBp8/DNKCqaDWixJqaGgWrhhK hI/1mzBliMKriNxPKSCGVlOk/QpZft+y1fs42s0DMd5BOFBo+ZcoXLYRncg9S3A2 xx/jT69Bt3ceiAZqnp7f6M+ZzoUifSelaoL7QIYg/GkEl+0oxTD0yRphGiCKwn9c pSbn7NgnbjqSgIMeEtlf/5Coyrs26pyFf/9GbusddPSxxxwIJ/7IJuF7P1Yy0WpZ kMeY83h9n2IdnEYi+rpdbLJPQd7Fpu2xrdA3Fokj8AvCpcmxn8NIXZuK++r8/xsE AUL30HH7dgVn50AvdPaJnqAORT3OlabW0DK9prcwKnyzAgMBAAE= -----END PUBLIC KEY----- ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.8512414 securesystemslib-0.20.0/tests/gpg_keyrings/0000755000076500000240000000000000000000000021127 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.9253602 securesystemslib-0.20.0/tests/gpg_keyrings/dsa/0000755000076500000240000000000000000000000021676 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/dsa/C242A830DAAF1C2BEF604A9EF033A3A3E267B3B1.pem0000644000076500000240000000225200000000000027120 0ustar00lukpstaff00000000000000-----BEGIN PUBLIC KEY----- MIIDRjCCAjkGByqGSM44BAEwggIsAoIBAQD8oyds14wg48c64jmGdARgOfXZD0Hj 7em8mflAANFFaTUiZx+6SB0i4KmzHmldGY2l5i9P+0213GQHbQ8tfQPOlT/HhGpt ThehC/Hc0XFn96/3YbWfohgOf80spSfAPFDHhmW1U5vytFZIttI/MfN5meantOCH bdrX7Hg7jux+H7FHM+dLawsQXL3Fp96OCUZX8hRs5Doxd1gcsCKk4s5meKM2Slbg IJBVmm39gdkco7fGr9T8/Gb9iDOdIXBiRi9Rxckdbsz6+zIGW+aOa5Hsg3xZpRuu vsoccP04kcm7tn99kg+RU/xNLKA/iKJ7cN8WhHCfma0YcHGJsBVEGyv7AiEAhHee 6uAjjXqaAwpjm/AaD571F6XZUFmcGaTlT7vyMhkCggEAf3JSrhgkuvK+X8j0MaGX hoOjjUoizCvNwBzNH17uR6lkqldjmmGM+xsQcHtNCf8RpEjoO6cBI1c/LUmlmfUx OnRGPluzyj1hcqAPArAQZc4xJQHheX97V+YGlHxEvYOf3o1DJp8ft0r2zt9Nt/q/ CyNX7QnVY4Gsdp71qK8bRFDgyItk7hyrn63rMbe+Yge34XAIozp2E4MfcKEj1ZJ5 3LwiOPRu6qgJd5W3gF8bg37zuOgHFk4Yb66fo/9RAhMJa/VAQOrFRaaltHyRDmz3 4wbh9Gcj8UsCzZ4LD/KlbDsmBIaUMasyY9Yb9QaL7jbIgMe/LHRtyuXQ17L/8kTv QwOCAQUAAoIBAC3VCyKSRBREWB+aC32Nf4i1c/xFH15yB8MkaUIywi4XG1CPaEKu m6vFb+TlhqIghhiLSCe3q6jHv/SkrJqoDINUILGvukq08bHA74lEN5A6n0xW6+8D eASpmSXJoVO4oWwVYvKXdVrqog+gKrMqpTZuBStrqpqTQ1bU9fwhh4UBjdErLI5t YF0q+zbLBqnM7Z6h9fgnmNY13iZO8OtZWQxKSy/fI2mjb5VhSATHqllmupWXQEui 0saIGVkRLeUt5LbU8eLIpZ3arbCKWayDNBGPFaoBWT6FECSQXqbYhMOlRa9v3QPI 0rVNodNecQ73WitHdbt4xQso0eL7SEFtyUE= -----END PUBLIC KEY----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/dsa/C242A830DAAF1C2BEF604A9EF033A3A3E267B3B1.ssh0000644000076500000240000000216000000000000027132 0ustar00lukpstaff00000000000000ssh-dss AAAAB3NzaC1kc3MAAAEBAPyjJ2zXjCDjxzriOYZ0BGA59dkPQePt6byZ+UAA0UVpNSJnH7pIHSLgqbMeaV0ZjaXmL0/7TbXcZAdtDy19A86VP8eEam1OF6EL8dzRcWf3r/dhtZ+iGA5/zSylJ8A8UMeGZbVTm/K0Vki20j8x83mZ5qe04Idt2tfseDuO7H4fsUcz50trCxBcvcWn3o4JRlfyFGzkOjF3WBywIqTizmZ4ozZKVuAgkFWabf2B2Ryjt8av1Pz8Zv2IM50hcGJGL1HFyR1uzPr7MgZb5o5rkeyDfFmlG66+yhxw/TiRybu2f32SD5FT/E0soD+Iontw3xaEcJ+ZrRhwcYmwFUQbK/sAAAAhAIR3nurgI416mgMKY5vwGg+e9Rel2VBZnBmk5U+78jIZAAABAH9yUq4YJLryvl/I9DGhl4aDo41KIswrzcAczR9e7kepZKpXY5phjPsbEHB7TQn/EaRI6DunASNXPy1JpZn1MTp0Rj5bs8o9YXKgDwKwEGXOMSUB4Xl/e1fmBpR8RL2Dn96NQyafH7dK9s7fTbf6vwsjV+0J1WOBrHae9aivG0RQ4MiLZO4cq5+t6zG3vmIHt+FwCKM6dhODH3ChI9WSedy8Ijj0buqoCXeVt4BfG4N+87joBxZOGG+un6P/UQITCWv1QEDqxUWmpbR8kQ5s9+MG4fRnI/FLAs2eCw/ypWw7JgSGlDGrMmPWG/UGi+42yIDHvyx0bcrl0Ney//JE70MAAAEALdULIpJEFERYH5oLfY1/iLVz/EUfXnIHwyRpQjLCLhcbUI9oQq6bq8Vv5OWGoiCGGItIJ7erqMe/9KSsmqgMg1Qgsa+6SrTxscDviUQ3kDqfTFbr7wN4BKmZJcmhU7ihbBVi8pd1WuqiD6AqsyqlNm4FK2uqmpNDVtT1/CGHhQGN0Sssjm1gXSr7NssGqcztnqH1+CeY1jXeJk7w61lZDEpLL98jaaNvlWFIBMeqWWa6lZdAS6LSxogZWREt5S3kttTx4silndqtsIpZrIM0EY8VqgFZPoUQJJBeptiEw6VFr2/dA8jStU2h015xDvdaK0d1u3jFCyjR4vtIQW3JQQ== openpgp:0xE267B3B1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/dsa/pubring.gpg0000644000076500000240000000172000000000000024043 0ustar00lukpstaff00000000000000™.Z ¦ü£'l׌ ãÇ:â9†t`9õÙAãíé¼™ù@ÑEi5"gºH"ੳi]¥æ/OûMµÜdm-}Ε?Ç„jmN¡ ñÜÑqg÷¯÷aµŸ¢Í,¥'À[³Ê=ar °eÎ1%áy{Wæ”|D½ƒŸÞC&Ÿ·JöÎßM·ú¿ #Wí Õc¬vžõ¨¯DPàÈ‹d­ë1·¾b·áp£:vƒp¡#Õ’yܼ"8ônê¨ w•·€_ƒ~ó¸èNo®Ÿ£ÿQ kõ@@êÅE¦¥´|‘l÷ãáôg#ñKÍž ò¥l;&†”1«2cÖõ‹î6Ȁǿ,tmÊåÐײÿòDïCþ-Õ "’DDXš }ˆµsüE^rÃ$iB2Â.PhB®›«Åoä冢 †‹H'·«¨Ç¿ô¤¬š¨ ƒT ±¯ºJ´ñ±Àï‰D7:ŸLVëïx©™%É¡S¸¡lbò—uZꢠ*³*¥6n+kªš“CVÔõü!‡…Ñ+,Žm`]*û6Ë©Ìíž¡õø'˜Ö5Þ&NðëYY JK/ß#i£o•aHǪYfº•—@K¢ÒƈY-å-ä¶ÔñâÈ¥Ú­°ŠY¬ƒ4ªY>…$^¦Ø„Ã¥E¯oÝÈÒµM¡Ó^q÷Z+Gu»xÅ (ÑâûHAmÉA´Joan Doe ˆz"Z ¦  € ð3££âg³±aÛþ>|¹}bß±@Òe!Yè~ÂéB‚‡.GúË.ÿ“Uþ?Vyùÿ, mø&€UÕq›€­x!hs×àXc0·!°././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/dsa/random_seed0000644000076500000240000000113000000000000024074 0ustar00lukpstaff00000000000000Òñ¡D'Ež°X‹Ýëí èÃvÄ—­õíö›½}¤ÒÞ-§«…겈­´xm6­¦0 º,b«Ñ ÖÔ#`Ĉ$°Ê<ë8õ1¦2©tI<žî+¼K¬ù•ƒnAÝgWÑmr 𙿣*›S®P‹J„màÎÿ7[㎛ÜÞu%ìyK[!Ö1;ðSh²æWà»;¥O¹·Óël€Å°}bè 91²o‚5‰z‚ŒˉVýWK(üyœÙAÛ¦õ΢¸Le1±ø…°Vö[§ W(º#§‚·ôäìG’ŒE]¹ÏÓ:¢HEB&¸çŒUþ¹¸­ÇžnµÖ z”⹉1$nsý· E°„•wœi÷ÎMÊR„‡ ޳‰dØdà¶ôÈ ÕõÀ^ yïó|UF®Y”:Ú*´|›]~B_áœÔRª8`‹]„èM1ÑÅjzAçàʼnøPó:PË«€¿²ÝênWEf%Z–—ÝS èaX`DýÜÇMS,#›ÑÕhm‡ts™J‰¸Oû™þd;´³²Ìó*Z¯Ì$ÅÅ%¹òm:7;*A Zþ•9c'cˆêëKyš´÷STú–¿g3b¥,¢ÔIèêq­šI‰>ÓáÙtPß±),ëq¡®ìŽ7‰|ñÍE'™>Âg&»oV+·á~fŒðê3Å— ¸Þ,]Ï„yRÜróù¿àAH ýwÊ`”²Ô{êÍÓ?½|vWØ•ÈÉãRmX¦øW™®g£Ù¦¡þ³{S›,ÁÕþ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/dsa/secring.gpg0000644000076500000240000000176500000000000024040 0ustar00lukpstaff00000000000000•SZ ¦ü£'l׌ ãÇ:â9†t`9õÙAãíé¼™ù@ÑEi5"gºH"ੳi]¥æ/OûMµÜdm-}Ε?Ç„jmN¡ ñÜÑqg÷¯÷aµŸ¢Í,¥'À[³Ê=ar °eÎ1%áy{Wæ”|D½ƒŸÞC&Ÿ·JöÎßM·ú¿ #Wí Õc¬vžõ¨¯DPàÈ‹d­ë1·¾b·áp£:vƒp¡#Õ’yܼ"8ônê¨ w•·€_ƒ~ó¸èNo®Ÿ£ÿQ kõ@@êÅE¦¥´|‘l÷ãáôg#ñKÍž ò¥l;&†”1«2cÖõ‹î6Ȁǿ,tmÊåÐײÿòDïCþ-Õ "’DDXš }ˆµsüE^rÃ$iB2Â.PhB®›«Åoä冢 †‹H'·«¨Ç¿ô¤¬š¨ ƒT ±¯ºJ´ñ±Àï‰D7:ŸLVëïx©™%É¡S¸¡lbò—uZꢠ*³*¥6n+kªš“CVÔõü!‡…Ñ+,Žm`]*û6Ë©Ìíž¡õø'˜Ö5Þ&NðëYY JK/ß#i£o•aHǪYfº•—@K¢ÒƈY-å-ä¶ÔñâÈ¥Ú­°ŠY¬ƒ4ªY>…$^¦Ø„Ã¥E¯oÝÈÒµM¡Ó^q÷Z+Gu»xÅ (ÑâûHAmÉAý1“Ká;sñòXÑž€žŽ¬ò_ýº¾^`gÖå×î´Joan Doe ˆz"Z ¦  € ð3££âg³±aÛþ>|¹}bß±@Òe!Yè~ÂéB‚‡.GúË.ÿ“Uþ?Vyùÿ, mø&€UÕq›€­x!hs×àXc0·!°././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/dsa/trustdb.gpg0000644000076500000240000000240000000000000024060 0ustar00lukpstaff00000000000000gpgZ §  ÂB¨0Ú¯+ï`Jžð3££âg³± “03,ßy™i âœ|]"µ2›¸././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.9305356 securesystemslib-0.20.0/tests/gpg_keyrings/eddsa/0000755000076500000240000000000000000000000022207 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.9313629 securesystemslib-0.20.0/tests/gpg_keyrings/eddsa/openpgp-revocs.d/0000755000076500000240000000000000000000000025400 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000021600000000000011454 xustar0000000000000000120 path=securesystemslib-0.20.0/tests/gpg_keyrings/eddsa/openpgp-revocs.d/4E630F84838BF6F7447B830B22692F5FEA9E2DD2.rev 22 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/eddsa/openpgp-revocs.d/4E630F84838BF6F7447B830B22692F5FEA0000644000076500000240000000226000000000000031256 0ustar00lukpstaff00000000000000This is a revocation certificate for the OpenPGP key: pub ed25519/EA9E2DD2 2019-10-28 Key fingerprint = 4E63 0F84 838B F6F7 447B 830B 2269 2F5F EA9E 2DD2 uid Test Ed25519 Key A revocation certificate is a kind of "kill switch" to publicly declare that a key shall not anymore be used. It is not possible to retract such a revocation certificate once it has been published. Use it to revoke this key in case of a compromise or loss of the secret key. However, if the secret key is still accessible, it is better to generate a new revocation certificate and give a reason for the revocation. For details see the description of of the gpg command "--gen-revoke" in the GnuPG manual. To avoid an accidental use of this file, a colon has been inserted before the 5 dashes below. Remove this colon with a text editor before importing and publishing this revocation certificate. :-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 Comment: This is a revocation certificate iGEEIBYIAAkFAl227JYCHQAACgkQImkvX+qeLdLirQEAq7zNdUKTyETUF1qRFJHa fcfYPgQkyacY8GlIchGsm/wA/jSv+jjdXmqz6toF+cY2cpyikqVGQCmRYmpOjtNk La0M =d/I0 -----END PGP PUBLIC KEY BLOCK----- ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1614341428.9320874 securesystemslib-0.20.0/tests/gpg_keyrings/eddsa/private-keys-v1.d/0000755000076500000240000000000000000000000025400 5ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000021700000000000011455 xustar0000000000000000121 path=securesystemslib-0.20.0/tests/gpg_keyrings/eddsa/private-keys-v1.d/672E9A973B33784B4579F316820434E5631C086A.key 22 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/eddsa/private-keys-v1.d/672E9A973B33784B4579F316820434E560000644000076500000240000000021200000000000031051 0ustar00lukpstaff00000000000000(11:private-key(3:ecc(5:curve7:Ed25519)(5:flags5:eddsa)(1:q33:@qnW¸ÅÔ9zA”ø Ô:òàv‘Û~å$sεlïÚui)(1:d32:ºa%“™•CÒ_¦p'!„±áu÷#[BäÆÖcòÐû«)))././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/eddsa/pubring.gpg0000644000076500000240000000000000000000000024342 0ustar00lukpstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/eddsa/pubring.kbx0000644000076500000240000000054700000000000024371 0ustar00lukpstaff00000000000000 KBXf]¶ìb]¶ìbG^ÕNc„ƒ‹ö÷D{ƒ "i/_êž-Ò  •#]¶ì–˜3]¶ì +ÚG@qnW¸ÅÔ9zA”ø Ô:òàv‘Û~å$sεlïÚui´#Test Ed25519 Key ˆy!]¶ì  € "i/_êž-ÒI)–0W¹÷ÓŠæ Vܫ½½¼ñÃ/•ÿ®¢Y3Û—Úà\cݧ '±_<\0‰Gá°fu´ñ 0ñ[èJáV.‘ ÂÔ)² PV3|¼—ü‹]µpJ!Ûc]²~ÖŠ@™X·~?™° àŽé ›™±À†Pyë”v ¾­$ÄùqÄ0j“rYeóÿ-É€n¯Wð&Œ«‹§X-.•å%©Ü~Ó•hÄUhב{çÉTÕaÍB‘çz{Ýiã¬/‘ÞUþ?NsGè€âüL_|Ç\ã:< R8M`0—Åa…ó¼æp;´Joan Doe ‰8"Z   € û?S~ Š ÿniZ§"ëŒKGÖÓŠ¡Ûß„jÇ«]5޳ŠÑé¹g7Ïñ+­diï/] ñÚçÂl©A»Ê:h'÷„…pÂõ«¸ —Ò«'˜Š,5“ ?áwÕ²*ÙÊh×qLÌùÊëDÆþ£X öíÒˆ¦`U÷Äœ=‰a"–ãÔí¥Ä¼ÅüEˆ$Sÿ—K`±XG¹›â}%ôŸÒ¡á@h…ðŸt›Ëɘ8È?Òº0ä“z²WÁô9¦§IqHδ e±~_q ñW¡g}¸Ç Ã»~w£èÿz>v+¨¼wQTï'VÆ?¯qåûÁ~3@³vÙïä-:T zvåfŒÏ?HGRWÔ›¬ùÿ°¹ Z ¤(kþ²¾éóm™Sï/ƒ&_+€ $˜u΄˜öðŠm~¡MÎÌ‚;qçÙ=nË)Oˆ€îp>§<´]‡@"q OKlßyôD8Ǹ¸´–=Càô ¾®çžHýùþËfvÚ{¡z]ÿ¨Šü‰„t=:8y¢c„Ìá#PâPhì8Ôã×Qv¶‚Ê¡-MødÜYñùŽì^^"ЫlÃ+ñÝpÏL,§Æªä ùMμ/ Ö2X!èÍÅOBÖ>¼6§&€¯¬NQ_˺"9¾ •«©Á}}p ßǬ–SzÕ ÃS=7í1~ήÞS#&ÿô•ÊŽ ñøF¯,’y¾G§u¸?°¹ Z”2ÔÁRü5¦ÓÁèÀÞÎ  2DfáN¥]r#«\C9>ëÊs̱-DÃyúãö;&= y8‚§3.ðo(¤¹®w]-(‘gèl-ñ¹©á'¬²hh…Vì°„’Ðqðl¯ˆÎ©Uq5CIØïÿ°ÒYú㿬šµ¬ÖòoGpþ/0üУÅOF:0”ªe$ã'¦%á$uÚ$³µ6ßa°öâ•G9¸‚Œaføáv‚>ˆ~eú ì²¥Ö÷ã³)¤Cá"+›bPÛ E§ ˆûˆÔ6Ôsö4H•>‰TÇ¥sKöNbtá–Û0t§Î6– ªË‰> Z”2Ô) û?S~ ŠÀ] Z”2Ô ›éßQ1Ù$éЫá™Ë¿a…R8ÀáDÖk²£Pt?éMM6xÆi¬Wɇ }a~Å£ÓhM·ôÖÚóåï©y©v²á$p¡Q÷ßÛ­^£ÑîÁ(ÿ•‘”?WÇpñÕ²gŸŸJ“ Hwåk–ŠXJÒÔ ÖÍö¤Ö>¿S5Ý Žm/ÌÏ–­œUK½ª Æ{ÔMm(™r¤ã‚àÎ\,¹M1“âøÐ6OæÖßJÅÚ•¥FH"\1”'er¿(”ßÄHiÅ4>›v}C$ÂÏÑ\H<6,ÚŒúÛ Þ¿ÑÐ1îûÐ ú9è|ée})$mÇ1„BšH¿­Ñ Ÿ¿=&F Æ­)þ9è<«©YmЉüšî²lƒø#˱µ“Ô/nzwán©÷ÓY*A£„ qNÊÔúœDÂ~J«/>Ø|è­Müð“›Î' Á櫯ìõ‡fﶪhµ§šIFžáÔÅûWáùIfÂÊ Ç^\2Ê « $R;x K^Q¥d÷19.U⧇µ"Ž·€w]#]dÖ´U-¢b%^l°³^€ ÿmé§{òð9º¹Kúø|¢…»€ÓIr¹’¤ÕûfÊæà¬qÉ ¡=»jNîïHs'f%YœAz³øÑ÷–ZîX€št³W!¸»w¢ëæ—;Š‚…M©Cî#õ»>³I°¹ Z—¨ÌjŠwCÇŠ ¥þ/áW©Òn]%º}1¡i¦ÕúB»ógô±Ó”]T2ð,5w>˜q†Lâ’Eh(,seÕ¹QÍQŒFù°ßÎf¬ùèZÔp@Fc`÷HõÖÀ-fA“KˆÀ0˜yDð¦î±Jp•%âÆ_û†5ï¬YþË~Ör¨N-óÍ&ƒ8§€‰h}ÿnÈvx0W«ñ2ÓË$–h¶9“ø‚Ÿ¡/u"JZ(ïŒHJ ®”Ñ]—¡l\:kX‘ó'";‹²z¡E¥¹Kq”¾ÂưÉIxª¿Cè*!vHþh$dǵ9<Ñ/K\®™VJ¸×öFÅýMÚÕ¸ŸrÁR"3ÿWý¥k\EÏÆ~ùSGÍ$6kXÄ-D êá1ê»gKC!ÁFï8óåö/°+ô¸.åHb0&-„]úÌ;­Ð­ù¥ÝRaHøtõÎ^2‹[ž· ]ÿù‹P0Ft°€‹-¢‡;¢ƒg;­ØÍ¾›|’½ºÇcÿs;€W!!Ñ©úú~¾¨14ÿæ!š;‡l›þþÊÛ‰RÊ=cdz ƒo€pHЈùEövVåW×j·3Uâs¬ÿÐ_ʛɭXïÈ"<íCkx‘ž—H£ÍÛŸO®ÆÅpQ¯17¨Q§©ìnpuî‚©ª§JônÛ—çüL ®n’‰ Z—¨ û?S~ ŠQ¸¸N—'+Jì@% +Ö|R50ýuŒGÐØ¾9//¢L#9åÂ0|…¿[ ‹Pßen ª\i3>¨P_Ù}¤ zUÖ¡µ9°Ý~L­õÔ^}N›<ö_ÙsüOWC¾/Ö¯ ‹ZeùÕÉIæÐ^Ù™U· %Ñ­düÁÔ²äYRf+9dùxð5”ñö~12M¤÷ðPòاɲÙíJ¶Ô oí¨ò±¶ ’P7GNªá…hÏ.•<ãS:,æ(€ºÙσlµúÉ<»/¹­cB ø™û©$¡1W¨Ù™\•$nmFË–ÆÉ¥C&–IXÁ¹¥Äu¤!/qcÑ p»T§º°™ Z!¢6é­9®2½OÌA ù—‰íjÑÅÁ(À*Ý"rÜ"1‹d¾ÉùF{iI±ŸÂéŒäZÔ] ÖÅÚ{Þ8 -ž>i|è¸7; W4-Ù!Ö4¸sòXõÁUYµ)!úK·ô‚ìC¡È\3…½R í½Ák%$¦Jìó*ÅimÔî©uá¶Å¯N¦œ¦E3B$2‡0YG“ˆUg»|ÿ¬öë_ýÆ@è˜å™W›!±^I\!Åý÷—NpVÍdþ„òË”müRP1)žbu“no”d§5½NÜŽ Þ?屿m;Áí™;†]Ëš+.òß0Ë ´ÀÞ¨êñ•´Don Joan ‰8"Z!¢6  € [ Ð.vŒCvy• JŠ‹yž#A­a­pËt<&cý¨û¯p¿~JõÃ}ÔQjTCÛýŒk[ýîX_a‘ÁŽnxbM›¿³öÁ÷–"ï>¦åƒâW„£r¹”¶—T@]ÖæØ¾XÞWO(“¨Ä’ßÏ4—­ŸëŸö 3©„"}FåǸ¦Ð?˜8ê‡\?Ç¢L¾)qS‚÷°¸Hnô—«ñæY£ò,Ûø€”¡S1mN·ã–þØÎ‹ÞÊAòîÏ?èáô'¡Q¦ó²½±÷ yõ¨“.õ¡; …‘|Ð¥¢ŽÆé½k== Uä˜Ç‰“é3jX¶©ÊNÂzØä°¹ Z!¢6´ƒÆ;;š¦Í×ÒÞN8¯ÀJ¼’W?†ÀÃEØ P*ÔRðˆÏ¥1éã<^×à ÿ|ÆUÙü’€/œÈ8ð–êQÞ=£³îÉ{Ïÿ£Õ}.CÃýqV†¶e’RÖ%…î„!ˆ>Õ ˆõ¯fÓÝÂyiµÜæS<‚ÿåÚzë{iÍà»^Ž¡•^ÔÊj ¨ßy¬’Ôòe i†òîd–¾3âÕëþÓåvhqbSZY·#„zs m¿÷­ÉçÓ9º|¾­“„„¿T9ŽL·áÑV·^4°DÝþäˆ$ƒó÷où÷U©`óoÓ2`"ÜD?ÅÌÅ05‰ Z!¢6 [ Ð.vŒCKPÙ­ˆm´Àâ0À ç2"4Ʀ?§v­”®ñÜÆ¾† }ÛÉ‹ct.k¶vÕº°WÇ@Œ¨¸3ú$?]Oä«DcV§c•Ø»#¦?sÅw.Û1ßó"k„ã>ŽCS[‰FØ»R¯ë†G‹€ø9u²ëí.˜X±”³bÄñ.•Ô‚îh 3ÅÝUÅÚ$J# fΰüÕ•8ðEéú%Y¼'¹tÕ:Qä®ûˆ¤œ²“§‰F¬›˜×Þ%.rT¡Zĸ)¥ÅÎ@MLéŒz㹩ٙÓw¯ƒr·’,ùœ¥5… n5½ºíC–Õýa8K¶\ΰ™ Z!¢¯ÉE¹K¼PJav›¹’â¹$2ÉŒ´‘ƒ–Úß1­™(ë!å+¡âÔò'7±ìãm48c5à–UÌ; ”‡Cö¦©˜‹½ïG€Lêä>±èñ¡ÅX•g¸€,Ÿ3À.8:`'•¥®ÀPr,Ç&IþКœæ_õùs¦¢<;Bí~úÁ=÷rtǾ7P·#@‡éÔu×½“² O\«Xd½ùLA~}=š Hª¨§Ê¬ô¬zþ41ªNýÚC jj^àæèRRW·Wo9F°Ó*aP  ìô—DQ†o—×,a|±(—½Ôqž´ŠïÛ_O« ½3°>»¼Ý´Dodo Jojo ‰8"Z!¢¯  € V0‰²…ÚXŒ˜lY÷¬ùð¨ó¸,yZËS'q¿ÈÙ'NŒëš¦pó²oâ4DyÎd•¶Zm?æÏÌûß¹´ $›²‘íðrc±Õh&ÓVàuæsú‹,i­ÿ¸ɹ$ï&m5ð-|Ì3èó–´­:¶ÎÃhaýÏå`nGZ„›©ý|8Z2¥wfAµ¹v¥£nž˜õÿMM|:žíà_Ò¨‚ÌÕpÎæÃý!°õò­f´j$Áo«OÇ|.íGí×F¿#)W0¤,è'Í:hD\ßžÓÿ«›Ð.so6¡é<`éú­ö× '¬ƒ2Ö‰w†Ð id· –‹.oâÀ‚xM¤-°¹ Z!¢¯ØuµJh—AßçÊ!X7Ú™Y4é˜çî.0l7 vãxÑþ¸{jÈ»±!Wˆ.¢é²p9˜6´gÛÅ%)ðÌåf¶K×3‰ÿ÷â’&£QˆzÚÓÿ–·ÙM$îGMsÕõöFÎÿŠpo,Ïü×[›Â<¨‹6±öìÓ¨uÊ_Š[소BuÔ& g(SËæég8È£¸kH0*û ëÈ_™‚éóµ¤;Ä“UNTå5°k';ð\¼â3,·Dõèž\T[tB­%βYÆ ³z‰8§+qè¿õǵ&†€!r˜Å%™>kUûr¹gä`êR¦=‰ Z!¢¯ V0‰²…ÚXëƒÿSmswå‚«[,X}7™ã51eœd"ûV& ŸWŒ™,·j™êja@²QH8ù=¬üL¾•1:õ ?“ÕH=ªK³±*Xê“æs±ÊèN'”©ÚªÌœ&2iMlÝ—´—©yÕÀióå3õÎÞoë¼H¡Ú ôâ®Á&¡…@ÆGìŒ9>ßos‚ØKø´-ë°(¤®]NC‹~°èîfý0þ´¹È1÷ãmúdžš’œ(…¹Pù+5³Œ¨(qYÊþ þ_‚lüÚ9õÔ¶IôÕñ¬ÞŽLi{¡ëøÑCв>O‘0¹€ô!ï½xì©ô¼´`C+¨Å¶eZ2İ™ \E¥óâ(Mb0œï]Í¡²ŒmçgWÈ„|䮦¤öøFÎ#±6Y3º†:8GŒ#º]U´1x2‹i~'Æ·`¼Q6É/š(|‘»Bõ+Žš7óbÇ(°c¤ÚHËXI‘J… ®¾ÄYê2<Â\0ÌúXHÝ, õ#G’Œ->ã I¥g̵‚+öXóöV)f2Á=G›Fª¾ Ã>Ú@áš1ïlèLøuÌ‹§¿åíkþ>6 Ƴt† =z¬çy4–DN²˜›Ü ¯°œbî²íÀÄ{¡J¦¡ 1âûÞ‹…~òtȦ[¥~Ø­vžƒ»l…ËlB)Qˆo´%Arthur Two Keys ‰8"\E¥ó  € ÒÉþ“f™—è–­Þs­òdf¹me¡ÑµIé2º`µûÏlO—÷X½cZwîH: k{Î{ªi`‘Øÿû²oÈþç÷ÅètÄ…ð£uÏ‹ùÂ@+H•£õ ¹ zfsóÈãR¯‰Œð}Ú–Ò!xa³cëŸâÐwüxå oJx®7€à`!¼ÃÎý ¼sóY.Eص%nŸ†YV¹q(¤ln¡+\ë.WKôo«¡Ç?=‰/ÉÅ{ù½§B½ guÒBüÖÍ 6¬s —1ÃÖ<ÖK ¢`Ú@wˆÙaDÈû×§!Ò¦žP÷ÆÖ˜«:åcb<þ6†´é‹RpöŒªxLް¹ \E¥óÅü½‘Øã Ö°r` O7ææ†Zc Ãm·«ÅèHõàý]ppâÉÐ>Œ¯Â=Î6:Ç_TÊ‹ö$‡ÄÿhðÂsHxà+.Þ®ú5“¶ÛF•h•Â1!òíÁ%Ó­mµSç=yñ®2ì4Üø”JwÛˆûØ-}Ñ!w¶ Ŭ伵žXQ†ª÷‡_µô±“Ê\h&¹Î(v² 10Úö× _~‘—üª™àá"©|œ$Ú€MZÝÁ׈cÍí# ÂQ„l%!ùr]/Km“W¡ êÝŠ£))è”I 'YžŸQ&ÑŒé(SÍ+KFDƒ‰ \E¥ó ÒÉþ“f™µ-ÿbælѼ½´‚þ¾¦¬îÞ“¬°:Õ­í6»O7!új’ìZ¼±Y©¸€ HµÂ©‚ —qÍ¥ÊÚȇæÜîML¿AâìŠt4ö&Ÿ±a(ß‹…SоÓÄHÄ^4!fŒi·Ž›€ØÎL˜y2|ùq6 ›`ÓŒ@9 ýx=±AM¸¨Æ;^ŒÉÎG”A‰bÔQô>Ñø!$(š§î¨é€ÕgÊÀÓîÉB”FªÎÍ)ÜÖ– ]Â’üvm÷ûҾĄ7w-ò§üw¿Nc¤ÝÍrÈë¹Á®»ˆ."°¤qt‘ÀàZ¾Ü\cŸót¯mª‡¿‚°¹ \E¦ßÛtµwøGq¦” 8àÿV9°JW çÖ¤A,’GšÎÃà–ÊaùL¼õDaõ€ÂÉ–Kâ9Ʀ³!¶ÎsŠpró‘!½ír?WÄ­€Fôi,ÞØÀÚûfgŒÌl‘Ã@ð•£Ï}´K!iºž*öh›7Á„zk0ø9!l¨nª!9Ÿñiû{JnVqä÷èŠg¾š4@Ngóxèö½£9 Œ­ò´>wöLYÕ~”8ÖÎkŠ;ÏÍeÞ •ÛQ1_ò®ûDÓ# ²È.‘4Žœ’÷B§èÃth’Ë_žHN uŒ¸8-Îg –uz unû}¨)±e Ï•…‰> \E¦ß) ÒÉþ“f™À] \E¦ß +%dðïYþK5ücÁÁ-³öR»ì¬¿"HÄeà°eÚtöfXj.vÃÓ¿œXù%d%4%t)»DžÜÚ t0~7‰,ÞdÜUq DlÞÚ§ +À0žkô1ëºÈ9-ã_ž2?üÐî’/EK[“è­ÇmÓÍó|Ø]Ž(%°Œåÿ”ÿñ¹˜:ÊÃÐÜÕËyí ˆxã3m"Q~U«ðŒÝpÆÅ>©©'--–› ¯†t;Å•¼D“O*kR”‘ÞÀêî†4AÉ ÐÀcmQ•?1ž‹õí¹îd‚£Ÿ”ëb‹íÕÖ„ÃÄ ‹êC’äMj{8m_×ÿu!Ò/»£áóéˆ> ]9N6ÖÒÁ !jÛ·K…+ ¥¤«…u0÷ó_.ÛhÓ¦ªxar)ŽÞLwœÍu¥D#^ïræi·Ï¹eŽì™3†ðÄáˤà¦Ö4}`šL*i}§ QqJ‘|£õÿNò-V¬E·6ŠÓ ƒÙY%…GdÈtqû7¢oNÄXu|ž¬VÝü&_&‡¾Ïù—ãEÜI¢DóƒcõX$zòìÃu@Cz„™Öu+S"‚ þÌ9îb7ÍÄ þĦ~º*-pü7­³½5@wg&Ad£GÌg×|¥RæûwoŽIôWÅ”æk÷|ÿ-BÌ~'t¹2°¹ \E¨½áMw—q¢e2¥ŒHö~£þ‚xC±º³¶)ùÅÇÊHùÊÃ@ß÷Ð[·ªÁb~]*Fÿ¬ª•©­³¯(8^ýþ¹øÇ‘ ß -ã0#ˆþeøZòb8T+Ή҂)?› ×ùrj£É*ìXmÌëüÈ 'ÊÍésªìe°d¶òÐ-õ*±Àу¢¶Àˆ]Pyà« <“ tG"C°ÐšÛP`ž`Y«|ä¬3Ñ®CbÖ }0ã%›S)Ñ霩\Æ’BCI¯"ˆk¸3Î÷щ> \E¨½) ÒÉþ“f™À] \E¨½ þ­’,‘ësQlcÿ|óItJ½ÏÒ—~1>…¢¥12תûã‰d+ÙñݢёšIºZêÍÇýUíð××ý êíGíú³.øììé wOÝk»«[/0îÕ_ÐlÐBãã¿ä½RÙ‰ aÎûpäý13·‰«Šc{Š1Ðõ%dYMŽ»krvÐL¸èÛrŠ:`Mä‰n2ɾ4ºç?–É'ÖÖk£…ü¾Âj$‚ö—¹ž0Ÿ¢Ò™oÏ¿qYCo +û@!SêHµpv*ŽñSKµ*w²^}ü‡üW_ªð‚nÕO÷Ê'ø×Ñœ–¦0ç¬H¢9–(ÿ.kÿ]Š+‡~J|U-MvÛÔUWº9”œBDQtøÚÌþH7è'Ìΰœâµt[ˆ2ïý›ð㻎)¿ëWÿUt­c ¨îv9ü|µºG!ˆD ‰!´ÐÏ•i;Y?XˆexêȇäÒÉøO9øóÉ)gýg¥M0yž~ÿ2¸ì;†5 9ÛM,*Nßc/Iêµxg}rËKIkO‰™ß4a›ŽšªËÔ§Ï¢Î(€ÅŒ*¿t³O;íaT ˜ ÿ86ŒN>•½ðª‹ìS0âxoöΠ9l päSE+pP:¡äPÊa›ñ¥³Qа™ \’gí½¡¹—òÎMŸ«ŸN©Ò ¨0µ —ÒEa¯°Ã•ËqÆxjUxE{ÿ}9Ø÷ÿX£c|ˆ9–e'•³:‚½Nºê~®_ô'Ð}„“†#ªàgÎ ”æã=B¬³zC¬ö«IDpmô8¥ 3~iÒùG®˜ùïVžaŠízj!©ÛO_×ÌÉ*ÿ¹…@}À4Ë¢$«¦dg(–9评ýâJ ~GÖ…ø§¯ÎÎ88 Æy2| ๬z}°äLJ1ض·+<ÊcÐMÁûÄ<%aò‘Ð¥oöhP\2xºèÇå‹$4™þ±™<Œö¥Y ÈèžÙ‹êøg^™·Ñó‰- \’k'Testing reason ž§ Ñ=ˆ3«ßÿaIèuÌ· ±›Ë‚Dî£Z¾ûÐšŠ–PÖs—³6[Øß·¯'Ú<¿Yñ¸ºzzµ³Ž¡Ç‹x~*÷½öaèò9…)›õ'°ÓõÈ,ź³üsÄ ©ê”ü4‚4Êûmâ€ßýPb‰HþGå¥z…ݱàU°DúË ¨JYáæ%¶òÄéAà­¡Në¾6;X7HJ×¢û–t¥Ç°@Øú׎âO(`êJWš»Ÿs¼gÔÿµˆmU¬ £(ÆÅµ”¥I¤mî•Ó·4Á-‹Yc6ÎA ‚Þ–]¦ OðÀG œ|i\à€Ýw³¬çÅP9¶•¾°nHän|=ãv>“#°´Test Self-sig ‰8"\’gí  € ž§ Ñ=ˆ3³®Üµ@çË )ñXb*ÛF“_Aß™õ¦:ž›5I¶’`v÷Ò{[ìð[Ù¯‹ôÞVX sÃ×”Õz†¶ÛS±>7Q¦€kÓF;Ê6”Ü荒<íˆü­jÉtPª44(«–ú3òOD×Um—E‰¥gƒ±"Õ:ÚçóÃ9(ž ëü>¼ö ‹vœ‚Òr¹[ˆ5 ZæÃÈ4Î{tY"„iG”á¡`ÞSj>wSe¤¶3?XßSµ01ù“ã½ðÓqž›[sËU„FqLx¶•§ô=™MÿúIgRÆ€»ÆH·V)ç$¾òãZGFû·ÿÙ±:¯²°ÑÒ¾Ò¼ÿØÿàJFIFÿÛC       ÿÀ ¢¢ÿÄ ÿÄQ  !WÔ V•"17Q¥ÒÓ)2AXuv”–¦³´ð45aq#38B‘¡CDRr’“£¤ÿÚ?²˜ßÅýåáªÔYš%…¡Ù™ÌV`=)õU£HuiZ$‘ Úy²"ËÎF+VµlBõ6îû:wÕ± ÔÛ»ìéÜXkVÄ/Snï³§qa­[½M»¾ÎņµlBõ6îû:wÕ± ÔÛ»ìéÜXkVÄ/Snï³§qa­[½M»¾ÎņµlBõ6îû:wÕ± ÔÛ»ìéÜXkVÄ/Snï³§qa­[½M»¾ÎņµlBõ6îû:wÕ± ÔÛ»ìéÜXkVÄ/Snï³§qa­[½M»¾ÎņµlBõ6îû:wÕ± ÔÛ»ìéÜX“])}û[k̲V2«dìP«ÕØÉ.G1.¡§ä!µš RŒ‰DJ<ŒÈË<³#ñ .ÊÉåÁ|Í+ñÈQÜ?ùy»o¥Ôδ7Œfw+'”ó4¯Ç!DK.Þé¯÷«}ï]µ¨W¦–FáFALç’uFM´“ÈÈjIìñ‹ƒ`ù(­åN2%Þ5çR(+Q¥]ÉL†¹ë$äY¥KRšJUã/L¿‰‰ìÎNl(Ùfs¶·ëZ„´†·«TØi#þN2¬¿Ì|w0eÉòÊÍ·±J¤,¼iU·¢ÿ‘°U_~h~À}û¾Ân(6öÍW,¦%½Ó­ÓªðåÓawåG{ºe6òÓ\Ûl’פ²JtRd£Ï"Ú/ˆÌîVO(6 æi_ŽBˆ ‘„ŒνZtkÒ¾7ߠ؃GuEŠK&dÔÙ-¼á¨ÿ±ŽdYéü%§3N‰\fõ1ût×G+«Â­Œ£ÏnœJdªÙ·KaddJ6Òœ—-g’³tÔ’3ÉZNfb^>!/ªöžyvþòku6ËJt0‹/Q›Ñh¿žŽgñæ9胇ïv_Lh¿hnøÌîVO(6 æi_ŽBˆµØÂäkñ·Û‹kž±vQä¬:ƒ6êS²%"9ç±HId· Ìm¤Ë%™—ÛÇN2¦Þ}f]ÐÝ…S¹ì=-Ó6LUäU—y„‡&Y%%±fZg™hhÓ`ÿÞ쾘Ñ:ÐÝñ™Ü¬žPlÌÒ¿…Í2›>µR‰G¥DrTÙÏ·3 kuÕ¨’„$¾351¦˜Ž¯BÁ~¨)c¦¡«Mi#®š¹lf•¨Ô’]FZOÆY›„Ú6é$F‰ÿV3tk¹Ã¥øÞË™w÷e[ªÂs= ÜÉ1yFI饣2?³¹>ñxe™]ÿÖ½LâG÷WÖ/7Géú_¾±yº?OÒøÕõ‹ÍÑú~—ݬ^nÓô¾$5}bót~Ÿ¥ñ!«ë›£ôý/‰ _X¼Ý§é|HjúÅæèý?KâDÆæp3Šk'|Ô×itkKL¨Mݺrù¨íJmn/E JÉ)3É$fyl#1¬â³bË¡g|”Ÿ½;ßr¿{~ϧáþÃàü,¾ß6ÁÂ=èo×|áïC~»çzõß8ú¶RÔrPØ›KLµöj[qjÔyMÌ„úãÚ7ɧÐzH^ƒ‰R ÈÈŒ³#ÈÈâ+×¾¾M[ð¬Ã¯Þªr·6n㌳hc¡¦´FDÛ(B33Q欴"#3$‘#Þ†ýwÎô7ë¾qìÓ*<‘ÔŠŒj¤Fã-莥æÓ&-£’Ñ©'™Úu*mÄùÒ¤šOÄdd,?˜B[h‰ öb°ÛI$! ¢ÔBRE°‹J9°ö#® ÝÄ&,Þ'èö}ײKn1>žÃÄgâÉ™Ygü "3xe‹™pUX¹,Y³=¥¤”ÄJå¥\OÆe.Šû†|îÙêRãÈ"ÈÌÚyÍ·2̳ÑQ™g‘äb¬›Üô/† `¸¼Þç )|0kÅæ÷=KáƒX./7¹è _ Áqy½Ï@Rø`Ö ‹Íîz—à uÏã£ö¦öìM˜¯^uS*öŽ›kâS‘ΰì–Ðâ4“”œÒ£,ÈÈË=†F5¨fw+'”ó4¯Ç!DLnÚø¯>è*…W»kmT¡³3‡ T×é×Ë õ™œ8j¦¿N¾XO¬ÌáÃU5úuòÂ}fg©¯Ó¯–ë38qøÈä©¿äq­¥€w/‰Sf$ÿ*cŠÞÞqrÑ«Û ûÔf ôêÔÇ2*R_ãY£Ãi;rÍÄ ³hõ*RJJŒ”G™Ò1¨(xñÉ€÷—)6ÜÙd+Â"Í×*Л̌¼Dj’ÂÈü$§Ïþ— '¸òóvßK¨ÿhoÏNS‹Ï¼»nlTK xvšÎ1.“!ÇÚ¤Õ¤CC«'ˆ‰K&–’Q‘lÌÅ.éˆ]ü^'Ú‰ÞÔ:Fâ‰ö¢wµ‘¸…ßÅâ}¨íD’íqk~V.ßÙûU[½‹m\¦Sj ¿:›6½&C2ã²u£mÕš ÔƒQ™l<Œ¶‘ «Ê*ócX{)w-z¦Ÿg”ÃqªEC®JÈò2\Id†–HÈÍjA¬öŸ8Ém"ÙAºFâ‰ö¢wµ‘¸…ßÅâ}¨íDîæñ»WSkÚ´[m\¶”Å‘56“_«H’Û­g™ókqJ6\ó-$Ä”[ý±ØÝÃòC„U[ÁªXz»DkîZ„çéÄÚ¼FfójîgSæ'~sIÂéô{ú¸}<áOĉ¬FoO´´²pÒà2hÛI‘Í©ÏÎf+=³Ã?'U±´Óí*/ÎÎÐ}ÐwžT =´¥³ ¥ÂæÛY,ÐFyž‰‰g’H‹"/‹Ñ “·å/íÝ#Ù‡D.Nß”¼o·tf;)˜:Ã[uÆ,#¬ä¦kꎹ-Um5ä!Ló„•6HÐÑ3' •ãÏE>aÇj¸EäóªU&TÓ‰ÈЊ\‡(Ñm½,°JQ«A²SFd„ç‘™žD[Lz½ ù=>U_~h~À:òz|ª¾üÐý€t7äôùU}ù¡û÷îû ¸ [Û5]²˜–÷J·N«Ã—M…ß•îé”ÛÈSMsm²K^’É)ÑI’<‹h¾#3¹Y< Ø/™¥~9 D9?oÞÍÞ5‡¨a.öÉ™ŒHˆûTR”½“!¬”§¡çã%·š–ّ磙6œêv'°åi°ßxÙš’•Cœk‘Bª©Èä̶¨Ì’´ìÈÌ”E¢¤™ò^_nÓé…ó­ àÊÉåÁ|Í+ñÈQÍ2§Q¢Ô¢V)Ÿ‡: è“K 48˨Q) B‹jTJ"22ñ 5¹÷º¼sÝšîÿŒÅ³KzM-–5ÆÒz3a+,›’’ÌÖÙFZfISf´&™âK—‡*£’*°×X²:i…_ˆÑó*#?§i°æÒðUà™ç¢¥dypÐÐ0õåöí>˜Q¿:ÐޙܬžPlÌÒ¿…´9“)ÒØ¨Så=Tgó²³C8“Í+J‹jTFDde´Œ…ã¸NRÊ¥&–Õ†ÄEv¦¶»•U˜í¡rÍ£#-,«$H,#QU‘™8£3>›Qž qHÕˋ·Pìíaô-åF£¼Z(3<´¦½¢ãI#-„Ži'·ÇãnÖòW_}*Kê²6ÂÊW¡£û#yסIsù¶h[iÿÊcžLäñżW¦.ɉi/öŒ×iä“ÿ½ôŸúÃWÖ/7Géú_¾±yº?OÒøÕõ‹ÍÑú~—ݬ^nÓô¾$5}bót~Ÿ¥ñ!«ë›£ôý/‰ _X¼Ý§é|HjúÅæèý?KâDÂæð1Šk){ÖÔWi”{ILŸ5ÿvéËæ˜jSkqz(jVIIžI#3Ëah›] ;à ô¤ýéÜnû•ûÛö}?öáeð¶ù¶ïC~»çzõß8{Ðß®ùÃÞ†ýwÎô7ë¾p÷¡¿]ó‡½ úïœy²ÿ$dg‘";ËiÖ”KCˆU¨J’¢<ÈÈËiÆ:Ý‘Æ,JnÏ_ ¢udMT´•¤ˆ¼IL²p’_Ë!½ÞTë J&sR}r¨y¡ªY¾æ‚ÞÍŽd®uÝ»4LšóçñSW(6/ Fe{D’3ñ™‘ó†°\^osо5‚âó{ž€¥ðÂú±|7¥r88‘{7ïiýÑ·u«~ŸL(ñÍ™RK(qy¦Ù(ÐDN¸FZeýqg’ *'¬C»ÉØP=ˆkÅ®òcvbÄ1k¼˜Ý…؆± Zï&7a@ö"[t8ñÅ «½›eë·ƒE6±h©°&4TXH7vKhq:Ih”œÒ£,ÈÈËâÄ3;•“Ê ‚ùšWã¢.Æpˆ«kT~×¥O(öFо餯–D„T¤6yóËÒÿwhË=»¢"Ú”¬ŽŽœQ•ÿÞ,ý“˜áØ‹.âÚ§™’gÉ=ŽK4ùà·žÒFgà›ŠIV0Ü?ùy»o¥Ôδ7Œfw+'”ó4¯Ç!D{TªMV»RG¡Ó%ÔgÌpš#*yç–~$¡ #RŒüÄB÷ᣓ­Ø¨nó±8äzEg0¬û²ƒ4'Âç&ºG¢Ód’ÌÛ#ÌóðÍN9ŒìpD·t×.RâÜ÷>ÅGl¢T*Ûæ=Ñm¢QØAsqHˆˆö¬²ND‚2])Ü?ùy»o¥Ôδ7ŒT oáòñ+j,ÍnÂ×,Ì(ЊújÒd4µ-nˆÐM2áeç2«UN!zåw}£;„ U8…ë•ÝöŒî5Tâ®WwÚ3¸@ÕSˆ^¹]ßhÎáUN!zåw}£;„ U8…ë•ÝöŒî5Tâ®WwÚ3¸@ÕSˆ^¹]ßhÎá¹K䤾ÇdkWƒb"±žÕÅv\…‘ʦ/õ"‰Ésö*­÷_kÎÓ£š\Y¶LQ⤋Ɨyn¤üäh?âCêÌņ 0±O‘D¸+ÐV *inÒš2K†G™%ú‹úN8ŒÌÌêKâÈSkÿÅåðâÕAµdS,ê\Óf…MÒn.ÃÍ*tÌÍO,²#ÍfdFFiJs2L÷þ^nÛéuó­ ãéÊqy÷—`mÍŠ‰aoÓYÆ%Òd8ûTš´ˆhudñ)dÒÒJ2-™˜¥Ý#q ¿‹ÄûQ;Ú‡HÜBïâñ>ÔNö¡Ò7»ø¼Oµ½¨tÄ.þ/íDïj#q ¿‹ÄûQ;Ú‡HÜBïâñ>ÔNö¡Ò7»ø¼Oµ½¨tÄ.þ/íDïj<ÄN ]I¡Ûô¼%¤ö*ÓÍ2?ý¢X­Ö­ åÕ+õyµ9Ž|9$-çUüÔ³3?ó îü¼Ý·Òê?çZÆ3ç”Êéï>ñmÅ‹™`®úÐÚ&!Ò¤5!Úe9Ù)ifñ%F„™™mÈÅ2èňÝÅÛ¾À“êF,Fî.ÝöŸP:1b7qvï°$úÑ‹»‹·}'ÔŒXÜ]»ì > tbÄnâíß`Iõ£#wnûO¨±¸»wØ}@èňÝÅÛ¾À“êF,Fî.ÝöŸP:1b7qvï°$úÑ‹»‹·}'ÔŒXÜ]»ì > tbÄnâíß`Iõ£#wnûO¨±¸»wØ}@èňÝÅÛ¾À“êF,Fî.ÝöŸPMnGwûF¾‹W«\Å´‡ ¨¥I“%ú$„6ËH–Ú–µ¨Ñ’RI#33ØDCg€ÿÙ‰8"\’jì  € ž§ Ñ=ˆ3ÇýÿMo­)9P`ˆ Ãö2ÌP/¹Á¹íÁÿÖ¿ÞøŽÕ<´ÄXS?.]4 —eÿE†mq×øÄœ^éžäœÃ3ã!d5¿nÍüAÏä?x—½´HHñáa4¦ÁÅEŒ^¦/Ê Î@3W½7Ź qJábùTæg‘êíg6ë_FýÞH¼n í Þ”@ÓRlÁàa} nSë¯iÅ|¿Ìï+êj…ÒÜàR²,;vLÿ¤–ÀNÑU¸=a ãÞð`9½üY„.°›ÓQViØEè2½/F°¿k}ûî“ó¬?ï;=|¤´¿ ¨Ã¶`"pyÚ1|{GÆ8°¹ \’gí²þ÷‚E*L¶ÙÝÚ÷ðŒân:ªjÔPzç³N3Wž‹µÊñR•mÜâ³Mâ«îÊ»DM¿ß‡Ù#‹IA7Œq{0ïÁ­zÌ­Ÿ–ÐùP<Ú£ZÒüUb¯šÜi=ç è%EÎOñç‡s¬ž¥;¾¹I4Ϻ„áòº­G °óHS¸ÇKU_¦w™w¶ô5›Ööƒ‡Q0áq2’¢]=˜µER©0ÌϘ1lÚ !̆sÔžxKG-|¢ ü¬ewAñký­ ®;„Ð$!5Pd¹¯ãH0Sy9=OP”߸–Ì™¬ÐÄi[iO|SEH§YÄu9¼ÿ‰ \’gí ž§ Ñ=ˆ3AhÿhÝì`DæèÚÐÛÊRPì84aJe›ÔFpIЃ5ZP±A*B¤Rgu~±Þ Íjí²×YÊXìíŠkè-fßnNßi {£ö.î™¶F'†Á…L{[luvÝ·Ê>_$ëskÖ÷ºÜnHpƒö²—ö`õqo)p*F=j‰aº—OâéüáüÂYÞÝwW †S®ðÏ2éÑ|Õ 6Ä}•te\vXK¹…|& qèÃÊò D‘‚\Óòè8ŽÛî )ÜkƒÙå"+BÇéÖ.9 KÒZRÐ*Aô–Xâ”eÃIÁuÍy³ÍV§P(Œå©4©h]¹ÈmÑ\L¼C'¡ÿuËù® Ž¨¤ÈûkM"ø€§ù¢>ã! Ù³D½† q–KE—žÊ1ÑJFŒ¶4’RŽEÔ]£Oø|v,NG´w|«bþO˜†ÚäSK´#Test Expiration II ‰A+ W]  €\˜ú  |°}m,œ|qÄÿiÏ‚®’‰Q.|öi‡•ôÉ|T\îqAù”tÛå$—3Øé‹0¿Èk–KžŸ-µbî²ÑùГoǹ_©j§6ZåK=¼â–­JE5ßc=®‹I-),°†tBjŦЮT /ï²¹—+sÈÊIæžTÍö¾¦O ± %4±W+ Ðàe3µ?'–ØÞçï½Â=yÊ )%ÊUy<µâpÇ<媞Vú)!ßK!!üP”ðÞ°Q©´±"\AZ…3ж[23[_îì!{ge o±ÈÉù!ƒ¾dr(S¿®Ìó!^uÝ ºHjÈ_ó57ãË=áû”°[v]°´"Test Expiration I ‰>(\˜æ¢ W]  € |°}m,œ|ö ÀkäúáꤽMQpØv³VKž”ÜÈYüg¹/#ª½*p6j&ÏMé7Ç)“µ@È€l®Š~8ZÙ2HüÌt™SãÆÐf”¬þOyó_mÒÀ3Pž"ŽÑ¿ÏÉLš‘ÐQòÀDŠ`ááÆl†¿SسD1_ÈvV9±ð[Ðô¼ò67—J9?EÒNO±LiªËÝ€ Cî±E–T›Í>Ó)WÇ“Ål¼#ëuºÑ˜Ê*ú9ÀJÿ:­‹œ©¹*¸Ýà‰S‰>(\˜ú! W]  € |°}m,œ|ÓÖ‰1ì=ˆÃói®Øýœj[ÐHè3Iy{W5gzŠ+,¼s<èºdÕ¾ùk€,B”–<°¦®9«=tw»+ ˜–f»úIt#)þÛÍ4e “ïëc#h߯ ªúû'¨ÖŠgÀMY–òå¯Eœà9›ˆ£ÿÞaÐGfŸA€Gûï£dÎHøHÕæ°]øý3kÛXìòá!MGíK«dÒîÒÞi–çuA{UH.´QÖgèÒ·Å•T[`;X^ó)TQÌÂëí¤Pƒøýn'‚/%YXôz´)ôÊÔÔ÷— zà㱊î{¼µ1ÙÅ-@^>Š)ûéÎP°¹ \˜Í”º7§Û”ìŠfþƒrÜ=³˜Ú ™C”ŒèÎj¬ª nçN4$Ó6v,qþ°Š €/RÁÝôªýé8ä¬â¤_›Ø|å[T”KÙv•]Þ~­ZÞI¨yÏxÜ0¿¨gæ§ ñ3\͆MÎ~eô¯êRŤ¾3}Å„Û ™K¸Ï g˜=¢ÞÃø/²5¯œ£« p%°—ÖAS>•Å\ˆ¨iѦdÿ‡WiXG9ûŠå'… ?wͶ™ àe¼&Ž‚ÙYñ#лü,"WßvÅÇÔp:+§ÄëûÎTáó¸)‘™ ʱ-=‚é™àŽJî,£ó}›÷"À£ß5ß*£‘‰% \˜×ú ­[ |°}m,œ|\ÿQ 3‰.ùB†¬ŸLuÿóçiøåõF¥Ç—óE;Û~æa­R.WßÛ®FÜ×+­¬ðÛ¹ˆÚ ì«*°ìÄT/5¶Ú½€°`m„˜•Õ³Øt\šJÕÅñW„DMASE~ ŒÔ7¸)9ˆÈ6£Làp¼éü8½Øx.êm’T·á¨Ïa@̙ũ´tµZàÙkJ„Ä*"Do67ÛzàCyTNß»]L⮉9sc„©É\e<¡•ÃþìrszÒ“¥ÂcÿÚ¾ÿƒŠwÕàßöYæŒÝ’ ³t©–ŽÆ´Ü?|'añ¶œ²a?ÕÖ¡xgªžµó OÎÛ\«ó°¹ \™:¶jppðš@òâ× Úm¼Înüûm d ™{êG;:s+µû »«`ŽmÐÁ «W×lzË“ì•è ¼ŸXiÙ¤6_ùµ¬$þW]é’n $À4úïĶtÓšÃà(î  ¦8È•Pâ}jÝzGέND1D˜—ñ*ŽË5婘aŒ‘•ƒ^UÊ™>Gœ méé{„À»3ø”u5úÞV´HeQûPŽÑnE:媖;¾[³¾DY9q¶ùÞ„\vǤjñ3½föá0Ãú©Ì4ðx¨…}2©×ÇPЖ=¬”Í¡@‡ ¾»þ)UEõ‰> \™:) |°}m,œ|À] \™: Í ÓÕ¼¶ï]`–O”žæìóý"S4Q}ªjôéñaøZà®|­ß|©è&‡'ÃÁQÇÿï‘Û„/V ÆŸÒ°5)K÷0~…Ëçõ^fô E¼8u·‡ÐßËž5®¥Mšupošû9´(R‡#ú²GmÄGòµBr¾‘)ñiÇÖ­“÷ؽ«oÏøZÆ™¶ènðFwäh[2ƒ,ºÑÇUÈ?ª¯Rñ¿õ¾ÙÏ”õšJW=Ýš¢›÷ú¨OÚAdu¿#H†a Ò/-¥cø\)cý^R$îIs}›ÜP‚ §êýVæ,ZE‹±D ˆ :7só8+I@JZz¼aœ†îû—¸XÅP1$0éñ))¢B±è󌧲Yñlþ¡ñÃh¨´Ár@kE©gÄôòyO Ñ:zà$¿>Hv Ö»à¾óæ‡!W ØH¸Œæal$”©”~ÿ#¥² „{Ûâh“Å ^'¸Ö¯]ÅÆš&°åŠŒ ©©ãeŠ7°*äç½ö—R>—®”‰sÜñÌê?Ç$õÀTÍ)3•AÚHÉÂÕŽ²<†Žñ`æá‘ÇÓsAA&\zß¶ê÷ÿfOVÝíq,C(YLff,˜™?¨úŸô"ôÙ†c^¼5^:`Æ7«\‘ZýTù±ìØVÙÃÿ œ]ÄòLð.Í…1ùRLFP·&Yã°././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/rsa/random_seed0000644000076500000240000000113000000000000024112 0ustar00lukpstaff00000000000000;T->‡§IVåwWéŠôm:Yq#N¯! ¥“üz)‡Â}ìV)sÝÇiÐéB u6ÿúx<5=¯ù5”K’úõò¸ð$Ë/c•a ôVüG)³×O (¹.¥6Ë; ø[F ÞÞ…Í5–^Î5¾ú%n³rý áÒ¥í4i>U,éVß3/f‡Úy@×âÇþ|]•ÛI'£Z¸ögÄø:>²¯Éx[¿ïIâH¯©*®ÊMÄÛj³T%+n´˜&zü$BÝ™„‚>s®T)JÃÑ’GN¿QXõÀ}bh²ÀÅÝÓ'b*ìæ?ó—†°\ËÉÜ3Û—Úà\cݧ '±_<\0‰Gá°fu´ñ 0ñ[èJáV.‘ ÂÔ)² PV3|¼—ü‹]µpJ!Ûc]²~ÖŠ@™X·~?™° àŽé ›™±À†Pyë”v ¾­$ÄùqÄ0j“rYeóÿ-É€n¯Wð&Œ«‹§X-.•å%©Ü~Ó•hÄUhב{çÉTÕaÍB‘çz{Ýiã¬/‘ÞUþ?NsGè€âüL_|Ç\ã:< R8M`0—Åa…ó¼æp;þ%5Pm¯6íËê—W×5üli%ù-D±£c^›‰:‡J)çz9‚½%OVo.6i5²:ónækÂÊšÝ:Ò:üº—Є†‘˜ä)Â%íUGÞ¼×Çøè9-S„ø½©GY‡_¶ù2ċÇÎ6v¢M(IhX°V4B>‘1‘-d3——²ûÀà£A¼ÌO ïÙ!ìáA_RUf3aÒx{ôš!>à­­³ñ~Ù*=F7<¤îÕÄÚ$¡@5®®IŽ“æåZáöT^±iîMd)íIÿ¹ã(¼[›€Ž‚ÑDíY¯eýä£@…ííý£'Õ6`äVKå”Zý!Ëù´³Öì=N{”ˆ?A¦½!P“TÜw€Ê&ñû…$0(LqÆ·ÎÖnØÏž á€Ëè4[M’\«¼ ô?›3‡)ªز¿G7²L¿ŒÖNŽ:`&ŒÑä|h“X¶D*µš:B„–öj) œ©zq 3C&jñôÍ,ÈVý•wÑ—ÞÕçrÑñ# 4UýÚimgà@XcÚkL©¦<´†¤—q ué=ÐÖxqÔX ïrM*)8·¿F'Œ'ªö‡ÈÏÍȃŒ5lXþ)X–qЩ¬SK«÷±wtÀ.ÝEaÈ]¢I`#vyL•Æ«nV¦b†3퇻þ1 )çvþ`ÁAdÑ ¶öè½\D¢…¡¾m‹ˆ¢[æ~ùþ@-ˆ&ùë/5(öa©œˆ j_p“úD’MD‰ÎH–Fï9y¹<þe@Z̈ò3Ÿ¨‰8"Z   € û?S~ Š ÿniZ§"ëŒKGÖÓŠ¡Ûß„jÇ«]5޳ŠÑé¹g7Ïñ+­diï/] ñÚçÂl©A»Ê:h'÷„…pÂõ«¸ —Ò«'˜Š,5“ ?áwÕ²*ÙÊh×qLÌùÊëDÆþ£X öíÒˆ¦`U÷Äœ=‰a"–ãÔí¥Ä¼ÅüEˆ$Sÿ—K`±XG¹›â}%ôŸÒ¡á@h…ðŸt›Ëɘ8È?Òº0ä“z²WÁô9¦§IqHδ e±~_q ñW¡g}¸Ç Ã»~w£èÿz>v+¨¼wQTï'VÆ?¯qåûÁ~3@³vÙïä-:T zvåfŒÏ?HGRWÔ›¬ùÿ°˜Z ¤(kþ²¾éóm™Sï/ƒ&_+€ $˜u΄˜öðŠm~¡MÎÌ‚;qçÙ=nË)Oˆ€îp>§<´]‡@"q OKlßyôD8Ǹ¸´–=Càô ¾®çžHýùþËfvÚ{¡z]ÿ¨Šü‰„t=:8y¢c„Ìá#PâPhì8Ôã×Qv¶‚Ê¡-MødÜYñùŽì^^"ЫlÃ+ñÝpÏL,§Æªä ùMμ/ Ö2X!èÍÅOBÖ>¼6§&€¯¬NQ_˺"9¾ •«©Á}}p ßǬ–SzÕ ÃS=7í1~ήÞS#&ÿô•ÊŽ ñøF¯,’y¾G§u¸?°˜Z”2ÔÁRü5¦ÓÁèÀÞÎ  2DfáN¥]r#«\C9>ëÊs̱-DÃyúãö;&= y8‚§3.ðo(¤¹®w]-(‘gèl-ñ¹©á'¬²hh…Vì°„’Ðqðl¯ˆÎ©Uq5CIØïÿ°ÒYú㿬šµ¬ÖòoGpþ/0üУÅOF:0”ªe$ã'¦%á$uÚ$³µ6ßa°öâ•G9¸‚Œaføáv‚>ˆ~eú ì²¥Ö÷ã³)¤Cá"+›bPÛ E§ ˆûˆÔ6Ôsö4H•>‰TÇ¥sKöNbtá–Û0t§Î6– ªËþ+¼Å“–­Ó·vØ\ÂK¿Œ3”nËCÁƒãq8QùeÛºüÒw¼ÊÉÖyâ™û žÊ=ùQ:~vß#üÓ-B‘3…É‚ºŠï"OzN"ÔITMªî—$íºS3ê2¸ŽÆÈ^fX¹Ž! x^ÿDÈ›m{ñ<‚ÿg¿^¶~²aÉ‚®¾ÖŒ|–pP-´Ÿñ1êUQ½¢Áðb\öýÂW¿‡¤ƒì¢áí©÷ædò¤ ŠI^˜˜WÍ ª€õíÙnAf;éþß î\13 ;³@4™…E‚n²N& ë™…ÕS1@ÌÆ@ô?;j`’týb7öhy,L¾Ðktå!;-†øÍw#iÜJŠeg’Çr ÷€&Ê~‚Za­~c*bvÂÍzåÀå°"óìÍÕ*»îä’‹ä‰\<#´oÛð “Þ–h•°£ˆxhÄ_Û ?±–MGñL1ÙÿþÑ×*D0ÜlÖMYz´»2*r·‰ä5L›'ÎÙ±7v¤a—$4"GÛ|Ìÿà©cº ~c–´Ýä°³Œ±\¯Î-éØ@øÂg#ÒPºb³Í¹‹÷ÿÜM£Í: (ãÐAÿ“ˆá.þ²5¶Í45jºMÄmÛÓRÍZ™ð&{¼Ôm@A¿qb¦•PʰìŸ"l‹±ydèwÃ~­´†ÅNcI•å¤ì¸?â=ƒj¬ËnjäÏθ¡º™_Æ:Àºøz ^ùea¼dl-?v¹vTO•*17ÔÒ=ò£óº3„GÔ‘ÃÉd¬Ü¼edQb LÊ€ä13“Q‡x> Gù¬¼êå½]‡t[Ó~%jMÔF‚›$Ø5C ,YälCû‰> Z”2Ô) û?S~ ŠÀ] Z”2Ô ›éßQ1Ù$éЫá™Ë¿a…R8ÀáDÖk²£Pt?éMM6xÆi¬Wɇ }a~Å£ÓhM·ôÖÚóåï©y©v²á$p¡Q÷ßÛ­^£ÑîÁ(ÿ•‘”?WÇpñÕ²gŸŸJ“ Hwåk–ŠXJÒÔ ÖÍö¤Ö>¿S5Ý Žm/ÌÏ–­œUK½ª Æ{ÔMm(™r¤ã‚àÎ\,¹M1“âøÐ6OæÖßJÅÚ•¥FH"\1”'er¿(”ßÄHiÅ4>›v}C$ÂÏÑ\H<6,ÚŒúÛ Þ¿ÑÐ1îûÐ ú9è|ée})$mÇ1„BšH¿­Ñ Ÿ¿=&F Æ­)þ9è<«©YmЉüšî²lƒø#˱µ“Ô/nzwán©÷ÓY*A£„ qNÊÔúœDÂ~J«/>Ø|è­Müð“›Î' Á櫯ìõ‡fﶪhµ§šIFžáÔÅûWáùIfÂÊ Ç^\2Ê « $R;x K^Q¥d÷19.U⧇µ"Ž·€w]#]dÖ´U-¢b%^l°³^€ ÿmé§{òð9º¹Kúø|¢…»€ÓIr¹’¤ÕûfÊæà¬qÉ ¡=»jNîïHs'f%YœAz³øÑ÷–ZîX€št³W!¸»w¢ëæ—;Š‚…M©Cî#õ»>³I°=Z—¨ÌjŠwCÇŠ ¥þ/áW©Òn]%º}1¡i¦ÕúB»ógô±Ó”]T2ð,5w>˜q†Lâ’Eh(,seÕ¹QÍQŒFù°ßÎf¬ùèZÔp@Fc`÷HõÖÀ-fA“KˆÀ0˜yDð¦î±Jp•%âÆ_û†5ï¬YþË~Ör¨N-óÍ&ƒ8§€‰h}ÿnÈvx0W«ñ2ÓË$–h¶9“ø‚Ÿ¡/u"JZ(ïŒHJ ®”Ñ]—¡l\:kX‘ó'";‹²z¡E¥¹Kq”¾ÂưÉIxª¿Cè*!vHþh$dǵ9<Ñ/K\®™VJ¸×öFÅýMÚÕ¸ŸrÁR"3ÿWý¥k\EÏÆ~ùSGÍ$6kXÄ-D êá1ê»gKC!ÁFï8óåö/°+ô¸.åHb0&-„]úÌ;­Ð­ù¥ÝRaHøtõÎ^2‹[ž· ]ÿù‹P0Ft°€‹-¢‡;¢ƒg;­ØÍ¾›|’½ºÇcÿs;€W!!Ñ©úú~¾¨14ÿæ!š;‡l›þþÊÛ‰RÊ=cdz ƒo€pHЈùEövVåW×j·3Uâs¬ÿÐ_ʛɭXïÈ"<íCkx‘ž—H£ÍÛŸO®ÆÅpQ¯17¨Q§©ìnpuî‚©ª§JônÛ—çüL ®n’TÉ[Và™¾,‹Ã‘•6†!†äkØV+WúË®?C|µã[Ó0Û¼C×Jª[‰ Z—¨ û?S~ ŠQ¸¸N—'+Jì@% +Ö|R50ýuŒGÐØ¾9//¢L#9åÂ0|…¿[ ‹Pßen ª\i3>¨P_Ù}¤ zUÖ¡µ9°Ý~L­õÔ^}N›<ö_ÙsüOWC¾/Ö¯ ‹ZeùÕÉIæÐ^Ù™U· %Ñ­düÁÔ²äYRf+9dùxð5”ñö~12M¤÷ðPòاɲÙíJ¶Ô oí¨ò±¶ ’P7GNªá…hÏ.•<ãS:,æ(€ºÙσlµúÉ<»/¹­cB ø™û©$¡1W¨Ù™\•$nmFË–ÆÉ¥C&–IXÁ¹¥Äu¤!/qcÑ p»T§º°•˜Z!¢6é­9®2½OÌA ù—‰íjÑÅÁ(À*Ý"rÜ"1‹d¾ÉùF{iI±ŸÂéŒäZÔ] ÖÅÚ{Þ8 -ž>i|è¸7; W4-Ù!Ö4¸sòXõÁUYµ)!úK·ô‚ìC¡È\3…½R í½Ák%$¦Jìó*ÅimÔî©uá¶Å¯N¦œ¦E3B$2‡0YG“ˆUg»|ÿ¬öë_ýÆ@è˜å™W›!±^I\!Åý÷—NpVÍdþ„òË”müRP1)žbu“no”d§5½NÜŽ Þ?屿m;Áí™;†]Ëš+.òß0Ë ´ÀÞ¨êñ•ÿj!ù'Ü‚Ã%):m¨¯ ©J¬Ð¥rR&öÛE6^ynŽíÛÞ+|gû×þä)ÝSt«ÈöËÇZü;%X„—ĉ„î@Àƒ~ýè=Ýx¦NÛ§¿•¦K&åÔ<7mÆf6£ )¤œ[Æ›)÷zO!›·Q&µ?u0Û|¹ãPdâo8Î40ÀÈ­M]—×/êëÑø ãÕ¿£EbÖfe¨&ʰsg—:{âsŽ ctéò ÞØ<ñÙÝFMa f„ò§ûäO¡¾\@n±^Ä£¤õ‰Tw— ‘Ú<¦9“cwØ" 2­W†òºÿ‰éààÿ$ù¬Ÿ[Â8̰Ôm>Øx´ÿ}†c†¶;éaXÁà˜?ãß.dÊG¤•àRÈ7ë=á´Don Joan ‰8"Z!¢6  € [ Ð.vŒCvy• JŠ‹yž#A­a­pËt<&cý¨û¯p¿~JõÃ}ÔQjTCÛýŒk[ýîX_a‘ÁŽnxbM›¿³öÁ÷–"ï>¦åƒâW„£r¹”¶—T@]ÖæØ¾XÞWO(“¨Ä’ßÏ4—­ŸëŸö 3©„"}FåǸ¦Ð?˜8ê‡\?Ç¢L¾)qS‚÷°¸Hnô—«ñæY£ò,Ûø€”¡S1mN·ã–þØÎ‹ÞÊAòîÏ?èáô'¡Q¦ó²½±÷ yõ¨“.õ¡; …‘|Ð¥¢ŽÆé½k== Uä˜Ç‰“é3jX¶©ÊNÂzØä°˜Z!¢6´ƒÆ;;š¦Í×ÒÞN8¯ÀJ¼’W?†ÀÃEØ P*ÔRðˆÏ¥1éã<^×à ÿ|ÆUÙü’€/œÈ8ð–êQÞ=£³îÉ{Ïÿ£Õ}.CÃýqV†¶e’RÖ%…î„!ˆ>Õ ˆõ¯fÓÝÂyiµÜæS<‚ÿåÚzë{iÍà»^Ž¡•^ÔÊj ¨ßy¬’Ôòe i†òîd–¾3âÕëþÓåvhqbSZY·#„zs m¿÷­ÉçÓ9º|¾­“„„¿T9ŽL·áÑV·^4°DÝþäˆ$ƒó÷où÷U©`óoÓ2`"ÜD?ÅÌÅ05ú*Ц¨4:9û´þ•†ˆ5mßZÈÃ*µR—¾/ «xµvÓKã{eøyr¦2[ð4›·,¶±ï •€šfWÖ—\¬`ò!9ExƒïúÜéÿ™ˆÿBK ÙÊÙÔ%Âz +q›»Ô¼ý.%¯â×N#P¡áðCF•¾ë(WÑMïÖoB5N<ý΢T(®šÏ6÷ž­]’œ4_K1¯f¶|oÛ8E:­ž£ ÞjŒ¾ä=ÈdžÅÿÉÙÓ<ràœ$:z‹†é3ë‹L.÷Ÿ·ú6ÊzÜ|WÕÀr—Áij èäs2¯@΄'!hA|d(­!S¸ÅxJ³uF¾õŽ%)šÂà¦eõà"oTìÊyhmIø»ž³RLw¥=ìéEÒ ¾ÞMs1kí[:11tþ Í#2~{ì …´LÓ(€Œ@¦ú%,#ÔvØ›þ(J„€iíS¹ &Ûf 3“’æ–m@âC%®6q{‘Û)­–‹j`s5êóÐ…y]ÌyU­H×4õŸ-<ñp3ñ¬É‰¹‰ÚèäY„V6×a¤)ºûZ>Dë3•µ»]åʸq~ïMÍ× ’CEÊ ™¤ÔðÈÍšÄcñF!B“Õº¨›§ØÓ›e5f÷à¹,<Þ~7ýˆýFB Ì@þÑgŸ¹ËÍ{iïà>mÑñxÚ‚³O.m°øB¹yãå„;rD쵘¶'™ zë”^†4ž`¶NÛ_§Û2ö \ ¬öI•Ãúýs<€¢å¯m©·ì‰Ò,fÁ\ú{|ZnØ>?»[i¾÷øÖ#™„UV&˜æË”w•{ýX=@ܨlT^TAß=þ‰ Z!¢6 [ Ð.vŒCKPÙ­ˆm´Àâ0À ç2"4Ʀ?§v­”®ñÜÆ¾† }ÛÉ‹ct.k¶vÕº°WÇ@Œ¨¸3ú$?]Oä«DcV§c•Ø»#¦?sÅw.Û1ßó"k„ã>ŽCS[‰FØ»R¯ë†G‹€ø9u²ëí.˜X±”³bÄñ.•Ô‚îh 3ÅÝUÅÚ$J# fΰüÕ•8ðEéú%Y¼'¹tÕ:Qä®ûˆ¤œ²“§‰F¬›˜×Þ%.rT¡Zĸ)¥ÅÎ@MLéŒz㹩ٙÓw¯ƒr·’,ùœ¥5… n5½ºíC–Õýa8K¶\ΰ•˜Z!¢¯ÉE¹K¼PJav›¹’â¹$2ÉŒ´‘ƒ–Úß1­™(ë!å+¡âÔò'7±ìãm48c5à–UÌ; ”‡Cö¦©˜‹½ïG€Lêä>±èñ¡ÅX•g¸€,Ÿ3À.8:`'•¥®ÀPr,Ç&IþКœæ_õùs¦¢<;Bí~úÁ=÷rtǾ7P·#@‡éÔu×½“² O\«Xd½ùLA~}=š Hª¨§Ê¬ô¬zþ41ªNýÚC jj^àæèRRW·Wo9F°Ó*aP  ìô—DQ†o—×,a|±(—½Ôqž´ŠïÛ_O« ½3°>»¼Ýý¬%D¡sÑr¹N9)'_„Í^ûn,˜á'žÍ ”Û:,û^Õ-Æ1G0:®lÓ/†2 "6ff!à£ÑÖ˜ðó.Y éQQ‘Õ|xeÒFðTY¹µ>'!6Âí )¡ŸÌ£S@ˤls $U+ʱ™(¾å Z ÝŸŽŠ_Q}T -P½œ ¤Z`tÛù ›&ÃÅÈNðŠJÑk+ùÏ’:ë#Çö[sM³ªn¦i=@x\„¦#Ðd#Úý¸ŽlžSïÙ'9¥_ñÓ's9‡‚C1F†1Èü,tœy¡EÛ@ Æ¡h‹}…~WÙ?p»ŒPÝ2kÒm¤˜ßZÇv‰*VFÃÕ r§Nÿx-LZÌ-ˆ$Þ*Ù"é™FÊw¤´ºôê'‰'Wz&j…¡ÄZRZ°h]€Žó‹æ ÜÛ÷·% )—ní`Ú²ñë³.û6ÒIQ} “c£,Ÿ¥ãAìdã­|ÚJô"{Õ„ªA/_©ò lV~5æ±}”œw|­ê$qå¹å¢p8èBw±ÖOFŠ(³›™ò;¹ÿÃüªAɬ5#öRs¶©Ëe%ÕÖpT9£@Ì4ì*𒼯zÝÁà#ý)G:‡s7€°iS†7¸.U¶’ŒâlžèóeŸ›½²< Ø÷çDv9< Ø)¹ ÿkø¡[HF—+¸Ñ‹âÁò.½ Ää—Ñ¡üPÛ.?Jj»B •tíªÈnÂã-Õ+(KøEßé› ÿç\2'†+öæ–Ý‹•Ã]Ù¤ò¿„tœGDéKÑMœW^òÇRAGàóæ´¾jÅéÒËhG*y*Uœ4*ë51+ v†ÔB29´Dodo Jojo ‰8"Z!¢¯  € V0‰²…ÚXŒ˜lY÷¬ùð¨ó¸,yZËS'q¿ÈÙ'NŒëš¦pó²oâ4DyÎd•¶Zm?æÏÌûß¹´ $›²‘íðrc±Õh&ÓVàuæsú‹,i­ÿ¸ɹ$ï&m5ð-|Ì3èó–´­:¶ÎÃhaýÏå`nGZ„›©ý|8Z2¥wfAµ¹v¥£nž˜õÿMM|:žíà_Ò¨‚ÌÕpÎæÃý!°õò­f´j$Áo«OÇ|.íGí×F¿#)W0¤,è'Í:hD\ßžÓÿ«›Ð.so6¡é<`éú­ö× '¬ƒ2Ö‰w†Ð id· –‹.oâÀ‚xM¤-°˜Z!¢¯ØuµJh—AßçÊ!X7Ú™Y4é˜çî.0l7 vãxÑþ¸{jÈ»±!Wˆ.¢é²p9˜6´gÛÅ%)ðÌåf¶K×3‰ÿ÷â’&£QˆzÚÓÿ–·ÙM$îGMsÕõöFÎÿŠpo,Ïü×[›Â<¨‹6±öìÓ¨uÊ_Š[소BuÔ& g(SËæég8È£¸kH0*û ëÈ_™‚éóµ¤;Ä“UNTå5°k';ð\¼â3,·Dõèž\T[tB­%βYÆ ³z‰8§+qè¿õǵ&†€!r˜Å%™>kUûr¹gä`êR¦=ü ?Ê(q™L…ÏbnœuÐý'ßH@QlûÒ§ ^¸No?̘x í3N"gBqìOßN ð#ŒtÐØ=`Tr{t$€)9¿ò-HÌ?x ÿa[yó“²[–,¿SÝ¢‹áIyª²A!‡Ù ¬~4/‹ÝCªIç vuQ°áŠÌs§ßþ’·”μ!UÌÉ!Ë‚„q[cí™ÕM Æ)f‘ˆvêÚ’ž°}”„È,¶Špž¿ÏgØG'ù Ùð«³Æ]Ÿ++4•Q3<—,u UYݯ÷ë¨f4šçØè¶/J>]ÙÊÐ^S&˜¤ØÝ–ÒÑÛôh½Ò)P¹°Ù Ǻs£ ™U µæÞ|íqTžä¾À"òÆ7Z}èß—-¤‘NLÓkôP{­ÇƒÉ6-ÑQÎÜ5áaÀÀŸY p!ÖÆ¾z™j¤UKÑçìÙ«! Æ ênï\¹ð”ø§ò   0àÃõ¨üþÜ3K?üâñ={Ø>ÇÇÍnÁ($Û&5…µ!Þð +»™)[¡FN·b9À0ìðÖjÃ\,އŒöæD&ô}¶³PÛD³ØÓÑï?*†ë`·T“^ri)ÄÇôi– `é£p^‡ƒ‰ï­ÜÙa‡ÉzÊû ŽÜX"…|›ƒÏðf8Üá‚ç[Õ0޵ÏqP¼õrLµ•ÿ* ¶K5<þóÐo?!šùK3©š§®9O™Ö#üña>+MƒƒÂZë…`ŒÅý†pÞ_ÛqÓtqùBöÍ1ò—n{%fª¸ ÒI¬SL­¢è¢î“ZðÛÝ8Öy‹ž:²‰ Z!¢¯ V0‰²…ÚXëƒÿSmswå‚«[,X}7™ã51eœd"ûV& ŸWŒ™,·j™êja@²QH8ù=¬üL¾•1:õ ?“ÕH=ªK³±*Xê“æs±ÊèN'”©ÚªÌœ&2iMlÝ—´—©yÕÀióå3õÎÞoë¼H¡Ú ôâ®Á&¡…@ÆGìŒ9>ßos‚ØKø´-ë°(¤®]NC‹~°èîfý0þ´¹È1÷ãmúdžš’œ(…¹Pù+5³Œ¨(qYÊþ þ_‚lüÚ9õÔ¶IôÕñ¬ÞŽLi{¡ëøÑCв>O‘0¹€ô!ï½xì©ô¼´`C+¨Å¶eZ2İ•˜\E¥óâ(Mb0œï]Í¡²ŒmçgWÈ„|䮦¤öøFÎ#±6Y3º†:8GŒ#º]U´1x2‹i~'Æ·`¼Q6É/š(|‘»Bõ+Žš7óbÇ(°c¤ÚHËXI‘J… ®¾ÄYê2<Â\0ÌúXHÝ, õ#G’Œ->ã I¥g̵‚+öXóöV)f2Á=G›Fª¾ Ã>Ú@áš1ïlèLøuÌ‹§¿åíkþ>6 Ƴt† =z¬çy4–DN²˜›Ü ¯°œbî²íÀÄ{¡J¦¡ 1âûÞ‹…~òtȦ[¥~Ø­vžƒ»l…ËlB)QˆoÿhЧ4)–:3ÉvÅY‘æÜŸ#€/ÌØªŠ¡ªäÇ´1æ F ç•4œera5Å7Dµžþkà€Ãù/ È‹ûR¿?í˜K†YpŽE.äƒiÚòhT@ƒ ‡8Yàâx@8¾8çûß/éŽÃ•Ó< Æ/ÒL4ÒûiýöÞÐb7ÉÎa”ÅÝyZt]øðL €&ââµÉq³â¿h¶–äqC¿õÆë5ûfSrÚþ¦î*„å‘D”($¡t§#1Ê AºË\ØiضyLà G«á(*ûµöG¹.€"½w‹Œö‰8"\E¥ó  € ÒÉþ“f™—è–­Þs­òdf¹me¡ÑµIé2º`µûÏlO—÷X½cZwîH: k{Î{ªi`‘Øÿû²oÈþç÷ÅètÄ…ð£uÏ‹ùÂ@+H•£õ ¹ zfsóÈãR¯‰Œð}Ú–Ò!xa³cëŸâÐwüxå oJx®7€à`!¼ÃÎý ¼sóY.Eص%nŸ†YV¹q(¤ln¡+\ë.WKôo«¡Ç?=‰/ÉÅ{ù½§B½ guÒBüÖÍ 6¬s —1ÃÖ<ÖK ¢`Ú@wˆÙaDÈû×§!Ò¦žP÷ÆÖ˜«:åcb<þ6†´é‹RpöŒªxLް˜\E¥óÅü½‘Øã Ö°r` O7ææ†Zc Ãm·«ÅèHõàý]ppâÉÐ>Œ¯Â=Î6:Ç_TÊ‹ö$‡ÄÿhðÂsHxà+.Þ®ú5“¶ÛF•h•Â1!òíÁ%Ó­mµSç=yñ®2ì4Üø”JwÛˆûØ-}Ñ!w¶ Ŭ伵žXQ†ª÷‡_µô±“Ê\h&¹Î(v² 10Úö× _~‘—üª™àá"©|œ$Ú€MZÝÁ׈cÍí# ÂQ„l%!ùr]/Km“W¡ êÝŠ£))è”I 'YžŸQ&ÑŒé(SÍ+KFDƒübHÁUùµzÁ·æ4€‚wjbïmu7,žjUõòqbh°øÎ0ÇVx#§5ù å$¦ÕUáE©vx²­I #_ ¡Ӥ±^Ê[phº"«ŒÏþ½ç8gÿS5XFàIŸQʼn´J^³~ÑÇà«X‘aP“‹¾8}fH¢s3Àëê‘]~^;Ž4µÞàòIÛ‚h@4Ï@î,Wk:¡õD²¼x¯]ÑÉøx&Àù´­m‡_?x‚‰Y¤±Îk\\”)íyâsýÍp¿÷zo¶Ð«L‘íDÝLÚJãõÌLŒÜª—†Þ?—GFË]¯[Ím××Éb¬ð›§‘ÉýHkS‡"_ßëÚ+LÑ€Û?.©E3=&;øølè'(ª¸ÉB&6ê‡tKÚ¿‚Q%"xè}tçS‘"X Y\-úQ/iâRÈe‘<Ò6ÀMÈx­‰g-p[Æ©éQæû‚Rœa±_ìÀ¬ÑóP®•û¾©S¤BUlUdÂßO•»úí€v“2È'ÞÜT„ÊŠMt®Ã(A??Fk~ìÐŒkËS>‚•‡yo,±MCÆ»'²Âš¯Ù˜w¶Ëq~dÍWÇæC׈žóøeÁ‹õ‘P6ÉRzdóKÙc÷>¼(9ÖÔÞ¡q+“û2ýL F°æÖ´ZJ íÀ³¤©æûÙÿB÷ßziRc{rÖ'e_+ÑŸT4‡øºðS¾»V‚ýí[럭Q5HŽq{æZ-ÏŠîig/äW£âPääºS'UFŒýغH'v«³  ùö‹T`ö™6oá¿P!ôÿü ³ÀËӪ먑.xlT”X´3‘»-‚&²zäE§‰ \E¥ó ÒÉþ“f™µ-ÿbælѼ½´‚þ¾¦¬îÞ“¬°:Õ­í6»O7!új’ìZ¼±Y©¸€ HµÂ©‚ —qÍ¥ÊÚȇæÜîML¿AâìŠt4ö&Ÿ±a(ß‹…SоÓÄHÄ^4!fŒi·Ž›€ØÎL˜y2|ùq6 ›`ÓŒ@9 ýx=±AM¸¨Æ;^ŒÉÎG”A‰bÔQô>Ñø!$(š§î¨é€ÕgÊÀÓîÉB”FªÎÍ)ÜÖ– ]Â’üvm÷ûҾĄ7w-ò§üw¿Nc¤ÝÍrÈë¹Á®»ˆ."°¤qt‘ÀàZ¾Ü\cŸót¯mª‡¿‚°˜\E¦ßÛtµwøGq¦” 8àÿV9°JW çÖ¤A,’GšÎÃà–ÊaùL¼õDaõ€ÂÉ–Kâ9Ʀ³!¶ÎsŠpró‘!½ír?WÄ­€Fôi,ÞØÀÚûfgŒÌl‘Ã@ð•£Ï}´K!iºž*öh›7Á„zk0ø9!l¨nª!9Ÿñiû{JnVqä÷èŠg¾š4@Ngóxèö½£9 Œ­ò´>wöLYÕ~”8ÖÎkŠ;ÏÍeÞ •ÛQ1_ò®ûDÓ# ²È.‘4Žœ’÷B§èÃth’Ë_žHN uŒ¸8-Îg –uz unû}¨)±e Ï•…ÿb%,a6~Ñ#“üc#Gª™Eù/Ï×n†p¶mÕ^¨:b<ØÌ¯nFôŒi4z`¡D·ì¥V¢Õ~óŸŠ4‹ºWÀyGæTߨ*¼;/çìFAµ]ò,ÉJ K„TÎ!ÀnaùY‡öGØ Ö\s’E£°ËºàGxîäÛT/Ì´yŒ–¤§»«Zš 7+ÏÞ¾B,êh±ªµéáX(·\h¦ŽØ›ÙJWˆi " ÒØŽÌ(’ðmœ…oa>hC¯ALŒªDù^C 0ñÁÚ'¬l–ÿ ²»È¾AâÙŒ"(»€*~#%MQ°ÌMóµœÞöQ0ÁS¾¦³¶£SßÃuáŒâ~¢_Dr%¼ ÉRPÍÁî§ÜPC±l1[9Ȱ'¡†h÷Ä:Z;:°Œ„«ž$Ka¦…Ù ŸXa\»ë ðíŠæN§¾À†¹éò(LÐÜñGqô‡ ÷nçÝE×óȸÙä.ïáÜkÀJ’ÛsâŠÁÿhÁ›þÙJ®Á$ÃûaiÌ)§¡´$g$ü—[t@…Û•Æn)¼›½=g´ý¶9B°ûÎO½ ³OÀ¡ðÿô_³漘A1è Ôó]DKk¬Á²ó7ÊW¤H‚"ÏV›)Ð%0ñè|°ÉAI&Ž•ÀŽ;5ä¤'ú’¸ûQæMêq5œÉÁÿg·ž–¶ŸK"MËVE¼Ó»}JQí‚»š\ðr£Ü€LeË^éÔæi.‘¦ƒ±.ÓÓ¨£:c$aý,*å-ÝUL0Ò»}¥ÝWˆë =‡­‹9UÙðÁëR0>.zÞgÁð†äžðîfzãšëƒ±A )cßc/Y'ð*xÄE{Dü‰> \E¦ß) ÒÉþ“f™À] \E¦ß +%dðïYþK5ücÁÁ-³öR»ì¬¿"HÄeà°eÚtöfXj.vÃÓ¿œXù%d%4%t)»DžÜÚ t0~7‰,ÞdÜUq DlÞÚ§ +À0žkô1ëºÈ9-ã_ž2?üÐî’/EK[“è­ÇmÓÍó|Ø]Ž(%°Œåÿ”ÿñ¹˜:ÊÃÐÜÕËyí ˆxã3m"Q~U«ðŒÝpÆÅ>©©'--–› ¯†t;Å•¼D“O*kR”‘ÞÀêî†4AÉ ÐÀcmQ•?1ž‹õí¹îd‚£Ÿ”ëb‹íÕÖ„ÃÄ ‹êC’äMj{8m_×ÿu!Ò/»£áóéˆ> ]9N6ÖÒÁ !jÛ·K…+ ¥¤«…u0÷ó_.ÛhÓ¦ªxar)ŽÞLwœÍu¥D#^ïræi·Ï¹eŽì™3†ðÄáˤà¦Ö4}`šL*i}§ QqJ‘|£õÿNò-V¬E·6ŠÓ ƒÙY%…GdÈtqû7¢oNÄXu|ž¬VÝü&_&‡¾Ïù—ãEÜI¢DóƒcõX$zòìÃu@Cz„™Öu+S"‚ þÌ9îb7ÍÄ þĦ~º*-pü7­³½5@wg&Ad£GÌg×|¥RæûwoŽIôWÅ”æk÷|ÿ-BÌ~'t¹2°˜\E¨½áMw—q¢e2¥ŒHö~£þ‚xC±º³¶)ùÅÇÊHùÊÃ@ß÷Ð[·ªÁb~]*Fÿ¬ª•©­³¯(8^ýþ¹øÇ‘ ß -ã0#ˆþeøZòb8T+Ή҂)?› ×ùrj£É*ìXmÌëüÈ 'ÊÍésªìe°d¶òÐ-õ*±Àу¢¶Àˆ]Pyà« <“ tG"C°ÐšÛP`ž`Y«|ä¬3Ñ®CbÖ }0ã%›S)Ñ霩\Æ’BCI¯"ˆk¸3Î÷ÑÿZ Θ sàÚ=ZsîØÅª˜¾…€VÅ™ô¶¶±}FëSúûþ¶ë'KPèï½ÎÿâN™&—ØU”•ÚóåD¶i-êÇUäU -rIHOóåã?þ§\ÜÙâ90Šàxi³—{”JQ3aЂ57;Ëc_\a#àê`)ˆVÍAÛûQ|­&|dd1;çñ¶Š´z—”îy§&duòe{Ëí½P3‡*ú!ÿ–ãMRŽe Q;[Iû!½­l‚é¼wÂö}öú oÃP—ÍÐ@[f§uòC*v^xa''MÆW"tF v,6 ÝŸÿïÉûøÞú/X*g·“ÇG½¾ƒì¥˜h‚ÐÆJidýŒ‘÷*=Ýô)Y1ª"Mç~XC¤ŽZ`§´àtÒ×d1¬ƒ-MïúŠP@¤·>'¬6;ðˆOº?ß_&b8–ÏI—\Cß•>T$ ±µf¯z`õÔ~Ьƒ±×ÖìÝKNqÉ`€ØóÕzÞRïg#?¬OƒôGPp\Ìú@¦PÑ®{»[­2u·Ž8%¾v›„Ž<*°Axž‚u®<†X…¼NF/qÖ ÒÙ‚ûÈù…$¹W¡Äžê€X»Ñ³ƒ;Ѧ’r(¹!Eå.•ɽêd/0…Sáµib›ëÑ«®M¹®¬#·×úJ²Òÿ{ØxÚ`:LÏô0JÁ‹¤ ÄÍ¡2¥2Õ&6¨¯{rL¸YÈlB)M²{¾Ãþ_¥BݾR\¸k´ä/¦£iÞD SvÂÈÃίê«Å7Þ­EB¶öh•ŸŽk+ûÅ ¤¹Ú=áyå×ÞF*«S+ÏŒÙF á$kÕÍ8Í`zBó‰> \E¨½) ÒÉþ“f™À] \E¨½ þ­’,‘ësQlcÿ|óItJ½ÏÒ—~1>…¢¥12תûã‰d+ÙñݢёšIºZêÍÇýUíð××ý êíGíú³.øììé wOÝk»«[/0îÕ_ÐlÐBãã¿ä½RÙ‰ aÎûpäý13·‰«Šc{Š1Ðõ%dYMŽ»krvÐL¸èÛrŠ:`Mä‰n2ɾ4ºç?–É'ÖÖk£…ü¾Âj$‚ö—¹ž0Ÿ¢Ò™oÏ¿qYCo +û@!SêHµpv*ŽñSKµ*w²^}ü‡üW_ªð‚nÕO÷Ê'ø×Ñœ–¦0ç¬H¢9–(ÿ.kÿ]Š+‡~J|U-MvÛÔUWº9”œBDQtøÚÌþH7è'Ìΰœâµt[ˆ2ïý›ð㻎)¿ëWÿUt­c ¨îv9ü|µºG!ˆD ‰!´ÐÏ•i;Y?XˆexêȇäÒÉøO9øóÉ)gýg¥M0yž~ÿ2¸ì;†5 9ÛM,*Nßc/Iêµxg}rËKIkO‰™ß4a›ŽšªËÔ§Ï¢Î(€ÅŒ*¿t³O;íaT ˜ ÿ86ŒN>•½ðª‹ìS0âxoöΠ9l päSE+pP:¡äPÊa›ñ¥³Qа•˜\’gí½¡¹—òÎMŸ«ŸN©Ò ¨0µ —ÒEa¯°Ã•ËqÆxjUxE{ÿ}9Ø÷ÿX£c|ˆ9–e'•³:‚½Nºê~®_ô'Ð}„“†#ªàgÎ ”æã=B¬³zC¬ö«IDpmô8¥ 3~iÒùG®˜ùïVžaŠízj!©ÛO_×ÌÉ*ÿ¹…@}À4Ë¢$«¦dg(–9评ýâJ ~GÖ…ø§¯ÎÎ88 Æy2| ๬z}°äLJ1ض·+<ÊcÐMÁûÄ<%aò‘Ð¥oöhP\2xºèÇå‹$4™þ±™<Œö¥Y ÈèžÙ‹êøg^™·ÑóÿF^ÅÛnJð“ÑZ›‰]SU÷3Ý’QŽFÓHNáb¡LÐó³;*Íãž,ú™ÙÊ¥ÈcGûý¦i¥g¡(¤% ±ZÍqÒ’á\ê!ÌÞAb —þ0ëGJ8È?£Ð¢=F…p5UJí$ß)g\º,‰™ºÙ.k»wym.Ì$r¡éϸ<¦ü1ÿŠæ¸P^+¨±×¸6;ÓoB»ôûó‚`ª{kt:œ‚î­F=([í¦æ‡Æ2¢o†B:¯âé¤X¿î×Ù‰-;¦KjÛr k®«ÅÚœ™ï†gá«"Êt,¹îKèRLŒ‰ƒ[óg/Áobêiã=ÂzŠŠÊƒ `ñe9ÿIyÈÆ&+ýrf͹«!â&Ün4È‹e%Ø‘“ÝGkÝâŸb¤È·9Þ¾\ÙSßä ÌŒ ~7®ÞŒ‘ó-Õl·ˆ3EADÊÊôÈÄOÊ»¢®^8ù„ùÔ2Ún'”ÍöûJ$äÈnåÞà{’ð½ùžª*ºM/ФdÜjfeá¶s†׬Æ ¯4î³Z{S~õ.8¤*ƒƒõ`ùOªá’تI¨¢ ±G cJ«ØÆFl]™ç<„ —õ+æÐÉB’%ǰ3OŠ_êÞpDÿûÓ5©ÿ‚†`ý:Ó!­›GÛ ‚&é0NĈœoý +¯O ²\ÌêƒípL¯Üà%‹ ÜC z`dA†½O÷MQ”8Æa žÛžM›¾˜Éœ¼¶Å (­JŠ!E[ƒÕÕsi¸úVíJot—Óºi`1Mm[¡íLªèäuCoâ°¤œSu–&ñ QJfAWñ+‚¹²1ǵ>¼´Test Self-sig ‰8"\’gí  € ž§ Ñ=ˆ3³®Üµ@çË )ñXb*ÛF“_Aß™õ¦:ž›5I¶’`v÷Ò{[ìð[Ù¯‹ôÞVX sÃ×”Õz†¶ÛS±>7Q¦€kÓF;Ê6”Ü荒<íˆü­jÉtPª44(«–ú3òOD×Um—E‰¥gƒ±"Õ:ÚçóÃ9(ž ëü>¼ö ‹vœ‚Òr¹[ˆ5 ZæÃÈ4Î{tY"„iG”á¡`ÞSj>wSe¤¶3?XßSµ01ù“ã½ðÓqž›[sËU„FqLx¶•§ô=™MÿúIgRÆ€»ÆH·V)ç$¾òãZGFû·ÿÙ±:¯²°ÑÒ¾Ò¼ÿØÿàJFIFÿÛC       ÿÀ ¢¢ÿÄ ÿÄQ  !WÔ V•"17Q¥ÒÓ)2AXuv”–¦³´ð45aq#38B‘¡CDRr’“£¤ÿÚ?²˜ßÅýåáªÔYš%…¡Ù™ÌV`=)õU£HuiZ$‘ Úy²"ËÎF+VµlBõ6îû:wÕ± ÔÛ»ìéÜXkVÄ/Snï³§qa­[½M»¾ÎņµlBõ6îû:wÕ± ÔÛ»ìéÜXkVÄ/Snï³§qa­[½M»¾ÎņµlBõ6îû:wÕ± ÔÛ»ìéÜXkVÄ/Snï³§qa­[½M»¾ÎņµlBõ6îû:wÕ± ÔÛ»ìéÜXkVÄ/Snï³§qa­[½M»¾ÎņµlBõ6îû:wÕ± ÔÛ»ìéÜX“])}û[k̲V2«dìP«ÕØÉ.G1.¡§ä!µš RŒ‰DJ<ŒÈË<³#ñ .ÊÉåÁ|Í+ñÈQÜ?ùy»o¥Ôδ7Œfw+'”ó4¯Ç!DK.Þé¯÷«}ï]µ¨W¦–FáFALç’uFM´“ÈÈjIìñ‹ƒ`ù(­åN2%Þ5çR(+Q¥]ÉL†¹ë$äY¥KRšJUã/L¿‰‰ìÎNl(Ùfs¶·ëZ„´†·«TØi#þN2¬¿Ì|w0eÉòÊÍ·±J¤,¼iU·¢ÿ‘°U_~h~À}û¾Ân(6öÍW,¦%½Ó­ÓªðåÓawåG{ºe6òÓ\Ûl’פ²JtRd£Ï"Ú/ˆÌîVO(6 æi_ŽBˆ ‘„ŒνZtkÒ¾7ߠ؃GuEŠK&dÔÙ-¼á¨ÿ±ŽdYéü%§3N‰\fõ1ût×G+«Â­Œ£ÏnœJdªÙ·KaddJ6Òœ—-g’³tÔ’3ÉZNfb^>!/ªöžyvþòku6ËJt0‹/Q›Ñh¿žŽgñæ9胇ïv_Lh¿hnøÌîVO(6 æi_ŽBˆµØÂäkñ·Û‹kž±vQä¬:ƒ6êS²%"9ç±HId· Ìm¤Ë%™—ÛÇN2¦Þ}f]ÐÝ…S¹ì=-Ó6LUäU—y„‡&Y%%±fZg™hhÓ`ÿÞ쾘Ñ:ÐÝñ™Ü¬žPlÌÒ¿…Í2›>µR‰G¥DrTÙÏ·3 kuÕ¨’„$¾351¦˜Ž¯BÁ~¨)c¦¡«Mi#®š¹lf•¨Ô’]FZOÆY›„Ú6é$F‰ÿV3tk¹Ã¥øÞË™w÷e[ªÂs= ÜÉ1yFI饣2?³¹>ñxe™]ÿÖ½LâG÷WÖ/7Géú_¾±yº?OÒøÕõ‹ÍÑú~—ݬ^nÓô¾$5}bót~Ÿ¥ñ!«ë›£ôý/‰ _X¼Ý§é|HjúÅæèý?KâDÆæp3Šk'|Ô×itkKL¨Mݺrù¨íJmn/E JÉ)3É$fyl#1¬â³bË¡g|”Ÿ½;ßr¿{~ϧáþÃàü,¾ß6ÁÂ=èo×|áïC~»çzõß8ú¶RÔrPØ›KLµöj[qjÔyMÌ„úãÚ7ɧÐzH^ƒ‰R ÈÈŒ³#ÈÈâ+×¾¾M[ð¬Ã¯Þªr·6n㌳hc¡¦´FDÛ(B33Q欴"#3$‘#Þ†ýwÎô7ë¾qìÓ*<‘ÔŠŒj¤Fã-莥æÓ&-£’Ñ©'™Úu*mÄùÒ¤šOÄdd,?˜B[h‰ öb°ÛI$! ¢ÔBRE°‹J9°ö#® ÝÄ&,Þ'èö}ײKn1>žÃÄgâÉ™Ygü "3xe‹™pUX¹,Y³=¥¤”ÄJå¥\OÆe.Šû†|îÙêRãÈ"ÈÌÚyÍ·2̳ÑQ™g‘äb¬›Üô/† `¸¼Þç )|0kÅæ÷=KáƒX./7¹è _ Áqy½Ï@Rø`Ö ‹Íîz—à uÏã£ö¦öìM˜¯^uS*öŽ›kâS‘ΰì–Ðâ4“”œÒ£,ÈÈË=†F5¨fw+'”ó4¯Ç!DLnÚø¯>è*…W»kmT¡³3‡ T×é×Ë õ™œ8j¦¿N¾XO¬ÌáÃU5úuòÂ}fg©¯Ó¯–ë38qøÈä©¿äq­¥€w/‰Sf$ÿ*cŠÞÞqrÑ«Û ûÔf ôêÔÇ2*R_ãY£Ãi;rÍÄ ³hõ*RJJŒ”G™Ò1¨(xñÉ€÷—)6ÜÙd+Â"Í×*Л̌¼Dj’ÂÈü$§Ïþ— '¸òóvßK¨ÿhoÏNS‹Ï¼»nlTK xvšÎ1.“!ÇÚ¤Õ¤CC«'ˆ‰K&–’Q‘lÌÅ.éˆ]ü^'Ú‰ÞÔ:Fâ‰ö¢wµ‘¸…ßÅâ}¨íD’íqk~V.ßÙûU[½‹m\¦Sj ¿:›6½&C2ã²u£mÕš ÔƒQ™l<Œ¶‘ «Ê*ócX{)w-z¦Ÿg”ÃqªEC®JÈò2\Id†–HÈÍjA¬öŸ8Ém"ÙAºFâ‰ö¢wµ‘¸…ßÅâ}¨íDîæñ»WSkÚ´[m\¶”Å‘56“_«H’Û­g™ókqJ6\ó-$Ä”[ý±ØÝÃòC„U[ÁªXz»DkîZ„çéÄÚ¼FfójîgSæ'~sIÂéô{ú¸}<áOĉ¬FoO´´²pÒà2hÛI‘Í©ÏÎf+=³Ã?'U±´Óí*/ÎÎÐ}ÐwžT =´¥³ ¥ÂæÛY,ÐFyž‰‰g’H‹"/‹Ñ “·å/íÝ#Ù‡D.Nß”¼o·tf;)˜:Ã[uÆ,#¬ä¦kꎹ-Um5ä!Ló„•6HÐÑ3' •ãÏE>aÇj¸EäóªU&TÓ‰ÈЊ\‡(Ñm½,°JQ«A²SFd„ç‘™žD[Lz½ ù=>U_~h~À:òz|ª¾üÐý€t7äôùU}ù¡û÷îû ¸ [Û5]²˜–÷J·N«Ã—M…ß•îé”ÛÈSMsm²K^’É)ÑI’<‹h¾#3¹Y< Ø/™¥~9 D9?oÞÍÞ5‡¨a.öÉ™ŒHˆûTR”½“!¬”§¡çã%·š–ّ磙6œêv'°åi°ßxÙš’•Cœk‘Bª©Èä̶¨Ì’´ìÈÌ”E¢¤™ò^_nÓé…ó­ àÊÉåÁ|Í+ñÈQÍ2§Q¢Ô¢V)Ÿ‡: è“K 48˨Q) B‹jTJ"22ñ 5¹÷º¼sÝšîÿŒÅ³KzM-–5ÆÒz3a+,›’’ÌÖÙFZfISf´&™âK—‡*£’*°×X²:i…_ˆÑó*#?§i°æÒðUà™ç¢¥dypÐÐ0õåöí>˜Q¿:ÐޙܬžPlÌÒ¿…´9“)ÒØ¨Så=Tgó²³C8“Í+J‹jTFDde´Œ…ã¸NRÊ¥&–Õ†ÄEv¦¶»•U˜í¡rÍ£#-,«$H,#QU‘™8£3>›Qž qHÕˋ·Pìíaô-åF£¼Z(3<´¦½¢ãI#-„Ži'·ÇãnÖòW_}*Kê²6ÂÊW¡£û#yסIsù¶h[iÿÊcžLäñżW¦.ɉi/öŒ×iä“ÿ½ôŸúÃWÖ/7Géú_¾±yº?OÒøÕõ‹ÍÑú~—ݬ^nÓô¾$5}bót~Ÿ¥ñ!«ë›£ôý/‰ _X¼Ý§é|HjúÅæèý?KâDÂæð1Šk){ÖÔWi”{ILŸ5ÿvéËæ˜jSkqz(jVIIžI#3Ëah›] ;à ô¤ýéÜnû•ûÛö}?öáeð¶ù¶ïC~»çzõß8{Ðß®ùÃÞ†ýwÎô7ë¾p÷¡¿]ó‡½ úïœy²ÿ$dg‘";ËiÖ”KCˆU¨J’¢<ÈÈËiÆ:Ý‘Æ,JnÏ_ ¢udMT´•¤ˆ¼IL²p’_Ë!½ÞTë J&sR}r¨y¡ªY¾æ‚ÞÍŽd®uÝ»4LšóçñSW(6/ Fe{D’3ñ™‘ó†°\^osо5‚âó{ž€¥ðÂú±|7¥r88‘{7ïiýÑ·u«~ŸL(ñÍ™RK(qy¦Ù(ÐDN¸FZeýqg’ *'¬C»ÉØP=ˆkÅ®òcvbÄ1k¼˜Ý…؆± Zï&7a@ö"[t8ñÅ «½›eë·ƒE6±h©°&4TXH7vKhq:Ih”œÒ£,ÈÈËâÄ3;•“Ê ‚ùšWã¢.Æpˆ«kT~×¥O(öFо餯–D„T¤6yóËÒÿwhË=»¢"Ú”¬ŽŽœQ•ÿÞ,ý“˜áØ‹.âÚ§™’gÉ=ŽK4ùà·žÒFgà›ŠIV0Ü?ùy»o¥Ôδ7Œfw+'”ó4¯Ç!D{TªMV»RG¡Ó%ÔgÌpš#*yç–~$¡ #RŒüÄB÷ᣓ­Ø¨nó±8äzEg0¬û²ƒ4'Âç&ºG¢Ód’ÌÛ#ÌóðÍN9ŒìpD·t×.RâÜ÷>ÅGl¢T*Ûæ=Ñm¢QØAsqHˆˆö¬²ND‚2])Ü?ùy»o¥Ôδ7ŒT oáòñ+j,ÍnÂ×,Ì(ЊújÒd4µ-nˆÐM2áeç2«UN!zåw}£;„ U8…ë•ÝöŒî5Tâ®WwÚ3¸@ÕSˆ^¹]ßhÎáUN!zåw}£;„ U8…ë•ÝöŒî5Tâ®WwÚ3¸@ÕSˆ^¹]ßhÎá¹K䤾ÇdkWƒb"±žÕÅv\…‘ʦ/õ"‰Ésö*­÷_kÎÓ£š\Y¶LQ⤋Ɨyn¤üäh?âCêÌņ 0±O‘D¸+ÐV *inÒš2K†G™%ú‹úN8ŒÌÌêKâÈSkÿÅåðâÕAµdS,ê\Óf…MÒn.ÃÍ*tÌÍO,²#ÍfdFFiJs2L÷þ^nÛéuó­ ãéÊqy÷—`mÍŠ‰aoÓYÆ%Òd8ûTš´ˆhudñ)dÒÒJ2-™˜¥Ý#q ¿‹ÄûQ;Ú‡HÜBïâñ>ÔNö¡Ò7»ø¼Oµ½¨tÄ.þ/íDïj#q ¿‹ÄûQ;Ú‡HÜBïâñ>ÔNö¡Ò7»ø¼Oµ½¨tÄ.þ/íDïj<ÄN ]I¡Ûô¼%¤ö*ÓÍ2?ý¢X­Ö­ åÕ+õyµ9Ž|9$-çUüÔ³3?ó îü¼Ý·Òê?çZÆ3ç”Êéï>ñmÅ‹™`®úÐÚ&!Ò¤5!Úe9Ù)ifñ%F„™™mÈÅ2èňÝÅÛ¾À“êF,Fî.ÝöŸP:1b7qvï°$úÑ‹»‹·}'ÔŒXÜ]»ì > tbÄnâíß`Iõ£#wnûO¨±¸»wØ}@èňÝÅÛ¾À“êF,Fî.ÝöŸP:1b7qvï°$úÑ‹»‹·}'ÔŒXÜ]»ì > tbÄnâíß`Iõ£#wnûO¨±¸»wØ}@èňÝÅÛ¾À“êF,Fî.ÝöŸPMnGwûF¾‹W«\Å´‡ ¨¥I“%ú$„6ËH–Ú–µ¨Ñ’RI#33ØDCg€ÿÙ‰8"\’jì  € ž§ Ñ=ˆ3ÇýÿMo­)9P`ˆ Ãö2ÌP/¹Á¹íÁÿÖ¿ÞøŽÕ<´ÄXS?.]4 —eÿE†mq×øÄœ^éžäœÃ3ã!d5¿nÍüAÏä?x—½´HHñáa4¦ÁÅEŒ^¦/Ê Î@3W½7Ź qJábùTæg‘êíg6ë_FýÞH¼n í Þ”@ÓRlÁàa} nSë¯iÅ|¿Ìï+êj…ÒÜàR²,;vLÿ¤–ÀNÑU¸=a ãÞð`9½üY„.°›ÓQViØEè2½/F°¿k}ûî“ó¬?ï;=|¤´¿ ¨Ã¶`"pyÚ1|{GÆ8°˜\’gí²þ÷‚E*L¶ÙÝÚ÷ðŒân:ªjÔPzç³N3Wž‹µÊñR•mÜâ³Mâ«îÊ»DM¿ß‡Ù#‹IA7Œq{0ïÁ­zÌ­Ÿ–ÐùP<Ú£ZÒüUb¯šÜi=ç è%EÎOñç‡s¬ž¥;¾¹I4Ϻ„áòº­G °óHS¸ÇKU_¦w™w¶ô5›Ööƒ‡Q0áq2’¢]=˜µER©0ÌϘ1lÚ !̆sÔžxKG-|¢ ü¬ewAñký­ ®;„Ð$!5Pd¹¯ãH0Sy9=OP”߸–Ì™¬ÐÄi[iO|SEH§YÄu9¼ÿü lØ';–£ŸÃŒñõyÛ2 za_Y¥A³QY¼pP(¡º—”>,Mc¤lÒ'¹*\üàH¶æ[³y?ÌçcpºÔ9ʘ8~úÍcN5²ë¢`,ˆwØbÞOŠ›4 Mºýnw¬éÆolòdÎðĤ.ýoˆ(\ŽÉa‹ª7ñ%L8s1~ ž¿ïáÊs¸1Õ³JÿÜå¡CÑìÃqî8¡ŸjŽ[KœºL\/ ϱ¥½¹Ô_²Õøq D?•¹Áÿ.­Uú￘u#d“Eù¢ñ1w„–+‰Ê‘Q䂉Éö˜(Ýã5ic­¿ÀzxaÒ”š± )ªAËïU ÷˜¢$`˜Ý.¨ÙE³·Õ¿×·KÀÇÝ]Xö–:ÃŽTK=¹·XΟ‡1-6•`Q*?ÜvO¯*[|Û úhàÁó'*¦*&Ý,@®ªÑž›÷D%DL‘~’jÀxÙÝg¹€˜–>j¾kà" ÚØ"Ä?EಠL v,þbOdˉ¸† 4ôFfòÉSV¸1n¤†­-¢”‚‹vÈ2M)’"©ß´ƒ€Î4Üð:Tãæ»âb¾b®a(äËa„¨Öå ¥TSƒ±9D`v6$Š—0ëî¯ï@ïº)à¶ìByÓ+å½ÁçtsÕëz|¼þ›äaá+ÅzPžx”Oêb$ªèRò†ú!ã° æqñ×SiH­Ãr¬Šrˆ^Çæk Gi4s°¨ùn.wPÏrõ#"ìÈ$SoTf5óþöûÌiÌïþ)öo#€Ø3lŠà>ãðÄ]¥•½®-lºG’/Ü8é‰ \’gí ž§ Ñ=ˆ3AhÿhÝì`DæèÚÐÛÊRPì84aJe›ÔFpIЃ5ZP±A*B¤Rgu~±Þ Íjí²×YÊXìíŠkè-fßnNßi {£ö.î™¶F'†Á…L{[luvÝ·Ê>_$ëskÖ÷ºÜnHpƒö²—ö`õqo)p*F=j‰aº—OâéüáüÂYÞÝwW †S®ðÏ2éÑ|Õ 6Ä}•te\vXK¹…|& qèÃÊò D‘‚\Óòè8ŽÛî )ÜkƒÙå"+BÇéÖ.9 KÒZRÐ*Aô–Xâ”eÃIÁuÍy³ÍV§P(Œå©4©h]¹ÈmÑ\L¼C'¡ÿuËù® Ž¨¤ÈûkM"ø€§ù¢>ã! Ù³D½† q–KE—žÊ1ÑJFŒ¶4’RŽEÔ]£Oø|v,NG´w|«bþO˜†ÚäSKÿb#§*ͨ™D%õ™{/FÍHK=b{T*“~-Ö··öU—òRn ‚ÓÏ äaW@3uÿ}Fn˜„&hQè×ažQ®+§õyJoFüŽV×#ÅU®}ÑKVX¡÷쇗{µo¼2•úxvPuh¢Ý¢%¸JúP9B²’#´ÌÀv2·ºß m§©ê•V¿üŠ–,|”ãv8üW°C+‹¾>gȵŽƒT!*LQòUu÷Æ I¼cU,œ&·ÉÞ’ìüÇÿ‡µ<²²ÿa³×/ 8f*Aú‚Ï’]KyÉ~Óº!¼'G˜?@ ØoyòUQÉá팟‡@Ð_Õí6ÚWH’ˇü+ (9Od*žwŠˆAS²×lÅ`¹’cN 4+ù,ï4¦rÉú&9züñ©£zÐêŠÐ os:] UÝŠiÏݯžµ+ZËiŒà«R¾øò>@ZéÝ»ßI¢ î¤Bäw§÷áQgÍkÿ¼)îVÚ‚Ó ¥|Ã)wß/¶(\‘ÞCA›»·Ïº?ûNS³P‰þ¨“^¸µ' YÖÄNô[c]9:GG'ð¿g€êa,’ RF3Ý/<ÖZ]#È@•ÝáÌé³Ê—b)8©‰ùåQpŸÙŠ¸ç²ƒjÂOáé\*á¢)¡ò÷"Ï…·w ‰¨HȦln9Çô»½ÍI­žh°/²n—òv«µ%íbƒ¶VCâÒƒ ž¦ä¶ä0¸¶Øàì¯Í(hMëWÍ=Ñ2Ý"áx+3™M° j±t h,y¨lóÁ¤»g&Ù2•ÍOÛG#ãžœFÁ„-Z9O´"Test Expiration I ‰>(\˜æ¢ W]  € |°}m,œ|ö ÀkäúáꤽMQpØv³VKž”ÜÈYüg¹/#ª½*p6j&ÏMé7Ç)“µ@È€l®Š~8ZÙ2HüÌt™SãÆÐf”¬þOyó_mÒÀ3Pž"ŽÑ¿ÏÉLš‘ÐQòÀDŠ`ááÆl†¿SسD1_ÈvV9±ð[Ðô¼ò67—J9?EÒNO±LiªËÝ€ Cî±E–T›Í>Ó)WÇ“Ål¼#ëuºÑ˜Ê*ú9ÀJÿ:­‹œ©¹*¸Ýà‰S‰>(\˜ú W]  € |°}m,œ|õý>Ó ¤pç¹-¤<üØÏ죫Žp²¨¿ËŽõ@MIqÀ¸QÒöxk-ÏÝp.Û„sÍ|ìGÂäÝÀ× §‘é¸:@2ÕuûÂŒï˜ E¢o¯¼:ɯVVƒööšÊ»›é1[ ‚F1æEø˜7øà×Ì};ºÓÊÚ¸³Þ¼GX2àý´qü4N"tà|™Í)ˆ“Jj\RÉÀvßõ´^ȞشÑ]ÇèUŠCN;R»Ÿ'¥õ€tW4…áÚ0ò2Až¦.CåˆÉÆ %覼–æî7¦&£¸umÍ ºÙouÿ£ç”6{PÄ“4RÔù¨˜ò·“G°´&Test Expiration III ‰>(\˜ú! W]  € |°}m,œ|ÓÖ‰1ì=ˆÃói®Øýœj[ÐHè3Iy{W5gzŠ+,¼s<èºdÕ¾ùk€,B”–<°¦®9«=tw»+ ˜–f»úIt#)þÛÍ4e “ïëc#h߯ ªúû'¨ÖŠgÀMY–òå¯Eœà9›ˆ£ÿÞaÐGfŸA€Gûï£dÎHøHÕæ°]øý3kÛXìòá!MGíK«dÒîÒÞi–çuA{UH.´QÖgèÒ·Å•T[`;X^ó)TQÌÂëí¤Pƒøýn'‚/%YXôz´)ôÊÔÔ÷— zà㱊î{¼µ1ÙÅ-@^>Š)ûéÎP°˜\˜Í”º7§Û”ìŠfþƒrÜ=³˜Ú ™C”ŒèÎj¬ª nçN4$Ó6v,qþ°Š €/RÁÝôªýé8ä¬â¤_›Ø|å[T”KÙv•]Þ~­ZÞI¨yÏxÜ0¿¨gæ§ ñ3\͆MÎ~eô¯êRŤ¾3}Å„Û ™K¸Ï g˜=¢ÞÃø/²5¯œ£« p%°—ÖAS>•Å\ˆ¨iѦdÿ‡WiXG9ûŠå'… ?wͶ™ àe¼&Ž‚ÙYñ#лü,"WßvÅÇÔp:+§ÄëûÎTáó¸)‘™ ʱ-=‚é™àŽJî,£ó}›÷"À£ß5ß*£‘ûpW€”j+ú VðÒ™a™ßñ&)ÝK«.í‚ÁtÀs¼„ú7…ƒèˆö„ãgu)²¦êá>=4é˜ÄãP#BÒÜ¢²s‡ß¼`o$䪭_°Â wôÕÝiIÇÍsñrœ³ðÍÅ¢}X Z[#Éî? K¿2tsí+ö·³I˜ð”:V¥9—¸;“øJÛîx`3&ç —¤µt)ùŒ‹•[¼ÿr´X3š£ß+" ;=0E‚¥lEÃY¶‚nÊ](o»®¬LpïÊú‰:Wy¥"q²¯EÞ:ÆÁ{Ç·à5›Õ!  ñö:ͪÊX2[žÅ¿_Û»W§×áØzþD¬¢p°ƒä²ä9G“—Õ¼Õ¦¡îõoîåûe©¦EBŒ.Þ ´_{¸p[Φãˆo‹åx4ÕÒœ¨2Â'о&O6= •–ÌWXüQŹ}£ýpL’òÀFØ…8Ç\Ú¹''ùRÝPçÒK,~fÚåKå)+¶žRªd-uåwÜÒ‘T¥Æ×ùˆ…ÄoºéÚÒuP\­Ö"¦ãòZs'¡\¦Fv¢(ŽsK®àÆ¢÷sµþð •‹ŒŠ<µ{ǰQDÀ&¡‘/Z{ñŠjBÈa.‘+O1$Hç1Pa+IÁ½8µƒâË„L Ê7òU‚ºi¦â¦D6Tÿ!•1ÙŠN!7ü ³m\°¶$-:’ý›xIÒù ze‡%×±Ýn™£ÔF ¾ ¹>$ÁidHK Ï9DáŒc†ª¬YdàŒ"D)°jª‚î9÷,Ö4Ú”yy/ÐPi½² ý.Zõ] ðù…§+k½5€ô—_ fĜǕ€ôæ.»Ð™ªV†=Þ‰% \˜×ú ­[ |°}m,œ|\ÿQ 3‰.ùB†¬ŸLuÿóçiøåõF¥Ç—óE;Û~æa­R.WßÛ®FÜ×+­¬ðÛ¹ˆÚ ì«*°ìÄT/5¶Ú½€°`m„˜•Õ³Øt\šJÕÅñW„DMASE~ ŒÔ7¸)9ˆÈ6£Làp¼éü8½Øx.êm’T·á¨Ïa@̙ũ´tµZàÙkJ„Ä*"Do67ÛzàCyTNß»]L⮉9sc„©É\e<¡•ÃþìrszÒ“¥ÂcÿÚ¾ÿƒŠwÕàßöYæŒÝ’ ³t©–ŽÆ´Ü?|'añ¶œ²a?ÕÖ¡xgªžµó OÎÛ\«ó°˜\™:¶jppðš@òâ× Úm¼Înüûm d ™{êG;:s+µû »«`ŽmÐÁ «W×lzË“ì•è ¼ŸXiÙ¤6_ùµ¬$þW]é’n $À4úïĶtÓšÃà(î  ¦8È•Pâ}jÝzGέND1D˜—ñ*ŽË5婘aŒ‘•ƒ^UÊ™>Gœ méé{„À»3ø”u5úÞV´HeQûPŽÑnE:媖;¾[³¾DY9q¶ùÞ„\vǤjñ3½föá0Ãú©Ì4ðx¨…}2©×ÇPЖ=¬”Í¡@‡ ¾»þ)UEõþ-†Ö&QÞeÌ'*ºÌ㫆( *¢@8õ48«‰x”ý@NF`Ž^¨Á‘úe‚|XÊ·î3ŒXÔÓì¼ ÌÛ²È}÷ׂÈ)d7ä–9ˆÓD1Óœ0«F‰T¶ìµæKŒð.UÀ¯‹§€ ®NWð ×EZËÒ7ݱºFe+ ßÜb±Á¡¿£|ŒASÔ2[‰÷OWDpMþfÈ"°u`‚7Äæ”Ž&¿Ò»ÝV×®426™áÞhæIŽ‚6'ñV͈OP©Á@Š/—â,ÄFÝñýËð^€9‘¯¤®QôEmË ²åÝ™öMòئãÙ‹¯À+óæép'ÒÄìtáÙ+ë²ò³˜ÔØ@mzDuW{eôÓM“++·°;Mí·ÈÀ<)/1AGËE®\¶M?eZ·…­ä! *¶M—„®SÞÚ@@ˆðÍd§U—€W„cÒ(.¢ªÇR½ì¸§¹8-Æzº³}4WV°)s7?EÉ$‹ÝÛÐ ÿëê5ÚøÈßÎäô ¸bÿPÇgèo°æ }Úî}ÂA¡C¶v>³"GVc²[ ¿ ,8pG(ˆœ"´>Ãïv™Dª†T-1Ê*Ù!{U×K{Ó­K )2Mѱq½%3qݧٕ X™’mµžmî¿w{âþ40Ù^0¼ê¨°a«yƒ&ÑP–ŒÛžjÊÌÂèB?q úKzçRE¸È+Tô!´øû|ƒ½Ï$ˆ‹CSç®*ׯ“4¯ ž¤È;7aÍ-a:jL„ÁöÒ4yX†¯ëaÒzÝtâPùFO+ýµo}œd`ñU'^ªNgÎ!Ï”=‰> \™:) |°}m,œ|À] \™: Í ÓÕ¼¶ï]`–O”žæìóý"S4Q}ªjôéñaøZà®|­ß|©è&‡'ÃÁQÇÿï‘Û„/V ÆŸÒ°5)K÷0~…Ëçõ^fô E¼8u·‡ÐßËž5®¥Mšupošû9´(R‡#ú²GmÄGòµBr¾‘)ñiÇÖ­“÷ؽ«oÏøZÆ™¶ènðFwäh[2ƒ,ºÑÇUÈ?ª¯Rñ¿õ¾ÙÏ”õšJW=Ýš¢›÷ú¨OÚAdu¿#H†a Ò/-¥cø\)cý^R$îIs}›ÜP‚ §êýVæ,ZE‹±D ˆ :7só8+I@JZz¼aœ†îû—¸XÅP1$0éñ))¢B±è󌧲Yñlþ¡ñÃh¨´Ár@kE©gÄôòyO Ñ:zà$¿>Hv Ö»à¾óæ‡!W ØH¸Œæal$”©”~ÿ#¥² „{Ûâh“Å ^'¸Ö¯]ÅÆš&°åŠŒ ©©ãeŠ7°*äç½ö—R>—®”‰sÜñÌê?Ç$õÀTÍ)3•AÚHÉÂÕŽ²<†Žñ`æá‘ÇÓsAA&\zß¶ê÷ÿfOVÝíq,C(YLff,˜™?¨úŸô"ôÙ†c^¼5^:`Æ7«\‘ZýTù±ìØVÙÃÿ œ]ÄòLð.Í…1ùRLFP·&Yã°././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/gpg_keyrings/rsa/trustdb.gpg0000644000076500000240000000365000000000000024106 0ustar00lukpstaff00000000000000gpg\™ \š$ñ $ " ) & „e¡âàû+@­²GŽû?S~ Š “03,ßy™i âœ|]"µ2›¸ {:»&¹{eZ¹)kÑ[ Ð.vŒC! ¿DݹÛkãô(ÓO*j…Ûöõ ‚ˆïVÓy_òÀÛV0‰²…ÚX# Òºž¨Œ×?¦¸M…Tâ•ô0á-² @æ’îö¸ÿ•ÐÒÉþ“f™% ñ·.·¡÷IšiKÈ[Q”½z õWÐÿEïE7%‘Bž§ Ñ=ˆ3( \µ&ãa{ØxäÄÑ£}U¼A’R ®Ï0 ¡t';¼O…”Ð{[â ' 謀É$m«µK˜|°}m,œ|0 aÿâJ_ž<Ĩ‰ÒM:¯ÊB ¤vK¢±8«— ½¼In$.ÐÔq* ?Ü5uÖ‡–Áäœj:À†·y|ÕËz+ ïiÇ€BLDã¡8y{¿@°ÚhÓ,  £…p¡bqª=D?û)°”- Íí7üÿ]¡V+M‰hXîf„¨. Ì&m z?/°ãø¹ÇLòöƬ…)/././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_ecdsa_keys.py0000755000076500000240000001343700000000000022175 0ustar00lukpstaff00000000000000#!/usr/bin/env/ python """ test_ecdsa_keys.py Vladimir Diaz November 23, 2016. See LICENSE for licensing information. Test cases for test_ecdsa_keys.py. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import unittest import os import securesystemslib.exceptions import securesystemslib.formats import securesystemslib.ecdsa_keys import securesystemslib.rsa_keys public, private = securesystemslib.ecdsa_keys.generate_public_and_private() FORMAT_ERROR_MSG = 'securesystemslib.exceptions.FormatError raised. Check object\'s format.' class TestECDSA_keys(unittest.TestCase): def setUp(self): pass def test_generate_public_and_private(self): public, private = securesystemslib.ecdsa_keys.generate_public_and_private() # Check format of 'public' and 'private'. self.assertEqual(True, securesystemslib.formats.PEMECDSA_SCHEMA.matches(public)) self.assertEqual(True, securesystemslib.formats.PEMECDSA_SCHEMA.matches(private)) # Test for invalid argument. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ecdsa_keys.generate_public_and_private, 'bad_algo') def test_create_ecdsa_public_and_private_from_pem(self): global public global private # Check format of 'public' and 'private'. self.assertEqual(True, securesystemslib.formats.PEMECDSA_SCHEMA.matches(public)) self.assertEqual(True, securesystemslib.formats.PEMECDSA_SCHEMA.matches(private)) # Check for a valid private pem. public, private = \ securesystemslib.ecdsa_keys.create_ecdsa_public_and_private_from_pem(private) # Check for an invalid pem (non-private). self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.ecdsa_keys.create_ecdsa_public_and_private_from_pem, public) # Test for invalid argument. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ecdsa_keys.create_ecdsa_public_and_private_from_pem, 123) def test_create_signature(self): global public global private data = b'The quick brown fox jumps over the lazy dog' signature, method = securesystemslib.ecdsa_keys.create_signature(public, private, data) # Verify format of returned values. self.assertEqual(True, securesystemslib.formats.ECDSASIGNATURE_SCHEMA.matches(signature)) self.assertEqual(True, securesystemslib.formats.NAME_SCHEMA.matches(method)) self.assertEqual('ecdsa-sha2-nistp256', method) # Check for improperly formatted argument. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ecdsa_keys.create_signature, 123, private, data) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ecdsa_keys.create_signature, public, 123, data) # Check for invalid 'data'. self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.ecdsa_keys.create_signature, public, private, 123) def test_verify_signature(self): global public global private data = b'The quick brown fox jumps over the lazy dog' scheme = 'ecdsa-sha2-nistp256' signature, scheme = securesystemslib.ecdsa_keys.create_signature(public, private, data, scheme) valid_signature = securesystemslib.ecdsa_keys.verify_signature(public, scheme, signature, data) self.assertEqual(True, valid_signature) # Generate an RSA key so that we can verify that non-ECDSA keys are # rejected. rsa_pem, junk = securesystemslib.rsa_keys.generate_rsa_public_and_private() # Verify that a non-ECDSA key (via the PEM argument) is rejected. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ecdsa_keys.verify_signature, rsa_pem, scheme, signature, data) # Check for improperly formatted arguments. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ecdsa_keys.verify_signature, 123, scheme, signature, data) # Signature method improperly formatted. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ecdsa_keys.verify_signature, public, 123, signature, data) # Invalid signature method. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ecdsa_keys.verify_signature, public, 'unsupported_scheme', signature, data) # Signature not a string. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ecdsa_keys.verify_signature, public, scheme, 123, data) # Invalid signature.. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ecdsa_keys.verify_signature, public, scheme, 'bad_signature', data) # Check for invalid signature and data. self.assertEqual(False, securesystemslib.ecdsa_keys.verify_signature(public, scheme, signature, b'123')) # Mismatched signature. bad_signature = b'a'*64 self.assertEqual(False, securesystemslib.ecdsa_keys.verify_signature(public, scheme, bad_signature, data)) # Generated signature created with different data. new_signature, scheme = securesystemslib.ecdsa_keys.create_signature(public, private, b'mismatched data') self.assertEqual(False, securesystemslib.ecdsa_keys.verify_signature(public, scheme, new_signature, data)) # Run the unit tests. if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_ed25519_keys.py0000755000076500000240000001135000000000000022104 0ustar00lukpstaff00000000000000#!/usr/bin/env/ python """ test_ed25519_keys.py Vladimir Diaz October 11, 2013. See LICENSE for licensing information. Test cases for test_ed25519_keys.py. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import unittest import os import securesystemslib.exceptions import securesystemslib.formats import securesystemslib.ed25519_keys public, private = securesystemslib.ed25519_keys.generate_public_and_private() FORMAT_ERROR_MSG = 'securesystemslib.exceptions.FormatError raised. Check object\'s format.' class TestEd25519_keys(unittest.TestCase): def setUp(self): pass def test_generate_public_and_private(self): pub, priv = securesystemslib.ed25519_keys.generate_public_and_private() # Check format of 'pub' and 'priv'. self.assertEqual(True, securesystemslib.formats.ED25519PUBLIC_SCHEMA.matches(pub)) self.assertEqual(True, securesystemslib.formats.ED25519SEED_SCHEMA.matches(priv)) def test_create_signature(self): global public global private data = b'The quick brown fox jumps over the lazy dog' scheme = 'ed25519' signature, scheme = securesystemslib.ed25519_keys.create_signature(public, private, data, scheme) # Verify format of returned values. self.assertEqual(True, securesystemslib.formats.ED25519SIGNATURE_SCHEMA.matches(signature)) self.assertEqual(True, securesystemslib.formats.ED25519_SIG_SCHEMA.matches(scheme)) self.assertEqual('ed25519', scheme) # Check for improperly formatted argument. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ed25519_keys.create_signature, 123, private, data, scheme) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ed25519_keys.create_signature, public, 123, data, scheme) # Check for invalid 'data'. self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.ed25519_keys.create_signature, public, private, 123, scheme) def test_verify_signature(self): global public global private data = b'The quick brown fox jumps over the lazy dog' scheme = 'ed25519' signature, scheme = securesystemslib.ed25519_keys.create_signature(public, private, data, scheme) valid_signature = securesystemslib.ed25519_keys.verify_signature(public, scheme, signature, data) self.assertEqual(True, valid_signature) bad_signature = os.urandom(64) valid_signature = securesystemslib.ed25519_keys.verify_signature(public, scheme, bad_signature, data) self.assertEqual(False, valid_signature) # Check for improperly formatted arguments. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ed25519_keys.verify_signature, 123, scheme, signature, data) # Signature method improperly formatted. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ed25519_keys.verify_signature, public, 123, signature, data) # Invalid signature method. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ed25519_keys.verify_signature, public, 'unsupported_scheme', signature, data) # Signature not a string. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ed25519_keys.verify_signature, public, scheme, 123, data) # Invalid signature length, which must be exactly 64 bytes.. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.ed25519_keys.verify_signature, public, scheme, 'bad_signature', data) # Check for invalid signature and data. # Mismatched data. self.assertEqual(False, securesystemslib.ed25519_keys.verify_signature( public, scheme, signature, b'123')) # Mismatched signature. bad_signature = b'a'*64 self.assertEqual(False, securesystemslib.ed25519_keys.verify_signature( public, scheme, bad_signature, data)) # Generated signature created with different data. new_signature, scheme = securesystemslib.ed25519_keys.create_signature( public, private, b'mismatched data', scheme) self.assertEqual(False, securesystemslib.ed25519_keys.verify_signature( public, scheme, new_signature, data)) # Run the unit tests. if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_exceptions.py0000755000076500000240000000301500000000000022233 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ test_exceptions.py Vladimir Diaz December 20, 2016. See LICENSE for licensing information. Test cases for exceptions.py (mainly the exceptions defined there). """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import unittest import logging import securesystemslib.exceptions logger = logging.getLogger(__name__) class TestExceptions(unittest.TestCase): def setUp(self): pass def tearDown(self): pass def test_bad_signature_error(self): bad_signature_error = securesystemslib.exceptions.BadSignatureError( 'bad sig') logger.error(bad_signature_error) def test_bad_hash_error(self): bad_hash_error = securesystemslib.exceptions.BadHashError( '01234', '56789') logger.error(bad_hash_error) def test_invalid_metadata_json_error(self): format_error = securesystemslib.exceptions.FormatError( 'Improperly formatted JSON') invalid_metadata_json_error = \ securesystemslib.exceptions.InvalidMetadataJSONError(format_error) logger.error(invalid_metadata_json_error) # Run the unit tests. if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_formats.py0000755000076500000240000002627100000000000021536 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ test_formats.py Vladimir Diaz January 2017 (modified from TUF's original formats.py) See LICENSE for licensing information. Unit test for 'formats.py' """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import unittest import datetime import securesystemslib.formats import securesystemslib.schema import six class TestFormats(unittest.TestCase): def setUp(self): pass def tearDown(self): pass def test_schemas(self): # Test conditions for valid schemas. valid_schemas = { 'ISO8601_DATETIME_SCHEMA': (securesystemslib.formats.ISO8601_DATETIME_SCHEMA, '1985-10-21T13:20:00Z'), 'UNIX_TIMESTAMP_SCHEMA': (securesystemslib.formats.UNIX_TIMESTAMP_SCHEMA, 499137720), 'HASH_SCHEMA': (securesystemslib.formats.HASH_SCHEMA, 'A4582BCF323BCEF'), 'HASHDICT_SCHEMA': (securesystemslib.formats.HASHDICT_SCHEMA, {'sha256': 'A4582BCF323BCEF'}), 'HEX_SCHEMA': (securesystemslib.formats.HEX_SCHEMA, 'A4582BCF323BCEF'), 'KEYID_SCHEMA': (securesystemslib.formats.KEYID_SCHEMA, '123456789abcdef'), 'KEYIDS_SCHEMA': (securesystemslib.formats.KEYIDS_SCHEMA, ['123456789abcdef', '123456789abcdef']), 'SCHEME_SCHEMA': (securesystemslib.formats.SCHEME_SCHEMA, 'ecdsa-sha2-nistp256'), 'PATH_SCHEMA': (securesystemslib.formats.PATH_SCHEMA, '/home/someuser/'), 'PATHS_SCHEMA': (securesystemslib.formats.PATHS_SCHEMA, ['/home/McFly/', '/home/Tannen/']), 'URL_SCHEMA': (securesystemslib.formats.URL_SCHEMA, 'https://www.updateframework.com/'), 'NAME_SCHEMA': (securesystemslib.formats.NAME_SCHEMA, 'Marty McFly'), 'TEXT_SCHEMA': (securesystemslib.formats.TEXT_SCHEMA, 'Password: '), 'BOOLEAN_SCHEMA': (securesystemslib.formats.BOOLEAN_SCHEMA, True), 'RSAKEYBITS_SCHEMA': (securesystemslib.formats.RSAKEYBITS_SCHEMA, 4096), 'PASSWORD_SCHEMA': (securesystemslib.formats.PASSWORD_SCHEMA, 'secret'), 'PASSWORDS_SCHEMA': (securesystemslib.formats.PASSWORDS_SCHEMA, ['pass1', 'pass2']), 'KEYVAL_SCHEMA': (securesystemslib.formats.KEYVAL_SCHEMA, {'public': 'pubkey', 'private': 'privkey'}), 'PUBLIC_KEYVAL_SCHEMA': (securesystemslib.formats.PUBLIC_KEYVAL_SCHEMA, {'public': 'pubkey'}), 'PUBLIC_KEYVAL_SCHEMA2': (securesystemslib.formats.PUBLIC_KEYVAL_SCHEMA, {'public': 'pubkey', 'private': ''}), 'RSA_SCHEME_SCHEMA_RSASSA_PSS': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsassa-pss-md5'), 'RSA_SCHEME_SCHEMA_RSASSA_PSS_2': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsassa-pss-sha1'), 'RSA_SCHEME_SCHEMA_RSASSA_PSS_3': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsassa-pss-sha224'), 'RSA_SCHEME_SCHEMA_RSASSA_PSS_4': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsassa-pss-sha256'), 'RSA_SCHEME_SCHEMA_RSASSA_PSS_5': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsassa-pss-sha384'), 'RSA_SCHEME_SCHEMA_RSASSA_PSS_6': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsassa-pss-sha512'), 'RSA_SCHEME_SCHEMA_PKCS1v15': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsa-pkcs1v15-md5'), 'RSA_SCHEME_SCHEMA_PKCS1v15_2': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsa-pkcs1v15-sha1'), 'RSA_SCHEME_SCHEMA_PKCS1v15_3': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsa-pkcs1v15-sha224'), 'RSA_SCHEME_SCHEMA_PKCS1v15_4': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsa-pkcs1v15-sha256'), 'RSA_SCHEME_SCHEMA_PKCS1v15_5': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsa-pkcs1v15-sha384'), 'RSA_SCHEME_SCHEMA_PKCS1v15_6': (securesystemslib.formats.RSA_SCHEME_SCHEMA, 'rsa-pkcs1v15-sha512'), 'KEY_SCHEMA': (securesystemslib.formats.KEY_SCHEMA, {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', 'keyval': {'public': 'pubkey', 'private': 'privkey'}}), 'PUBLIC_KEY_SCHEMA': (securesystemslib.formats.KEY_SCHEMA, {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', 'keyval': {'public': 'pubkey'}}), 'PUBLIC_KEY_SCHEMA2': (securesystemslib.formats.KEY_SCHEMA, {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', 'keyval': {'public': 'pubkey', 'private': ''}}), 'RSAKEY_SCHEMA': (securesystemslib.formats.RSAKEY_SCHEMA, {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', 'keyid': '123456789abcdef', 'keyval': {'public': 'pubkey', 'private': 'privkey'}}), 'SIGNATURE_SCHEMA': (securesystemslib.formats.SIGNATURE_SCHEMA, {'keyid': '123abc', 'method': 'evp', 'sig': 'A4582BCF323BCEF'}), 'SIGNABLE_SCHEMA': (securesystemslib.formats.SIGNABLE_SCHEMA, {'signed': 'signer', 'signatures': [{'keyid': '123abc', 'method': 'evp', 'sig': 'A4582BCF323BCEF'}]}), 'ANY_KEYDICT_SCHEMA': (securesystemslib.formats.ANY_KEYDICT_SCHEMA, {'123abc': {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', 'keyval': {'public': 'pubkey', 'private': 'privkey'}}})} # Iterate 'valid_schemas', ensuring each 'valid_schema' correctly matches # its respective 'schema_type'. for schema_name, (schema_type, valid_schema) in six.iteritems(valid_schemas): if not schema_type.matches(valid_schema): print('bad schema: ' + repr(valid_schema)) self.assertEqual(True, schema_type.matches(valid_schema)) # Test conditions for invalid schemas. # Set the 'valid_schema' of 'valid_schemas' to an invalid # value and test that it does not match 'schema_type'. for schema_name, (schema_type, valid_schema) in six.iteritems(valid_schemas): invalid_schema = 0xBAD if isinstance(schema_type, securesystemslib.schema.Integer): invalid_schema = 'BAD' self.assertEqual(False, schema_type.matches(invalid_schema)) def test_unix_timestamp_to_datetime(self): # Test conditions for valid arguments. UNIX_TIMESTAMP_SCHEMA = securesystemslib.formats.UNIX_TIMESTAMP_SCHEMA self.assertTrue(datetime.datetime, securesystemslib.formats.unix_timestamp_to_datetime(499137720)) datetime_object = datetime.datetime(1985, 10, 26, 1, 22) self.assertEqual(datetime_object, securesystemslib.formats.unix_timestamp_to_datetime(499137720)) # Test conditions for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.unix_timestamp_to_datetime, 'bad') self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.unix_timestamp_to_datetime, 1000000000000000000000) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.unix_timestamp_to_datetime, -1) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.unix_timestamp_to_datetime, ['5']) def test_datetime_to_unix_timestamp(self): # Test conditions for valid arguments. datetime_object = datetime.datetime(2015, 10, 21, 19, 28) self.assertEqual(1445455680, securesystemslib.formats.datetime_to_unix_timestamp(datetime_object)) # Test conditions for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.datetime_to_unix_timestamp, 'bad') self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.datetime_to_unix_timestamp, 1000000000000000000000) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.datetime_to_unix_timestamp, ['1']) def test_format_base64(self): # Test conditions for valid arguments. data = 'updateframework'.encode('utf-8') self.assertEqual('dXBkYXRlZnJhbWV3b3Jr', securesystemslib.formats.format_base64(data)) self.assertTrue(isinstance(securesystemslib.formats.format_base64(data), six.string_types)) # Test conditions for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.format_base64, 123) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.format_base64, True) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.format_base64, ['123']) def test_parse_base64(self): # Test conditions for valid arguments. base64 = 'dXBkYXRlZnJhbWV3b3Jr' self.assertEqual(b'updateframework', securesystemslib.formats.parse_base64(base64)) self.assertTrue(isinstance(securesystemslib.formats.parse_base64(base64), six.binary_type)) # Test conditions for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.parse_base64, 123) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.parse_base64, True) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.parse_base64, ['123']) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.formats.parse_base64, '/') def test_encode_canonical(self): # Test conditions for valid arguments. encode = securesystemslib.formats.encode_canonical result = [] output = result.append bad_output = 123 self.assertEqual('""', encode("")) self.assertEqual('[1,2,3]', encode([1, 2, 3])) self.assertEqual('[1,2,3]', encode([1,2,3])) self.assertEqual('[]', encode([])) self.assertEqual('{}', encode({})) self.assertEqual('{"A":[99]}', encode({"A": [99]})) self.assertEqual('{"A":true}', encode({"A": True})) self.assertEqual('{"B":false}', encode({"B": False})) self.assertEqual('{"x":3,"y":2}', encode({"x": 3, "y": 2})) self.assertEqual('{"x":3,"y":null}', encode({"x": 3, "y": None})) # Condition where 'encode()' sends the result to the callable # 'output'. self.assertEqual(None, encode([1, 2, 3], output)) self.assertEqual('[1,2,3]', ''.join(result)) # Test conditions for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, encode, securesystemslib.exceptions.FormatError) self.assertRaises(securesystemslib.exceptions.FormatError, encode, 8.0) self.assertRaises(securesystemslib.exceptions.FormatError, encode, {"x": 8.0}) self.assertRaises(securesystemslib.exceptions.FormatError, encode, 8.0, output) self.assertRaises(securesystemslib.exceptions.FormatError, encode, {"x": securesystemslib.exceptions.FormatError}) # Run unit test. if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_gpg.py0000644000076500000240000007703500000000000020641 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ test_gpg.py Santiago Torres-Arias Lukas Puehringer Nov 15, 2017 See LICENSE for licensing information. Test gpg/pgp-related functions. """ import os import sys import shutil import tempfile import unittest if sys.version_info >= (3, 3): from unittest.mock import patch # pylint: disable=no-name-in-module,import-error else: from mock import patch # pylint: disable=import-error from six import string_types from copy import deepcopy from collections import OrderedDict import cryptography.hazmat.primitives.serialization as serialization import cryptography.hazmat.backends as backends import cryptography.hazmat.primitives.hashes as hashing from securesystemslib import process from securesystemslib.gpg.functions import (create_signature, export_pubkey, verify_signature, export_pubkeys) from securesystemslib.gpg.util import (get_version, is_version_fully_supported, get_hashing_class, parse_packet_header, parse_subpacket_header) from securesystemslib.gpg.rsa import create_pubkey as rsa_create_pubkey from securesystemslib.gpg.dsa import create_pubkey as dsa_create_pubkey from securesystemslib.gpg.eddsa import create_pubkey as eddsa_create_pubkey from securesystemslib.gpg.eddsa import ED25519_SIG_LENGTH from securesystemslib.gpg.common import (parse_pubkey_payload, parse_pubkey_bundle, get_pubkey_bundle, _assign_certified_key_info, _get_verified_subkeys, parse_signature_packet) from securesystemslib.gpg.constants import (SHA1, SHA256, SHA512, GPG_EXPORT_PUBKEY_COMMAND, PACKET_TYPE_PRIMARY_KEY, PACKET_TYPE_USER_ID, PACKET_TYPE_USER_ATTR, PACKET_TYPE_SUB_KEY, HAVE_GPG) from securesystemslib.gpg.exceptions import (PacketParsingError, PacketVersionNotSupportedError, SignatureAlgorithmNotSupportedError, KeyNotFoundError, CommandError, KeyExpirationError) from securesystemslib.formats import (GPG_PUBKEY_SCHEMA, ANY_PUBKEY_DICT_SCHEMA) @unittest.skipIf(not HAVE_GPG, "gpg not found") class TestUtil(unittest.TestCase): """Test util functions. """ def test_version_utils_return_types(self): """Run dummy tests for coverage. """ self.assertTrue(isinstance(get_version(), string_types)) self.assertTrue(isinstance(is_version_fully_supported(), bool)) def test_get_hashing_class(self): # Assert return expected hashing class expected_hashing_class = [hashing.SHA1, hashing.SHA256, hashing.SHA512] for idx, hashing_id in enumerate([SHA1, SHA256, SHA512]): result = get_hashing_class(hashing_id) self.assertEqual(result, expected_hashing_class[idx]) # Assert raises ValueError with non-supported hashing id with self.assertRaises(ValueError): get_hashing_class("bogus_hashing_id") def test_parse_packet_header(self): """Test parse_packet_header with manually crafted data. """ data_list = [ ## New format packet length with mock packet type 100001 # one-octet length, header len: 2, body len: 0 to 191 [0b01100001, 0], [0b01100001, 191], # two-octet length, header len: 3, body len: 192 to 8383 [0b01100001, 192, 0], [0b01100001, 223, 255], # five-octet length, header len: 6, body len: 0 to 4,294,967,295 [0b01100001, 255, 0, 0, 0, 0], [0b01100001, 255, 255, 255, 255, 255], ## Old format packet lengths with mock packet type 1001 # one-octet length, header len: 2, body len: 0 to 255 [0b00100100, 0], [0b00100100, 255], # two-octet length, header len: 3, body len: 0 to 65,535 [0b00100101, 0, 0], [0b00100101, 255, 255], # four-octet length, header len: 5, body len: 0 to 4,294,967,295 [0b00100110, 0, 0, 0, 0, 0], [0b00100110, 255, 255, 255, 255, 255], ] # packet_type | header_len | body_len | packet_len expected = [ (33, 2, 0, 2), (33, 2, 191, 193), (33, 3, 192, 195), (33, 3, 8383, 8386), (33, 6, 0, 6), (33, 6, 4294967295, 4294967301), (9, 2, 0, 2), (9, 2, 255, 257), (9, 3, 0, 3), (9, 3, 65535, 65538), (9, 5, 0, 5), (9, 5, 4294967295, 4294967300), ] for idx, data in enumerate(data_list): result = parse_packet_header(bytearray(data)) self.assertEqual(result, expected[idx]) # New Format Packet Lengths with Partial Body Lengths range for second_octet in [224, 254]: with self.assertRaises(PacketParsingError): parse_packet_header(bytearray([0b01100001, second_octet])) # Old Format Packet Lengths with indeterminate length (length type 3) with self.assertRaises(PacketParsingError): parse_packet_header(bytearray([0b00100111])) # Get expected type parse_packet_header(bytearray([0b01100001, 0]), expected_type=33) # Raise with unexpected type with self.assertRaises(PacketParsingError): parse_packet_header(bytearray([0b01100001, 0]), expected_type=34) def test_parse_subpacket_header(self): """Test parse_subpacket_header with manually crafted data. """ # All items until last item encode the length of the subpacket, # the last item encodes the mock subpacket type. data_list = [ # length of length 1, subpacket length 0 to 191 [0, 33], # NOTE: Nonsense 0-length [191, 33], # # length of length 2, subpacket length 192 to 16,319 [192, 0, 33], [254, 255, 33], # # length of length 5, subpacket length 0 to 4,294,967,295 [255, 0, 0, 0, 0, 33], # NOTE: Nonsense 0-length [255, 255, 255, 255, 255, 33], ] # packet_type | header_len | body_len | packet_len expected = [ (33, 2, -1, 1), # NOTE: Nonsense negative payload (33, 2, 190, 192), (33, 3, 191, 194), (33, 3, 16318, 16321), (33, 6, -1, 5), # NOTE: Nonsense negative payload (33, 6, 4294967294, 4294967300) ] for idx, data in enumerate(data_list): result = parse_subpacket_header(bytearray(data)) self.assertEqual(result, expected[idx]) @unittest.skipIf(not HAVE_GPG, "gpg not found") class TestCommon(unittest.TestCase): """Test common functions of the securesystemslib.gpg module. """ @classmethod def setUpClass(self): gpg_keyring_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), "gpg_keyrings", "rsa") homearg = "--homedir {}".format(gpg_keyring_path).replace("\\", "/") # Load test raw public key bundle from rsa keyring, used to construct # erroneous gpg data in tests below. keyid = "F557D0FF451DEF45372591429EA70BD13D883381" cmd = GPG_EXPORT_PUBKEY_COMMAND.format(keyid=keyid, homearg=homearg) proc = process.run(cmd, stdout=process.PIPE, stderr=process.PIPE) self.raw_key_data = proc.stdout self.raw_key_bundle = parse_pubkey_bundle(self.raw_key_data) # Export pubkey bundle with expired key for key expiration tests keyid = "E8AC80C924116DABB51D4B987CB07D6D2C199C7C" cmd = GPG_EXPORT_PUBKEY_COMMAND.format(keyid=keyid, homearg=homearg) proc = process.run(cmd, stdout=process.PIPE, stderr=process.PIPE) self.raw_expired_key_bundle = parse_pubkey_bundle(proc.stdout) def test_parse_pubkey_payload_errors(self): """ Test parse_pubkey_payload errors with manually crafted data. """ # passed data | expected error | expected error message test_data = [ (None, ValueError, "empty pubkey"), (bytearray([0x03]), PacketVersionNotSupportedError, "packet version '3' not supported"), (bytearray([0x04, 0, 0, 0, 0, 255]), SignatureAlgorithmNotSupportedError, "Signature algorithm '255' not supported") ] for data, error, error_str in test_data: with self.assertRaises(error) as ctx: parse_pubkey_payload(data) self.assertTrue(error_str in str(ctx.exception)) def test_parse_pubkey_bundle_errors(self): """Test parse_pubkey_bundle errors with manually crafted data partially based on real gpg key data (see self.raw_key_bundle). """ # Extract sample (legitimate) user ID packet and pass as first packet to # raise first packet must be primary key error user_id_packet = list(self.raw_key_bundle[PACKET_TYPE_USER_ID].keys())[0] # Extract sample (legitimate) primary key packet and pass as first two # packets to raise unexpected second primary key error primary_key_packet = self.raw_key_bundle[PACKET_TYPE_PRIMARY_KEY]["packet"] # Create incomplete packet to re-raise header parsing IndexError as # PacketParsingError incomplete_packet = bytearray([0b01111111]) # passed data | expected error message test_data = [ (None, "empty gpg data"), (user_id_packet, "must be a primary key"), (primary_key_packet + primary_key_packet, "Unexpected primary key"), (incomplete_packet, "index out of range") ] for data, error_str in test_data: with self.assertRaises(PacketParsingError) as ctx: parse_pubkey_bundle(data) self.assertTrue(error_str in str(ctx.exception)) # Create empty packet of unsupported type 66 (bit 0-5) and length 0 and # pass as second packet to provoke skipping of unsupported packet unsupported_packet = bytearray([0b01111111, 0]) with patch("securesystemslib.gpg.common.log") as mock_log: parse_pubkey_bundle(primary_key_packet + unsupported_packet) self.assertTrue("Ignoring gpg key packet '63'" in mock_log.info.call_args[0][0]) def test_parse_pubkey_bundle(self): """Assert presence of packets expected returned from `parse_pubkey_bundle` for specific test key). See ``` gpg --homedir tests/gpg_keyrings/rsa/ --export 9EA70BD13D883381 | \ gpg --list-packets ``` """ # Expect parsed primary key matching GPG_PUBKEY_SCHEMA self.assertTrue(GPG_PUBKEY_SCHEMA.matches( self.raw_key_bundle[PACKET_TYPE_PRIMARY_KEY]["key"])) # Parse corresponding raw packet for comparison _, header_len, _, _ = parse_packet_header( self.raw_key_bundle[PACKET_TYPE_PRIMARY_KEY]["packet"]) # pylint: disable=unsubscriptable-object parsed_raw_packet = parse_pubkey_payload(bytearray( self.raw_key_bundle[PACKET_TYPE_PRIMARY_KEY]["packet"][header_len:])) # And compare self.assertDictEqual( self.raw_key_bundle[PACKET_TYPE_PRIMARY_KEY]["key"], parsed_raw_packet) # Expect one primary key signature (revocation signature) self.assertEqual( len(self.raw_key_bundle[PACKET_TYPE_PRIMARY_KEY]["signatures"]), 1) # Expect one User ID packet, one User Attribute packet and one Subkey, # each with correct data for _type in [PACKET_TYPE_USER_ID, PACKET_TYPE_USER_ATTR, PACKET_TYPE_SUB_KEY]: # Of each type there is only one packet self.assertTrue(len(self.raw_key_bundle[_type]) == 1) # The raw packet is stored as key in the per-packet type collection raw_packet = next(iter(self.raw_key_bundle[_type])) # Its values are the raw packets header and body length self.assertEqual(len(raw_packet), self.raw_key_bundle[_type][raw_packet]["header_len"] + self.raw_key_bundle[_type][raw_packet]["body_len"]) # and one self-signature self.assertEqual( len(self.raw_key_bundle[_type][raw_packet]["signatures"]), 1) def test_assign_certified_key_info_errors(self): """Test _assign_certified_key_info errors with manually crafted data based on real gpg key data (see self.raw_key_bundle). """ # Replace legitimate user certifacte with a bogus packet wrong_cert_bundle = deepcopy(self.raw_key_bundle) packet, packet_data = wrong_cert_bundle[PACKET_TYPE_USER_ID].popitem() packet_data["signatures"] = [bytearray([0b01111111, 0])] wrong_cert_bundle[PACKET_TYPE_USER_ID][packet] = packet_data # Replace primary key id with a non-associated keyid wrong_keyid_bundle = deepcopy(self.raw_key_bundle) wrong_keyid_bundle[PACKET_TYPE_PRIMARY_KEY]["key"]["keyid"] = \ "8465A1E2E0FB2B40ADB2478E18FB3F537E0C8A17" # Remove a byte in user id packet to make signature verification fail invalid_cert_bundle = deepcopy(self.raw_key_bundle) packet, packet_data = invalid_cert_bundle[PACKET_TYPE_USER_ID].popitem() packet = packet[:-1] invalid_cert_bundle[PACKET_TYPE_USER_ID][packet] = packet_data test_data = [ # Skip and log parse_signature_packet error (wrong_cert_bundle, "Expected packet 2, but got 63 instead"), # Skip and log signature packet that doesn't match primary key id (wrong_keyid_bundle, "Ignoring User ID certificate issued by"), # Skip and log invalid signature (invalid_cert_bundle, "Ignoring invalid User ID self-certificate") ] for bundle, expected_msg in test_data: with patch("securesystemslib.gpg.common.log") as mock_log: _assign_certified_key_info(bundle) msg = str(mock_log.info.call_args[0][0]) self.assertTrue(expected_msg in msg, "'{}' not in '{}'".format(expected_msg, msg)) def test_assign_certified_key_info_expiration(self): """Test assignment of key expiration date in gpg.common._assign_certified_key_info using real gpg data (with ambiguity resolution / prioritization). # FIXME: Below tests are missing proper assertions for which User ID self-certificate is considered for the expiration date. Reasons are: - gpg does not let you (easily) modify individual expiration dates of User IDs (changing one changes all), hence we cannot assert the chosen packet by the particular date - _assign_certified_key_info first verifies all self-certificates and then only considers successfully verified ones, hence we cannot modify the certificate data, before passing it to _assign_certified_key_info IMO the best solution is a better separation of concerns, e.g. separate self-certificate verification and packet prioritization. """ # Test ambiguity resolution scheme with 3 User IDs # :user ID packet: "Test Expiration I " # :user ID packet: "Test Expiration II " # :user ID packet: "Test Expiration III " # User ID packets are ordered by their creation time in ascending order. # "Test Expiration II" has the primary user ID flag set and therefor has # the highest priority. key = _assign_certified_key_info(self.raw_expired_key_bundle) self.assertTrue(key["validity_period"] == 87901) # ~ 1 day # Test ambiguity resolution scheme with 2 User IDs # :user ID packet: "Test Expiration III " # :user ID packet: "Test Expiration I " # User ID packets are ordered by their creation time in descending order. # Neither packet has the primary user ID flag set. # "Test Expiration III" has the highest priority. raw_key_bundle = deepcopy(self.raw_expired_key_bundle) user_id_items = list(reversed(raw_key_bundle[PACKET_TYPE_USER_ID].items())) del user_id_items[1] raw_key_bundle[PACKET_TYPE_USER_ID] = OrderedDict(user_id_items) key = _assign_certified_key_info(raw_key_bundle) self.assertTrue(key["validity_period"] == 87901) # ~ 1 day def test_get_verified_subkeys_errors(self): """Test _get_verified_subkeys errors with manually crafted data based on real gpg key data (see self.raw_key_bundle). """ # Tamper with subkey (change version number) to trigger key parsing error bad_subkey_bundle = deepcopy(self.raw_key_bundle) packet, packet_data = bad_subkey_bundle[PACKET_TYPE_SUB_KEY].popitem() packet = bytes(packet[:packet_data["header_len"]] + bytearray([0x03]) + packet[packet_data["header_len"]+1:]) bad_subkey_bundle[PACKET_TYPE_SUB_KEY][packet] = packet_data # Add bogus sig to trigger sig parsing error wrong_sig_bundle = deepcopy(self.raw_key_bundle) packet, packet_data = wrong_sig_bundle[PACKET_TYPE_SUB_KEY].popitem() # NOTE: We can't only pass the bogus sig, because that would also trigger # the not enough sigs error (see not_enough_sigs_bundle) and mock only # lets us assert for the most recent log statement packet_data["signatures"].append(bytearray([0b01111111, 0])) wrong_sig_bundle[PACKET_TYPE_SUB_KEY][packet] = packet_data # Remove sigs to trigger not enough sigs error not_enough_sigs_bundle = deepcopy(self.raw_key_bundle) packet, packet_data = not_enough_sigs_bundle[PACKET_TYPE_SUB_KEY].popitem() packet_data["signatures"] = [] not_enough_sigs_bundle[PACKET_TYPE_SUB_KEY][packet] = packet_data # Duplicate sig to trigger wrong amount signatures too_many_sigs_bundle = deepcopy(self.raw_key_bundle) packet, packet_data = too_many_sigs_bundle[PACKET_TYPE_SUB_KEY].popitem() packet_data["signatures"] = packet_data["signatures"] * 2 too_many_sigs_bundle[PACKET_TYPE_SUB_KEY][packet] = packet_data # Tamper with primary key to trigger signature verification error invalid_sig_bundle = deepcopy(self.raw_key_bundle) invalid_sig_bundle[PACKET_TYPE_PRIMARY_KEY]["packet"] = \ invalid_sig_bundle[PACKET_TYPE_PRIMARY_KEY]["packet"][:-1] test_data = [ (bad_subkey_bundle, "Pubkey packet version '3' not supported"), (wrong_sig_bundle, "Expected packet 2, but got 63 instead"), (not_enough_sigs_bundle, "wrong amount of key binding signatures (0)"), (too_many_sigs_bundle, "wrong amount of key binding signatures (2)"), (invalid_sig_bundle, "invalid key binding signature"), ] for bundle, expected_msg in test_data: with patch("securesystemslib.gpg.common.log") as mock_log: _get_verified_subkeys(bundle) msg = str(mock_log.info.call_args[0][0]) self.assertTrue(expected_msg in msg, "'{}' not in '{}'".format(expected_msg, msg)) def test_get_verified_subkeys(self): """Test correct assignment of subkey expiration date in gpg.common._get_verified_subkeys using real gpg data. """ subkeys = _get_verified_subkeys(self.raw_expired_key_bundle) # Test subkey with validity period 175451, i.e. ~ 2 days self.assertTrue(subkeys["0ce427fa3f0f50bc83a4a760ed95e1581691db4d"].get( "validity_period") == 175451) # Test subkey without validity period, i.e. it does not expire self.assertTrue(subkeys["70cfabf1e2f1dc60ac5c7bca10cd20d3d5bcb6ef"].get( "validity_period") == None) def test_get_pubkey_bundle_errors(self): """Test correct error raising in get_pubkey_bundle. """ # Call without key data with self.assertRaises(KeyNotFoundError): get_pubkey_bundle(None, "deadbeef") # Pass wrong keyid with valid gpg data to trigger KeyNotFoundError. not_associated_keyid = "8465A1E2E0FB2B40ADB2478E18FB3F537E0C8A17" with self.assertRaises(KeyNotFoundError): get_pubkey_bundle(self.raw_key_data, not_associated_keyid) def test_parse_signature_packet_errors(self): """Test parse_signature_packet errors with manually crafted data. """ # passed data | expected error message test_data = [ (bytearray([0b01000010, 1, 255]), "Signature version '255' not supported"), (bytearray([0b01000010, 2, 4, 255]), "Signature type '255' not supported"), (bytearray([0b01000010, 3, 4, 0, 255]), "Signature algorithm '255' not supported"), (bytearray([0b01000010, 4, 4, 0, 1, 255]), "Hash algorithm '255' not supported"), ] for data, expected_error_str in test_data: with self.assertRaises(ValueError) as ctx: parse_signature_packet(data) self.assertTrue(expected_error_str in str(ctx.exception), "'{}' not in '{}'".format(expected_error_str, str(ctx.exception))) @unittest.skipIf(not HAVE_GPG, "gpg not found") class TestGPGRSA(unittest.TestCase): """Test signature creation, verification and key export from the gpg module""" default_keyid = "8465A1E2E0FB2B40ADB2478E18FB3F537E0C8A17" signing_subkey_keyid = "C5A0ABE6EC19D0D65F85E2C39BE9DF5131D924E9" encryption_subkey_keyid = "6A112FD3390B2E53AFC2E57F8FC8E12099AECEEA" unsupported_subkey_keyid = "611A9B648E16F54E8A7FAD5DA51E8CDF3B06524F" expired_key_keyid = "E8AC80C924116DABB51D4B987CB07D6D2C199C7C" keyid_768C43 = "7B3ABB26B97B655AB9296BD15B0BD02E1C768C43" @classmethod def setUpClass(self): # Create directory to run the tests without having everything blow up self.working_dir = os.getcwd() # Find demo files gpg_keyring_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), "gpg_keyrings", "rsa") self.test_dir = os.path.realpath(tempfile.mkdtemp()) self.gnupg_home = os.path.join(self.test_dir, "rsa") shutil.copytree(gpg_keyring_path, self.gnupg_home) os.chdir(self.test_dir) @classmethod def tearDownClass(self): """Change back to initial working dir and remove temp test directory. """ os.chdir(self.working_dir) shutil.rmtree(self.test_dir) def test_export_pubkey_error(self): """Test correct error is raised if function called incorrectly. """ with self.assertRaises(ValueError): export_pubkey("not-a-key-id") def test_export_pubkey(self): """ export a public key and make sure the parameters are the right ones: since there's very little we can do to check rsa key parameters are right we pre-exported the public key to an ssh key, which we can load with cryptography for the sake of comparison """ # export our gpg key, using our functions key_data = export_pubkey(self.default_keyid, homedir=self.gnupg_home) our_exported_key = rsa_create_pubkey(key_data) # load the equivalent ssh key, and make sure that we get the same RSA key # parameters ssh_key_basename = "{}.ssh".format(self.default_keyid) ssh_key_path = os.path.join(self.gnupg_home, ssh_key_basename) with open(ssh_key_path, "rb") as fp: keydata = fp.read() ssh_key = serialization.load_ssh_public_key(keydata, backends.default_backend()) self.assertEqual(ssh_key.public_numbers().n, our_exported_key.public_numbers().n) self.assertEqual(ssh_key.public_numbers().e, our_exported_key.public_numbers().e) subkey_keyids = list(key_data["subkeys"].keys()) # We export the whole master key bundle which must contain the subkeys self.assertTrue(self.signing_subkey_keyid.lower() in subkey_keyids) # Currently we do not exclude encryption subkeys self.assertTrue(self.encryption_subkey_keyid.lower() in subkey_keyids) # However we do exclude subkeys, whose algorithm we do not support self.assertFalse(self.unsupported_subkey_keyid.lower() in subkey_keyids) # When passing the subkey keyid we also export the whole keybundle key_data2 = export_pubkey(self.signing_subkey_keyid, homedir=self.gnupg_home) self.assertDictEqual(key_data, key_data2) def test_export_pubkeys(self): """Test export multiple pubkeys at once. """ key_dict = export_pubkeys([self.default_keyid, self.keyid_768C43], homedir=self.gnupg_home) ANY_PUBKEY_DICT_SCHEMA.check_match(key_dict) self.assertListEqual( sorted([self.default_keyid.lower(), self.keyid_768C43.lower()]), sorted(key_dict.keys())) def test_gpg_sign_and_verify_object_with_default_key(self): """Create a signature using the default key on the keyring """ test_data = b'test_data' wrong_data = b'something malicious' signature = create_signature(test_data, homedir=self.gnupg_home) key_data = export_pubkey(self.default_keyid, homedir=self.gnupg_home) self.assertTrue(verify_signature(signature, key_data, test_data)) self.assertFalse(verify_signature(signature, key_data, wrong_data)) def test_gpg_sign_and_verify_object(self): """Create a signature using a specific key on the keyring """ test_data = b'test_data' wrong_data = b'something malicious' signature = create_signature(test_data, keyid=self.default_keyid, homedir=self.gnupg_home) key_data = export_pubkey(self.default_keyid, homedir=self.gnupg_home) self.assertTrue(verify_signature(signature, key_data, test_data)) self.assertFalse(verify_signature(signature, key_data, wrong_data)) def test_gpg_sign_and_verify_object_default_keyring(self): """Sign/verify using keyring from envvar. """ test_data = b'test_data' gnupg_home_backup = os.environ.get("GNUPGHOME") os.environ["GNUPGHOME"] = self.gnupg_home signature = create_signature(test_data, keyid=self.default_keyid) key_data = export_pubkey(self.default_keyid) self.assertTrue(verify_signature(signature, key_data, test_data)) # Reset GNUPGHOME if gnupg_home_backup: os.environ["GNUPGHOME"] = gnupg_home_backup else: del os.environ["GNUPGHOME"] def test_create_signature_with_expired_key(self): """Test signing with expired key raises gpg CommandError. """ with self.assertRaises(CommandError) as ctx: create_signature(b"livestock", keyid=self.expired_key_keyid, homedir=self.gnupg_home) expected = "returned non-zero exit status '2'" self.assertTrue(expected in str(ctx.exception), "{} not in {}".format( expected, ctx.exception)) def test_verify_signature_with_expired_key(self): """Test sig verification with expired key raises KeyExpirationError. """ signature = { "keyid": self.expired_key_keyid, "other_headers": "deadbeef", "signature": "deadbeef", } content = b"livestock" key = export_pubkey(self.expired_key_keyid, homedir=self.gnupg_home) with self.assertRaises(KeyExpirationError) as ctx: verify_signature(signature, key, content) expected = ("GPG key 'e8ac80c924116dabb51d4b987cb07d6d2c199c7c' " "created on '2019-03-25 12:46 UTC' with validity period '1 day, " "0:25:01' expired on '2019-03-26 13:11 UTC'.") self.assertTrue(expected == str(ctx.exception), "\nexpected: {}" "\ngot: {}".format(expected, ctx.exception)) @unittest.skipIf(not HAVE_GPG, "gpg not found") class TestGPGDSA(unittest.TestCase): """ Test signature creation, verification and key export from the gpg module """ default_keyid = "C242A830DAAF1C2BEF604A9EF033A3A3E267B3B1" @classmethod def setUpClass(self): # Create directory to run the tests without having everything blow up self.working_dir = os.getcwd() self.test_dir = os.path.realpath(tempfile.mkdtemp()) self.gnupg_home = os.path.join(self.test_dir, "dsa") # Find keyrings keyrings = os.path.join( os.path.dirname(os.path.realpath(__file__)), "gpg_keyrings", "dsa") shutil.copytree(keyrings, self.gnupg_home) os.chdir(self.test_dir) @classmethod def tearDownClass(self): """Change back to initial working dir and remove temp test directory. """ os.chdir(self.working_dir) shutil.rmtree(self.test_dir) def test_export_pubkey(self): """ export a public key and make sure the parameters are the right ones: since there's very little we can do to check key parameters are right we pre-exported the public key to an x.509 SubjectPublicKeyInfo key, which we can load with cryptography for the sake of comparison """ # export our gpg key, using our functions key_data = export_pubkey(self.default_keyid, homedir=self.gnupg_home) our_exported_key = dsa_create_pubkey(key_data) # load same key, pre-exported with 3rd-party tooling pem_key_basename = "{}.pem".format(self.default_keyid) pem_key_path = os.path.join(self.gnupg_home, pem_key_basename) with open(pem_key_path, "rb") as fp: keydata = fp.read() pem_key = serialization.load_pem_public_key(keydata, backends.default_backend()) # make sure keys match self.assertEqual(pem_key.public_numbers().y, our_exported_key.public_numbers().y) self.assertEqual(pem_key.public_numbers().parameter_numbers.g, our_exported_key.public_numbers().parameter_numbers.g) self.assertEqual(pem_key.public_numbers().parameter_numbers.q, our_exported_key.public_numbers().parameter_numbers.q) self.assertEqual(pem_key.public_numbers().parameter_numbers.p, our_exported_key.public_numbers().parameter_numbers.p) def test_gpg_sign_and_verify_object_with_default_key(self): """Create a signature using the default key on the keyring """ test_data = b'test_data' wrong_data = b'something malicious' signature = create_signature(test_data, homedir=self.gnupg_home) key_data = export_pubkey(self.default_keyid, homedir=self.gnupg_home) self.assertTrue(verify_signature(signature, key_data, test_data)) self.assertFalse(verify_signature(signature, key_data, wrong_data)) def test_gpg_sign_and_verify_object(self): """Create a signature using a specific key on the keyring """ test_data = b'test_data' wrong_data = b'something malicious' signature = create_signature(test_data, keyid=self.default_keyid, homedir=self.gnupg_home) key_data = export_pubkey(self.default_keyid, homedir=self.gnupg_home) self.assertTrue(verify_signature(signature, key_data, test_data)) self.assertFalse(verify_signature(signature, key_data, wrong_data)) @unittest.skipIf(not HAVE_GPG, "gpg not found") class TestGPGEdDSA(unittest.TestCase): """ Test signature creation, verification and key export from the gpg module """ default_keyid = "4E630F84838BF6F7447B830B22692F5FEA9E2DD2" @classmethod def setUpClass(self): # Create directory to run the tests without having everything blow up self.working_dir = os.getcwd() self.test_dir = os.path.realpath(tempfile.mkdtemp()) self.gnupg_home = os.path.join(self.test_dir, "dsa") # Find keyrings keyrings = os.path.join( os.path.dirname(os.path.realpath(__file__)), "gpg_keyrings", "eddsa") shutil.copytree(keyrings, self.gnupg_home) os.chdir(self.test_dir) @classmethod def tearDownClass(self): """Change back to initial working dir and remove temp test directory. """ os.chdir(self.working_dir) shutil.rmtree(self.test_dir) def test_gpg_sign_and_verify_object_with_default_key(self): """Create a signature using the default key on the keyring """ test_data = b'test_data' wrong_data = b'something malicious' signature = create_signature(test_data, homedir=self.gnupg_home) key_data = export_pubkey(self.default_keyid, homedir=self.gnupg_home) self.assertTrue(verify_signature(signature, key_data, test_data)) self.assertFalse(verify_signature(signature, key_data, wrong_data)) def test_gpg_sign_and_verify_object_with_specific_key(self): """Create a signature using a specific key on the keyring """ test_data = b'test_data' wrong_data = b'something malicious' signature = create_signature(test_data, keyid=self.default_keyid, homedir=self.gnupg_home) key_data = export_pubkey(self.default_keyid, homedir=self.gnupg_home) self.assertTrue(verify_signature(signature, key_data, test_data)) self.assertFalse(verify_signature(signature, key_data, wrong_data)) def test_verify_short_signature(self): """Correctly verify a special-crafted short signature. """ test_data = b"hello" signature_path = os.path.join(self.gnupg_home, "short.sig") # Read special-crafted raw gpg signature that is one byte too short with open(signature_path, "rb") as f: signature_data = f.read() # Check that the signature is padded upon parsing # NOTE: The returned signature is a hex string and thus twice as long signature = parse_signature_packet(signature_data) self.assertTrue(len(signature["signature"]) == (ED25519_SIG_LENGTH * 2)) # Check that the signature can be successfully verified key = export_pubkey(self.default_keyid, homedir=self.gnupg_home) self.assertTrue(verify_signature(signature, key, test_data)) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_hash.py0000755000076500000240000002537600000000000021013 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ test_hash.py Geremy Condra Vladimir Diaz Refactored March 1, 2012 (VLAD). Based on a previous version of this module. See LICENSE for licensing information. Unit test for 'hash.py'. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import os import logging import sys import tempfile import unittest import securesystemslib.exceptions import securesystemslib.hash import six logger = logging.getLogger(__name__) if not 'hashlib' in securesystemslib.hash.SUPPORTED_LIBRARIES: logger.warning('Not testing hashlib: could not be imported.') class TestHash(unittest.TestCase): @staticmethod def _is_supported_combination(library, algorithm): blake_algos = ['blake2b', 'blake2b-256', 'blake2s'] # pyca does not support blake2* if algorithm in blake_algos: if library == 'pyca_crypto': return False # hashlib does not support blake2* if < 3.6 elif library == 'hashlib' and sys.version_info[:2] < (3, 6): return False return True def _run_with_all_algos_and_libs(self, test_func): algorithms = [ 'md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'blake2b-256', 'blake2b', 'blake2s', ] for algorithm in algorithms: self._run_with_all_hash_libraries(test_func, algorithm) def _run_with_all_hash_libraries(self, test_func, algorithm): for lib in securesystemslib.hash.SUPPORTED_LIBRARIES: if self._is_supported_combination(lib, algorithm): test_func(lib, algorithm) else: self.assertRaises( securesystemslib.exceptions.UnsupportedAlgorithmError, test_func, lib, algorithm) def _do_algorithm_update(self, library, algorithm): expected = { 'blake2b': [ '786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce', '333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34d5abe2c6b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c', 'e1161a4e6e6ed9da6928b5e96c24d5b957018f997994f16c05497af059d4f32bb80b34f478aa1fc173f6e45d859958c891e53c2c0bf8eda7c6d3917263641b46', 'e1161a4e6e6ed9da6928b5e96c24d5b957018f997994f16c05497af059d4f32bb80b34f478aa1fc173f6e45d859958c891e53c2c0bf8eda7c6d3917263641b46', ], 'blake2b-256': [ '0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8', '8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4', '92af150df67e34827f3c13239c4d11cad6f488b447f72e844c10fce6c651e9f0', '92af150df67e34827f3c13239c4d11cad6f488b447f72e844c10fce6c651e9f0', ], 'blake2s': [ '69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9', '4a0d129873403037c2cd9b9048203687f6233fb6738956e0349bd4320fec3e90', '2b68156e70f71280f7ad021f74620446ee49613a7ed34f5220da7b1dbae9adb2', '2b68156e70f71280f7ad021f74620446ee49613a7ed34f5220da7b1dbae9adb2', ], 'md5': [ 'd41d8cd98f00b204e9800998ecf8427e', '0cc175b9c0f1b6a831c399e269772661', 'f034e93091235adbb5d2781908e2b313', 'f034e93091235adbb5d2781908e2b313', ], 'sha1': [ 'da39a3ee5e6b4b0d3255bfef95601890afd80709', '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', 'd7bfa42fc62b697bf6cf1cda9af1fb7f40a27817', 'd7bfa42fc62b697bf6cf1cda9af1fb7f40a27817', ], 'sha224': [ 'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f', 'abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5', 'ab1342f31c2a6f242d9a3cefb503fb49465c95eb255c16ad791d688c', 'ab1342f31c2a6f242d9a3cefb503fb49465c95eb255c16ad791d688c', ], 'sha256': [ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb', '01d162a5c95d4698c0a3e766ae80d85994b549b877ed275803725f43dadc83bd', '01d162a5c95d4698c0a3e766ae80d85994b549b877ed275803725f43dadc83bd', ], 'sha384': [ '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b', '54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31', 'f2c1438e9cc1d24bebbf3b88e60adc169db0c5c459d02054ec131438bf20ebee5ca88c17cb5f1a824fcccf8d2b20b0a9', 'f2c1438e9cc1d24bebbf3b88e60adc169db0c5c459d02054ec131438bf20ebee5ca88c17cb5f1a824fcccf8d2b20b0a9', ], 'sha512': [ 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e', '1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75', '09ade82ae3c5d54f8375f348563a372106488adef16a74b63b5591849f740bff55ceab22e117b4b09349b860f8a644adb32a9ea542abdecb80bf625160604251', '09ade82ae3c5d54f8375f348563a372106488adef16a74b63b5591849f740bff55ceab22e117b4b09349b860f8a644adb32a9ea542abdecb80bf625160604251', ], } digest_object = securesystemslib.hash.digest(algorithm, library) self.assertEqual(digest_object.hexdigest(), expected[algorithm][0]) digest_object.update('a'.encode('utf-8')) self.assertEqual(digest_object.hexdigest(), expected[algorithm][1]) digest_object.update('bbb'.encode('utf-8')) self.assertEqual(digest_object.hexdigest(), expected[algorithm][2]) digest_object.update(''.encode('utf-8')) self.assertEqual(digest_object.hexdigest(), expected[algorithm][3]) def test_blake2s_update(self): self._run_with_all_hash_libraries(self._do_algorithm_update, 'blake2s') def test_blake2b_update(self): self._run_with_all_hash_libraries(self._do_algorithm_update, 'blake2b') def test_blake2b_256_update(self): self._run_with_all_hash_libraries(self._do_algorithm_update, 'blake2b-256') def test_md5_update(self): self._run_with_all_hash_libraries(self._do_algorithm_update, 'md5') def test_sha1_update(self): self._run_with_all_hash_libraries(self._do_algorithm_update, 'sha1') def test_sha224_update(self): self._run_with_all_hash_libraries(self._do_algorithm_update, 'sha224') def test_sha256_update(self): self._run_with_all_hash_libraries(self._do_algorithm_update, 'sha256') def test_sha384_update(self): self._run_with_all_hash_libraries(self._do_algorithm_update, 'sha384') def test_sha512_update(self): self._run_with_all_hash_libraries(self._do_algorithm_update, 'sha512') def test_unsupported_algorithm(self): self._run_with_all_hash_libraries(self._do_unsupported_algorithm, 'bogus') def _do_unsupported_algorithm(self, library, algorithm): self.assertRaises(securesystemslib.exceptions.UnsupportedAlgorithmError, securesystemslib.hash.digest, algorithm, library) def test_digest_size(self): self._run_with_all_algos_and_libs(self._do_digest_size) def _do_digest_size(self, library, algorithm): digest_sizes = { 'md5': 16, 'sha1': 20, 'sha224': 28, 'sha256': 32, 'sha384': 48, 'sha512': 64, 'blake2b-256': 32, 'blake2b': 64, 'blake2s': 32, } self.assertEqual(digest_sizes[algorithm], securesystemslib.hash.digest(algorithm, library).digest_size) def test_update_filename(self): self._run_with_all_algos_and_libs(self._do_update_filename) def _do_update_filename(self, library, algorithm): data = 'abcdefgh' * 4096 fd, filename = tempfile.mkstemp() try: os.write(fd, data.encode('utf-8')) os.close(fd) digest_object_truth = securesystemslib.hash.digest(algorithm, library) digest_object_truth.update(data.encode('utf-8')) digest_object = securesystemslib.hash.digest_filename(filename, algorithm, library) self.assertEqual(digest_object_truth.digest(), digest_object.digest()) finally: os.remove(filename) def test_update_filename_normalize(self): self._run_with_all_algos_and_libs(self._do_update_filename_normalize) def _do_update_filename_normalize(self, library, algorithm): data = b'ab\r\nd\nf\r' * 4096 normalized_data = data.replace(b'\r\n', b'\n').replace(b'\r', b'\n') fd, filename = tempfile.mkstemp() try: os.write(fd, data) os.close(fd) digest_object_truth = securesystemslib.hash.digest(algorithm, library) digest_object_truth.update(normalized_data) digest_object = securesystemslib.hash.digest_filename(filename, algorithm, library, normalize_line_endings=True) self.assertEqual(digest_object_truth.digest(), digest_object.digest()) finally: os.remove(filename) def test_update_file_obj(self): self._run_with_all_algos_and_libs(self._do_update_file_obj) def _do_update_file_obj(self, library, algorithm): data = 'abcdefgh' * 4096 file_obj = six.StringIO() file_obj.write(data) digest_object_truth = securesystemslib.hash.digest(algorithm, library) digest_object_truth.update(data.encode('utf-8')) digest_object = securesystemslib.hash.digest_fileobject(file_obj, algorithm, library) # Note: we don't seek because the update_file_obj call is supposed # to always seek to the beginning. self.assertEqual(digest_object_truth.digest(), digest_object.digest()) def test_digest_from_rsa_scheme(self): self._run_with_all_hash_libraries(self._do_get_digest_from_rsa_valid_schemes, 'sha256') self._run_with_all_hash_libraries(self._do_get_digest_from_rsa_non_valid_schemes, 'sha256') def _do_get_digest_from_rsa_valid_schemes(self, library, algorithm): scheme = 'rsassa-pss-sha256' expected_digest_cls = type(securesystemslib.hash.digest(algorithm, library)) self.assertIsInstance(securesystemslib.hash.digest_from_rsa_scheme(scheme, library), expected_digest_cls) def _do_get_digest_from_rsa_non_valid_schemes(self, library, algorithm): self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.hash.digest_from_rsa_scheme, 'rsassa-pss-sha123', library) def test_unsupported_digest_algorithm_and_library(self): self.assertRaises(securesystemslib.exceptions.UnsupportedAlgorithmError, securesystemslib.hash.digest, 'sha123', 'hashlib') self.assertRaises(securesystemslib.exceptions.UnsupportedLibraryError, securesystemslib.hash.digest, 'sha256', 'badlib') # Run unit test. if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_interface.py0000755000076500000240000007146700000000000022032 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ test_interface.py Vladimir Diaz January 5, 2017. See LICENSE for licensing information. Unit test for 'interface.py'. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import os import time import datetime import tempfile import json import shutil import stat import sys import unittest from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.hazmat.backends import default_backend # Use external backport 'mock' on versions under 3.3 if sys.version_info >= (3, 3): import unittest.mock as mock else: import mock from securesystemslib import ( KEY_TYPE_RSA, KEY_TYPE_ED25519, KEY_TYPE_ECDSA) from securesystemslib.formats import ( RSAKEY_SCHEMA, PUBLIC_KEY_SCHEMA, ANY_PUBKEY_DICT_SCHEMA, ED25519KEY_SCHEMA, ECDSAKEY_SCHEMA) from securesystemslib.exceptions import Error, FormatError, CryptoError from securesystemslib.interface import ( _generate_and_write_rsa_keypair, generate_and_write_rsa_keypair, generate_and_write_rsa_keypair_with_prompt, generate_and_write_unencrypted_rsa_keypair, import_rsa_privatekey_from_file, import_rsa_publickey_from_file, _generate_and_write_ed25519_keypair, generate_and_write_ed25519_keypair, generate_and_write_ed25519_keypair_with_prompt, generate_and_write_unencrypted_ed25519_keypair, import_ed25519_publickey_from_file, import_ed25519_privatekey_from_file, _generate_and_write_ecdsa_keypair, generate_and_write_ecdsa_keypair, generate_and_write_ecdsa_keypair_with_prompt, generate_and_write_unencrypted_ecdsa_keypair, import_ecdsa_publickey_from_file, import_ecdsa_privatekey_from_file, import_publickeys_from_file, import_privatekey_from_file) class TestInterfaceFunctions(unittest.TestCase): @classmethod def setUpClass(cls): cls.test_data_dir = os.path.join( os.path.dirname(os.path.realpath(__file__)), "data") cls.path_rsa = os.path.join( cls.test_data_dir, "keystore", "rsa_key") cls.path_ed25519 = os.path.join( cls.test_data_dir, "keystore", "ed25519_key") cls.path_ecdsa = os.path.join( cls.test_data_dir, "keystore", "ecdsa_key") cls.path_no_key = os.path.join( cls.test_data_dir, "keystore", "no_key") cls.orig_cwd = os.getcwd() def setUp(self): self.tmp_dir = tempfile.mkdtemp(dir=self.orig_cwd) os.chdir(self.tmp_dir) def tearDown(self): os.chdir(self.orig_cwd) shutil.rmtree(self.tmp_dir) def test_rsa(self): """Test RSA key _generation and import interface functions. """ # TEST: Generate default keys and import # Assert location and format fn_default = "default" fn_default_ret = _generate_and_write_rsa_keypair(filepath=fn_default) pub = import_rsa_publickey_from_file(fn_default + ".pub") priv = import_rsa_privatekey_from_file(fn_default) self.assertEqual(fn_default, fn_default_ret) self.assertTrue(RSAKEY_SCHEMA.matches(pub)) self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) self.assertTrue(RSAKEY_SCHEMA.matches(priv)) # NOTE: There is no private key schema, at least check it has a value self.assertTrue(priv["keyval"]["private"]) # TEST: Generate unencrypted keys with empty prompt # Assert importable without password fn_empty_prompt = "empty_prompt" with mock.patch("securesystemslib.interface.get_password", return_value=""): _generate_and_write_rsa_keypair(filepath=fn_empty_prompt, prompt=True) import_rsa_privatekey_from_file(fn_empty_prompt) # TEST: Generate keys with auto-filename, i.e. keyid # Assert filename is keyid fn_keyid = _generate_and_write_rsa_keypair() pub = import_rsa_publickey_from_file(fn_keyid + ".pub") priv = import_rsa_privatekey_from_file(fn_keyid) self.assertTrue( os.path.basename(fn_keyid) == pub["keyid"] == priv["keyid"]) # TEST: Generate keys with custom bits # Assert length bits = 4096 fn_bits = "bits" _generate_and_write_rsa_keypair(filepath=fn_bits, bits=bits) priv = import_rsa_privatekey_from_file(fn_bits) # NOTE: Parse PEM with pyca/cryptography to get the key size property obj_bits = load_pem_private_key( priv["keyval"]["private"].encode("utf-8"), password=None, backend=default_backend()) self.assertEqual(obj_bits.key_size, bits) # TEST: Generate two keypairs with encrypted private keys using ... pw = "pw" fn_encrypted = "encrypted" fn_prompt = "prompt" # ... a passed pw ... _generate_and_write_rsa_keypair(filepath=fn_encrypted, password=pw) with mock.patch("securesystemslib.interface.get_password", return_value=pw): # ... and a prompted pw. _generate_and_write_rsa_keypair(filepath=fn_prompt, prompt=True) # Assert that both private keys are importable using the prompted pw ... import_rsa_privatekey_from_file(fn_prompt, prompt=True) import_rsa_privatekey_from_file(fn_encrypted, prompt=True) # ... and the passed pw. import_rsa_privatekey_from_file(fn_prompt, password=pw) import_rsa_privatekey_from_file(fn_encrypted, password=pw) # TEST: Import existing keys with encrypted private key (test regression) # Assert format pub = import_rsa_publickey_from_file(self.path_rsa + ".pub") priv = import_rsa_privatekey_from_file(self.path_rsa, "password") self.assertTrue(RSAKEY_SCHEMA.matches(pub)) self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) self.assertTrue(RSAKEY_SCHEMA.matches(priv)) # NOTE: There is no private key schema, at least check it has a value self.assertTrue(priv["keyval"]["private"]) # TEST: Generation errors for idx, (kwargs, err_msg) in enumerate([ # Error on empty password ({"password": ""}, "encryption password must be 1 or more characters long"), # Error on 'password' and 'prompt=True' ({"password": pw, "prompt": True}, "passing 'password' and 'prompt=True' is not allowed")]): with self.assertRaises(ValueError, msg="(row {})".format(idx)) as ctx: _generate_and_write_rsa_keypair(**kwargs) self.assertEqual(err_msg, str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on bad argument format for idx, kwargs in enumerate([ {"bits": 1024}, # Too low {"bits": "not-an-int"}, {"filepath": 123456}, # Not a string {"password": 123456}, # Not a string {"prompt": "not-a-bool"}]): with self.assertRaises(FormatError, msg="(row {})".format(idx)): _generate_and_write_rsa_keypair(**kwargs) # TEST: Import errors # Error public key import err_msg = "Invalid public pem" with self.assertRaises(Error) as ctx: import_rsa_publickey_from_file(fn_default) self.assertTrue(err_msg in str(ctx.exception), "expected: '{}' got: '{}'".format(err_msg, ctx.exception)) # Error on private key import... for idx, (args, kwargs, err, err_msg) in enumerate([ # Error on not a private key ([fn_default + ".pub"], {}, CryptoError, "Could not deserialize key data"), # Error on not encrypted ([fn_default], {"password": pw}, CryptoError, "Password was given but private key is not encrypted"), # Error on encrypted but no pw ([fn_encrypted], {}, CryptoError, "Password was not given but private key is encrypted"), # Error on encrypted but empty pw passed ([fn_encrypted], {"password": ""}, CryptoError, "Password was not given but private key is encrypted"), # Error on encrypted but bad pw passed ([fn_encrypted], {"password": "bad pw"}, CryptoError, "Bad decrypt. Incorrect password?"), # Error on pw and prompt ([fn_default], {"password": pw, "prompt": True}, ValueError, "passing 'password' and 'prompt=True' is not allowed")]): with self.assertRaises(err, msg="(row {})".format(idx)) as ctx: import_rsa_privatekey_from_file(*args, **kwargs) self.assertTrue(err_msg in str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on encrypted but bad pw prompted err_msg = "Password was not given but private key is encrypted" with self.assertRaises(CryptoError) as ctx, mock.patch( "securesystemslib.interface.get_password", return_value="bad_pw"): import_rsa_privatekey_from_file(fn_encrypted) self.assertTrue(err_msg in str(ctx.exception), "expected: '{}' got: '{}'".format(err_msg, ctx.exception)) # Error on bad argument format for idx, (args, kwargs) in enumerate([ ([123456], {}), # bad path ([fn_default], {"scheme": 123456}), # bad scheme ([fn_default], {"scheme": "bad scheme"}) # bad scheme ]): with self.assertRaises(FormatError, msg="(row {})".format(idx)): import_rsa_publickey_from_file(*args, **kwargs) with self.assertRaises(FormatError, msg="(row {})".format(idx)): import_rsa_privatekey_from_file(*args, **kwargs) # bad password with self.assertRaises(FormatError): import_rsa_privatekey_from_file(fn_default, password=123456) # bad prompt with self.assertRaises(FormatError): import_rsa_privatekey_from_file(fn_default, prompt="not-a-bool") def test_ed25519(self): """Test ed25519 key _generation and import interface functions. """ # TEST: Generate default keys and import # Assert location and format fn_default = "default" fn_default_ret = _generate_and_write_ed25519_keypair(filepath=fn_default) pub = import_ed25519_publickey_from_file(fn_default + ".pub") priv = import_ed25519_privatekey_from_file(fn_default) self.assertEqual(fn_default, fn_default_ret) self.assertTrue(ED25519KEY_SCHEMA.matches(pub)) self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) self.assertTrue(ED25519KEY_SCHEMA.matches(priv)) # NOTE: There is no private key schema, at least check it has a value self.assertTrue(priv["keyval"]["private"]) # TEST: Generate unencrypted keys with empty prompt # Assert importable with empty prompt password and without password fn_empty_prompt = "empty_prompt" with mock.patch("securesystemslib.interface.get_password", return_value=""): _generate_and_write_ed25519_keypair(filepath=fn_empty_prompt) import_ed25519_privatekey_from_file(fn_empty_prompt, prompt=True) import_ed25519_privatekey_from_file(fn_empty_prompt) # TEST: Generate keys with auto-filename, i.e. keyid # Assert filename is keyid fn_keyid = _generate_and_write_ed25519_keypair() pub = import_ed25519_publickey_from_file(fn_keyid + ".pub") priv = import_ed25519_privatekey_from_file(fn_keyid) self.assertTrue( os.path.basename(fn_keyid) == pub["keyid"] == priv["keyid"]) # TEST: Generate two keypairs with encrypted private keys using ... pw = "pw" fn_encrypted = "encrypted" fn_prompt = "prompt" # ... a passed pw ... _generate_and_write_ed25519_keypair(filepath=fn_encrypted, password=pw) with mock.patch("securesystemslib.interface.get_password", return_value=pw): # ... and a prompted pw. _generate_and_write_ed25519_keypair(filepath=fn_prompt, prompt=True) # Assert that both private keys are importable using the prompted pw ... import_ed25519_privatekey_from_file(fn_prompt, prompt=True) import_ed25519_privatekey_from_file(fn_encrypted, prompt=True) # ... and the passed pw. import_ed25519_privatekey_from_file(fn_prompt, password=pw) import_ed25519_privatekey_from_file(fn_encrypted, password=pw) # TEST: Import existing keys with encrypted private key (test regression) # Assert format pub = import_ed25519_publickey_from_file(self.path_ed25519 + ".pub") priv = import_ed25519_privatekey_from_file(self.path_ed25519, "password") self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) self.assertTrue(ED25519KEY_SCHEMA.matches(pub)) self.assertTrue(ED25519KEY_SCHEMA.matches(priv)) # NOTE: There is no private key schema, at least check it has a value self.assertTrue(priv["keyval"]["private"]) # TEST: Unexpected behavior # FIXME: Should 'import_ed25519_publickey_from_file' be able to import a # a non-encrypted ed25519 private key? I think it should not, but it is: priv = import_ed25519_publickey_from_file(fn_default) self.assertTrue(ED25519KEY_SCHEMA.matches(priv)) self.assertTrue(priv["keyval"]["private"]) # FIXME: Should 'import_ed25519_privatekey_from_file' be able to import a # an ed25519 public key? I think it should not, but it is: pub = import_ed25519_privatekey_from_file(fn_default + ".pub") self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) # TEST: Generation errors for idx, (kwargs, err_msg) in enumerate([ # Error on empty password ({"password": ""}, "encryption password must be 1 or more characters long"), # Error on 'password' and 'prompt=True' ({"password": pw, "prompt": True}, "passing 'password' and 'prompt=True' is not allowed")]): with self.assertRaises(ValueError, msg="(row {})".format(idx)) as ctx: _generate_and_write_ed25519_keypair(**kwargs) self.assertEqual(err_msg, str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on bad argument format for idx, kwargs in enumerate([ {"filepath": 123456}, # Not a string {"password": 123456}, # Not a string {"prompt": "not-a-bool"}]): with self.assertRaises(FormatError, msg="(row {})".format(idx)): _generate_and_write_ed25519_keypair(**kwargs) # TEST: Import errors # Error on public key import... for idx, (fn, err_msg) in enumerate([ # Error on invalid json (custom key format) (fn_encrypted, "Cannot deserialize to a Python object"), # Error on invalid custom key format (self.path_no_key, "Missing key" ), # Error on invalid key type (self.path_ecdsa + ".pub", "Invalid key type loaded")]): with self.assertRaises(Error, msg="(row {})".format(idx)) as ctx: import_ed25519_publickey_from_file(fn) self.assertTrue(err_msg in str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on private key import... for idx, (args, kwargs, err, err_msg) in enumerate([ # Error on not an ed25519 private key ([self.path_ecdsa], {}, CryptoError, "Malformed Ed25519 key JSON, possibly due to encryption, " "but no password provided?"), # Error on not encrypted ([fn_default], {"password": pw}, CryptoError, "Invalid encrypted file."), # Error on encrypted but no pw ([fn_encrypted], {}, CryptoError, "Malformed Ed25519 key JSON, possibly due to encryption, " "but no password provided?"), # Error on encrypted but empty pw ([fn_encrypted], {"password": ""}, CryptoError, "Decryption failed."), # Error on encrypted but bad pw passed ([fn_encrypted], {"password": "bad pw"}, CryptoError, "Decryption failed."), # Error on pw and prompt ([fn_default], {"password": pw, "prompt": True}, ValueError, "passing 'password' and 'prompt=True' is not allowed")]): with self.assertRaises(err, msg="(row {})".format(idx)) as ctx: import_ed25519_privatekey_from_file(*args, **kwargs) self.assertTrue(err_msg in str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on encrypted but bad pw prompted err_msg = ("Malformed Ed25519 key JSON, possibly due to encryption, " "but no password provided?") with self.assertRaises(CryptoError) as ctx, mock.patch( "securesystemslib.interface.get_password", return_value="bad_pw"): import_ed25519_privatekey_from_file(fn_encrypted) self.assertTrue(err_msg in str(ctx.exception), "expected: '{}' got: '{}'".format(err_msg, ctx.exception)) # Error on bad path format with self.assertRaises(FormatError): import_ed25519_publickey_from_file(123456) with self.assertRaises(FormatError): import_ed25519_privatekey_from_file(123456) # Error on bad password format with self.assertRaises(FormatError): import_ed25519_privatekey_from_file(fn_default, password=123456) # Error on bad prompt format with self.assertRaises(FormatError): import_ed25519_privatekey_from_file(fn_default, prompt="not-a-bool") def test_ecdsa(self): """Test ecdsa key _generation and import interface functions. """ # TEST: Generate default keys and import # Assert location and format fn_default = "default" fn_default_ret = _generate_and_write_ecdsa_keypair(filepath=fn_default) pub = import_ecdsa_publickey_from_file(fn_default + ".pub") priv = import_ecdsa_privatekey_from_file(fn_default) self.assertEqual(fn_default, fn_default_ret) self.assertTrue(ECDSAKEY_SCHEMA.matches(pub)) self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) self.assertTrue(ECDSAKEY_SCHEMA.matches(priv)) # NOTE: There is no private key schema, at least check it has a value self.assertTrue(priv["keyval"]["private"]) # TEST: Generate unencrypted keys with empty prompt # Assert importable with empty prompt password and without password fn_empty_prompt = "empty_prompt" with mock.patch("securesystemslib.interface.get_password", return_value=""): _generate_and_write_ecdsa_keypair(filepath=fn_empty_prompt) import_ecdsa_privatekey_from_file(fn_empty_prompt, prompt=True) import_ecdsa_privatekey_from_file(fn_empty_prompt) # TEST: Generate keys with auto-filename, i.e. keyid # Assert filename is keyid fn_keyid = _generate_and_write_ecdsa_keypair() pub = import_ecdsa_publickey_from_file(fn_keyid + ".pub") priv = import_ecdsa_privatekey_from_file(fn_keyid) self.assertTrue( os.path.basename(fn_keyid) == pub["keyid"] == priv["keyid"]) # TEST: Generate two key pairs with encrypted private keys using ... pw = "pw" fn_encrypted = "encrypted" fn_prompt = "prompt" # ... a passed pw ... _generate_and_write_ecdsa_keypair(filepath=fn_encrypted, password=pw) with mock.patch("securesystemslib.interface.get_password", return_value=pw): # ... and a prompted pw. _generate_and_write_ecdsa_keypair(filepath=fn_prompt, prompt=True) # Assert that both private keys are importable using the prompted pw ... import_ecdsa_privatekey_from_file(fn_prompt, prompt=True) import_ecdsa_privatekey_from_file(fn_encrypted, prompt=True) # ... and the passed pw. import_ecdsa_privatekey_from_file(fn_prompt, password=pw) import_ecdsa_privatekey_from_file(fn_encrypted, password=pw) # TEST: Import existing keys with encrypted private key (test regression) # Assert format pub = import_ecdsa_publickey_from_file(self.path_ecdsa + ".pub") priv = import_ecdsa_privatekey_from_file(self.path_ecdsa, "password") self.assertTrue(ECDSAKEY_SCHEMA.matches(pub)) self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) self.assertTrue(ECDSAKEY_SCHEMA.matches(priv)) # NOTE: There is no private key schema, at least check it has a value self.assertTrue(priv["keyval"]["private"]) # FIXME: Should 'import_ecdsa_publickey_from_file' be able to import a # an ed25519 public key? I think it should not, but it is: import_ecdsa_publickey_from_file(self.path_ed25519 + ".pub") self.assertTrue(ECDSAKEY_SCHEMA.matches(pub)) # TEST: Generation errors for idx, (kwargs, err_msg) in enumerate([ # Error on empty password ({"password": ""}, "encryption password must be 1 or more characters long"), # Error on 'password' and 'prompt=True' ({"password": pw, "prompt": True}, "passing 'password' and 'prompt=True' is not allowed")]): with self.assertRaises(ValueError, msg="(row {})".format(idx)) as ctx: _generate_and_write_ecdsa_keypair(**kwargs) self.assertEqual(err_msg, str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on bad argument format for idx, kwargs in enumerate([ {"filepath": 123456}, # Not a string {"password": 123456}, # Not a string {"prompt": "not-a-bool"}]): with self.assertRaises(FormatError, msg="(row {})".format(idx)): _generate_and_write_ecdsa_keypair(**kwargs) # TEST: Import errors # Error on public key import... for idx, (fn, err_msg) in enumerate([ # Error on invalid json (custom key format) (fn_encrypted, "Cannot deserialize to a Python object"), # Error on invalid custom key format (self.path_no_key, "Missing key")]): with self.assertRaises(Error, msg="(row {})".format(idx)) as ctx: import_ecdsa_publickey_from_file(fn) self.assertTrue(err_msg in str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on private key import... for idx, (args, kwargs, err, err_msg) in enumerate([ # Error on not an ecdsa private key ([self.path_ed25519], {}, Error, "Cannot deserialize to a Python object"), # Error on not encrypted ([fn_default], {"password": pw}, CryptoError, "Invalid encrypted file."), # Error on encrypted but no pw ([fn_encrypted], {}, Error, "Cannot deserialize to a Python object"), # Error on encrypted but empty pw ([fn_encrypted], {"password": ""}, CryptoError, "Decryption failed."), # Error on encrypted but bad pw passed ([fn_encrypted], {"password": "bad pw"}, CryptoError, "Decryption failed."), # Error on pw and prompt ([fn_default], {"password": pw, "prompt": True}, ValueError, "passing 'password' and 'prompt=True' is not allowed")]): with self.assertRaises(err, msg="(row {})".format(idx)) as ctx: import_ecdsa_privatekey_from_file(*args, **kwargs) self.assertTrue(err_msg in str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on encrypted but bad pw prompted err_msg = ("Decryption failed") with self.assertRaises(CryptoError) as ctx, mock.patch( "securesystemslib.interface.get_password", return_value="bad_pw"): import_ecdsa_privatekey_from_file(fn_encrypted, prompt=True) self.assertTrue(err_msg in str(ctx.exception), "expected: '{}' got: '{}'".format(err_msg, ctx.exception)) # Error on bad path format with self.assertRaises(FormatError): import_ecdsa_publickey_from_file(123456) with self.assertRaises(FormatError): import_ecdsa_privatekey_from_file(123456) # Error on bad password format with self.assertRaises(FormatError): # bad password import_ecdsa_privatekey_from_file(fn_default, password=123456) # Error on bad prompt format with self.assertRaises(FormatError): import_ecdsa_privatekey_from_file(fn_default, prompt="not-a-bool") def test_generate_keypair_wrappers(self): """Basic tests for thin wrappers around _generate_and_write_*_keypair. See 'test_rsa', 'test_ed25519' and 'test_ecdsa' for more thorough key generation tests for each key type. """ key_pw = "pw" for idx, (gen, gen_prompt, gen_plain, import_priv, schema) in enumerate([ ( generate_and_write_rsa_keypair, generate_and_write_rsa_keypair_with_prompt, generate_and_write_unencrypted_rsa_keypair, import_rsa_privatekey_from_file, RSAKEY_SCHEMA ), ( generate_and_write_ed25519_keypair, generate_and_write_ed25519_keypair_with_prompt, generate_and_write_unencrypted_ed25519_keypair, import_ed25519_privatekey_from_file, ED25519KEY_SCHEMA ), ( generate_and_write_ecdsa_keypair, generate_and_write_ecdsa_keypair_with_prompt, generate_and_write_unencrypted_ecdsa_keypair, import_ecdsa_privatekey_from_file, ECDSAKEY_SCHEMA)]): assert_msg = "(row {})".format(idx) # Test generate_and_write_*_keypair creates an encrypted private key fn_encrypted = gen(key_pw) priv = import_priv(fn_encrypted, key_pw) self.assertTrue(schema.matches(priv), assert_msg) # Test generate_and_write_*_keypair errors if password is None or empty with self.assertRaises(FormatError, msg=assert_msg): fn_encrypted = gen(None) with self.assertRaises(ValueError, msg=assert_msg): fn_encrypted = gen("") # Test generate_and_write_*_keypair_with_prompt creates encrypted private # key with mock.patch( "securesystemslib.interface.get_password", return_value=key_pw): fn_prompt = gen_prompt() priv = import_priv(fn_prompt, key_pw) self.assertTrue(schema.matches(priv), assert_msg) # Test generate_and_write_*_keypair_with_prompt creates unencrypted # private key if no password is entered with mock.patch( "securesystemslib.interface.get_password", return_value=""): fn_empty_prompt = gen_prompt() priv = import_priv(fn_empty_prompt) self.assertTrue(schema.matches(priv), assert_msg) # Test generate_and_write_unencrypted_*_keypair doesn't encrypt fn_unencrypted = gen_plain() priv = import_priv(fn_unencrypted) self.assertTrue(schema.matches(priv), assert_msg) def test_import_publickeys_from_file(self): """Test import multiple public keys with different types. """ # Successfully import key dict with one key per supported key type key_dict = import_publickeys_from_file([ self.path_rsa + ".pub", self.path_ed25519 + ".pub", self.path_ecdsa + ".pub"], [KEY_TYPE_RSA, KEY_TYPE_ED25519, KEY_TYPE_ECDSA]) ANY_PUBKEY_DICT_SCHEMA.check_match(key_dict) self.assertListEqual( sorted([key["keytype"] for key in key_dict.values()]), sorted([KEY_TYPE_RSA, KEY_TYPE_ED25519, KEY_TYPE_ECDSA]) ) # Successfully import default rsa key key_dict = import_publickeys_from_file([self.path_rsa + ".pub"]) ANY_PUBKEY_DICT_SCHEMA.check_match(key_dict) RSAKEY_SCHEMA.check_match( list(key_dict.values()).pop()) # Bad default rsa key type for ed25519 with self.assertRaises(Error): import_publickeys_from_file([self.path_ed25519 + ".pub"]) # Bad ed25519 key type for rsa key with self.assertRaises(Error): import_publickeys_from_file( [self.path_rsa + ".pub"], [KEY_TYPE_ED25519]) # Unsupported key type with self.assertRaises(FormatError): import_publickeys_from_file( [self.path_ed25519 + ".pub"], ["KEY_TYPE_UNSUPPORTED"]) # Mismatching arguments lists lenghts with self.assertRaises(FormatError): import_publickeys_from_file( [self.path_rsa + ".pub", self.path_ed25519 + ".pub"], [KEY_TYPE_ED25519]) def test_import_privatekey_from_file(self): """Test generic private key import function. """ pw = "password" for idx, (path, key_type, key_schema) in enumerate([ (self.path_rsa, None, RSAKEY_SCHEMA), # default key type (self.path_rsa, KEY_TYPE_RSA, RSAKEY_SCHEMA), (self.path_ed25519, KEY_TYPE_ED25519, ED25519KEY_SCHEMA), (self.path_ecdsa, KEY_TYPE_ECDSA, ECDSAKEY_SCHEMA)]): # Successfully import key per supported type, with ... # ... passed password key = import_privatekey_from_file(path, key_type=key_type, password=pw) self.assertTrue(key_schema.matches(key), "(row {})".format(idx)) # ... entered password on mock-prompt with mock.patch("securesystemslib.interface.get_password", return_value=pw): key = import_privatekey_from_file(path, key_type=key_type, prompt=True) self.assertTrue(key_schema.matches(key), "(row {})".format(idx)) # Error on wrong key for default key type with self.assertRaises(Error): import_privatekey_from_file(self.path_ed25519, password=pw) # Error on unsupported key type with self.assertRaises(FormatError): import_privatekey_from_file( self.path_rsa, key_type="KEY_TYPE_UNSUPPORTED", password=pw) # Run the test cases. if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_keys.py0000755000076500000240000006603500000000000021040 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ test_keys.py Vladimir Diaz October 10, 2013. See LICENSE for licensing information. Test cases for test_keys.py. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import unittest import copy import securesystemslib.exceptions import securesystemslib.formats import securesystemslib.keys import securesystemslib.ecdsa_keys KEYS = securesystemslib.keys FORMAT_ERROR_MSG = 'securesystemslib.exceptions.FormatError was raised!' + \ ' Check object\'s format.' DATA_STR = 'SOME DATA REQUIRING AUTHENTICITY.' DATA = securesystemslib.formats.encode_canonical(DATA_STR).encode('utf-8') class TestKeys(unittest.TestCase): @classmethod def setUpClass(cls): cls.rsakey_dict = KEYS.generate_rsa_key() cls.ed25519key_dict = KEYS.generate_ed25519_key() cls.ecdsakey_dict = KEYS.generate_ecdsa_key() def test_generate_rsa_key(self): _rsakey_dict = KEYS.generate_rsa_key() # Check if the format of the object returned by generate() corresponds # to RSAKEY_SCHEMA format. self.assertEqual(None, securesystemslib.formats.RSAKEY_SCHEMA.check_match(_rsakey_dict), FORMAT_ERROR_MSG) # Passing a bit value that is <2048 to generate() - should raise # 'securesystemslib.exceptions.FormatError'. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.generate_rsa_key, 555) # Passing a string instead of integer for a bit value. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.generate_rsa_key, 'bits') # NOTE if random bit value >=2048 (not 4096) is passed generate(bits) # does not raise any errors and returns a valid key. self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(KEYS.generate_rsa_key(2048))) self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(KEYS.generate_rsa_key(4096))) def test_generate_ecdsa_key(self): _ecdsakey_dict = KEYS.generate_ecdsa_key() # Check if the format of the object returned by generate_ecdsa_key() # corresponds to ECDSAKEY_SCHEMA format. self.assertEqual(None, securesystemslib.formats.ECDSAKEY_SCHEMA.check_match(_ecdsakey_dict), FORMAT_ERROR_MSG) # Passing an invalid algorithm to generate() should raise # 'securesystemslib.exceptions.FormatError'. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.generate_rsa_key, 'bad_algorithm') # Passing a string instead of integer for a bit value. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.generate_rsa_key, 123) def test_format_keyval_to_metadata(self): keyvalue = self.rsakey_dict['keyval'] keytype = self.rsakey_dict['keytype'] scheme = self.rsakey_dict['scheme'] key_meta = KEYS.format_keyval_to_metadata(keytype, scheme, keyvalue) # Check if the format of the object returned by this function corresponds # to KEY_SCHEMA format. self.assertEqual(None, securesystemslib.formats.KEY_SCHEMA.check_match(key_meta), FORMAT_ERROR_MSG) key_meta = KEYS.format_keyval_to_metadata(keytype, scheme, keyvalue, private=True) # Check if the format of the object returned by this function corresponds # to KEY_SCHEMA format. self.assertEqual(None, securesystemslib.formats.KEY_SCHEMA.check_match(key_meta), FORMAT_ERROR_MSG) # Supplying a 'bad' keyvalue. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.format_keyval_to_metadata, 'bad_keytype', scheme, keyvalue, private=True) # Test for missing 'public' entry. public = keyvalue['public'] del keyvalue['public'] self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.format_keyval_to_metadata, keytype, scheme, keyvalue) keyvalue['public'] = public # Test for missing 'private' entry. private = keyvalue['private'] del keyvalue['private'] self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.format_keyval_to_metadata, keytype, scheme, keyvalue, private=True) keyvalue['private'] = private def test_import_rsakey_from_public_pem(self): pem = self.rsakey_dict['keyval']['public'] rsa_key = KEYS.import_rsakey_from_public_pem(pem) # Check if the format of the object returned by this function corresponds # to 'securesystemslib.formats.RSAKEY_SCHEMA' format. self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key)) # Verify whitespace is stripped. self.assertEqual(rsa_key, KEYS.import_rsakey_from_public_pem(pem + '\n')) # Supplying a 'bad_pem' argument. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_public_pem, 'bad_pem') # Supplying an improperly formatted PEM. # Strip the PEM header and footer. pem_header = '-----BEGIN PUBLIC KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_public_pem, pem[len(pem_header):]) pem_footer = '-----END PUBLIC KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_public_pem, pem[:-len(pem_footer)]) def test_format_metadata_to_key(self): # Copying self.rsakey_dict so that rsakey_dict remains # unchanged during and after this test execution. test_rsakey_dict = copy.copy(self.rsakey_dict) del test_rsakey_dict['keyid'] # Call format_metadata_to_key by using the default value for keyid_hash_algorithms rsakey_dict_from_meta_default, junk = KEYS.format_metadata_to_key(test_rsakey_dict) # Check if the format of the object returned by calling this function with # default hash algorithms e.g. securesystemslib.settings.HASH_ALGORITHMS corresponds # to RSAKEY_SCHEMA format. self.assertTrue( securesystemslib.formats.RSAKEY_SCHEMA.matches(rsakey_dict_from_meta_default), FORMAT_ERROR_MSG) self.assertTrue( securesystemslib.formats.KEY_SCHEMA.matches(rsakey_dict_from_meta_default), FORMAT_ERROR_MSG) # Call format_metadata_to_key by using custom value for keyid_hash_algorithms rsakey_dict_from_meta_custom, junk = KEYS.format_metadata_to_key(test_rsakey_dict, keyid_hash_algorithms=['sha384']) # Check if the format of the object returned by calling this function with # custom hash algorithms corresponds to RSAKEY_SCHEMA format. self.assertTrue( securesystemslib.formats.RSAKEY_SCHEMA.matches(rsakey_dict_from_meta_custom), FORMAT_ERROR_MSG) self.assertTrue( securesystemslib.formats.KEY_SCHEMA.matches(rsakey_dict_from_meta_custom), FORMAT_ERROR_MSG) test_rsakey_dict['keyid'] = self.rsakey_dict['keyid'] # Supplying a wrong number of arguments. self.assertRaises(TypeError, KEYS.format_metadata_to_key) args = (test_rsakey_dict, test_rsakey_dict) self.assertRaises(TypeError, KEYS.format_metadata_to_key, *args) # Supplying a malformed argument to the function - should get FormatError del test_rsakey_dict['keyval'] self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.format_metadata_to_key, test_rsakey_dict) def test_helper_get_keyid(self): keytype = self.rsakey_dict['keytype'] keyvalue = self.rsakey_dict['keyval'] scheme = self.rsakey_dict['scheme'] # Check format of 'keytype'. self.assertEqual(None, securesystemslib.formats.KEYTYPE_SCHEMA.check_match(keytype), FORMAT_ERROR_MSG) # Check format of 'keyvalue'. self.assertEqual(None, securesystemslib.formats.KEYVAL_SCHEMA.check_match(keyvalue), FORMAT_ERROR_MSG) # Check format of 'scheme'. self.assertEqual(None, securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme), FORMAT_ERROR_MSG) keyid = KEYS._get_keyid(keytype, scheme, keyvalue) # Check format of 'keyid' - the output of '_get_keyid()' function. self.assertEqual(None, securesystemslib.formats.KEYID_SCHEMA.check_match(keyid), FORMAT_ERROR_MSG) def test_create_signature(self): # Creating a signature for 'DATA'. rsa_signature = KEYS.create_signature(self.rsakey_dict, DATA) ed25519_signature = KEYS.create_signature(self.ed25519key_dict, DATA) # Check format of output. self.assertEqual(None, securesystemslib.formats.SIGNATURE_SCHEMA.check_match(rsa_signature), FORMAT_ERROR_MSG) self.assertEqual(None, securesystemslib.formats.SIGNATURE_SCHEMA.check_match(ed25519_signature), FORMAT_ERROR_MSG) # Test for invalid signature scheme. args = (self.rsakey_dict, DATA) valid_scheme = self.rsakey_dict['scheme'] self.rsakey_dict['scheme'] = 'invalid_scheme' self.assertRaises(securesystemslib.exceptions.UnsupportedAlgorithmError, KEYS.create_signature, *args) self.rsakey_dict['scheme'] = valid_scheme # Removing private key from 'rsakey_dict' - should raise a TypeError. private = self.rsakey_dict['keyval']['private'] self.rsakey_dict['keyval']['private'] = '' self.assertRaises(ValueError, KEYS.create_signature, *args) # Supplying an incorrect number of arguments. self.assertRaises(TypeError, KEYS.create_signature) self.rsakey_dict['keyval']['private'] = private # Test generation of ECDSA signatures. # Creating a signature for 'DATA'. ecdsa_signature = KEYS.create_signature(self.ecdsakey_dict, DATA) # Check format of output. self.assertEqual(None, securesystemslib.formats.SIGNATURE_SCHEMA.check_match(ecdsa_signature), FORMAT_ERROR_MSG) # Removing private key from 'ecdsakey_dict' - should raise a TypeError. private = self.ecdsakey_dict['keyval']['private'] self.ecdsakey_dict['keyval']['private'] = '' args = (self.ecdsakey_dict, DATA) self.assertRaises(ValueError, KEYS.create_signature, *args) # Supplying an incorrect number of arguments. self.assertRaises(TypeError, KEYS.create_signature) self.ecdsakey_dict['keyval']['private'] = private def test_verify_signature(self): # Creating a signature of 'DATA' to be verified. rsa_signature = KEYS.create_signature(self.rsakey_dict, DATA) ed25519_signature = KEYS.create_signature(self.ed25519key_dict, DATA) ecdsa_signature = KEYS.create_signature(self.ecdsakey_dict, DATA) # Verifying the 'signature' of 'DATA'. verified = KEYS.verify_signature(self.rsakey_dict, rsa_signature, DATA) self.assertTrue(verified, "Incorrect signature.") # Verifying the 'ed25519_signature' of 'DATA'. verified = KEYS.verify_signature(self.ed25519key_dict, ed25519_signature, DATA) self.assertTrue(verified, "Incorrect signature.") # Verify that an invalid ed25519 signature scheme is rejected. valid_scheme = self.ed25519key_dict['scheme'] self.ed25519key_dict['scheme'] = 'invalid_scheme' self.assertRaises(securesystemslib.exceptions.UnsupportedAlgorithmError, KEYS.verify_signature, self.ed25519key_dict, ed25519_signature, DATA) self.ed25519key_dict['scheme'] = valid_scheme # Verifying the 'ecdsa_signature' of 'DATA'. verified = KEYS.verify_signature(self.ecdsakey_dict, ecdsa_signature, DATA) self.assertTrue(verified, "Incorrect signature.") # Verifying the 'ecdsa_signature' of 'DATA' with an old-style key dict old_key_dict = self.ecdsakey_dict.copy() old_key_dict['keytype'] = 'ecdsa-sha2-nistp256' verified = KEYS.verify_signature(old_key_dict, ecdsa_signature, DATA) self.assertTrue(verified, "Incorrect signature.") # Test for an invalid ecdsa signature scheme. valid_scheme = self.ecdsakey_dict['scheme'] self.ecdsakey_dict['scheme'] = 'invalid_scheme' self.assertRaises(securesystemslib.exceptions.UnsupportedAlgorithmError, KEYS.verify_signature, self.ecdsakey_dict, ecdsa_signature, DATA) self.ecdsakey_dict['scheme'] = valid_scheme # Testing invalid signatures. Same signature is passed, with 'DATA' being # different than the original 'DATA' that was used in creating the # 'rsa_signature'. Function should return 'False'. # Modifying 'DATA'. _DATA_STR = '1111' + DATA_STR + '1111' _DATA = securesystemslib.formats.encode_canonical(_DATA_STR).encode('utf-8') # Verifying the 'signature' of modified '_DATA'. verified = KEYS.verify_signature(self.rsakey_dict, rsa_signature, _DATA) self.assertFalse(verified, 'Returned \'True\' on an incorrect signature.') verified = KEYS.verify_signature(self.ed25519key_dict, ed25519_signature, _DATA) self.assertFalse(verified, 'Returned \'True\' on an incorrect signature.') verified = KEYS.verify_signature(self.ecdsakey_dict, ecdsa_signature, _DATA) self.assertFalse(verified, 'Returned \'True\' on an incorrect signature.') # Modifying 'rsakey_dict' to pass an incorrect scheme. valid_scheme = self.rsakey_dict['scheme'] self.rsakey_dict['scheme'] = 'Biff' args = (self.rsakey_dict, rsa_signature, DATA) self.assertRaises(securesystemslib.exceptions.UnsupportedAlgorithmError, KEYS.verify_signature, *args) # Restore self.rsakey_dict['scheme'] = valid_scheme # Verify that the KEYIDS of 'key_dict' and 'signature' match. valid_keyid = self.rsakey_dict['keyid'] = '12345' self.rsakey_dict['keyid'] = 'bad123' self.assertRaises(securesystemslib.exceptions.CryptoError, KEYS.verify_signature, self.rsakey_dict, rsa_signature, DATA) self.rsakey_dict['keyid'] = valid_keyid # Passing incorrect number of arguments. self.assertRaises(TypeError, KEYS.verify_signature) # Verify that the pure python 'ed25519' base case (triggered if 'pynacl' # is unavailable) is executed in securesystemslib.keys.verify_signature(). KEYS._ED25519_CRYPTO_LIBRARY = 'invalid' KEYS._available_crypto_libraries = ['invalid'] verified = KEYS.verify_signature(self.ed25519key_dict, ed25519_signature, DATA) self.assertTrue(verified, "Incorrect signature.") def test_create_rsa_encrypted_pem(self): # Test valid arguments. private = self.rsakey_dict['keyval']['private'] passphrase = 'secret' scheme = 'rsassa-pss-sha256' encrypted_pem = KEYS.create_rsa_encrypted_pem(private, passphrase) self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem)) self.assertTrue(KEYS.is_pem_private(encrypted_pem)) # Try to import the encrypted PEM file. rsakey = KEYS.import_rsakey_from_private_pem(encrypted_pem, scheme, passphrase) self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(rsakey)) # Test improperly formatted arguments. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.create_rsa_encrypted_pem, 8, passphrase) self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.create_rsa_encrypted_pem, private, 8) def test_import_rsakey_from_private_pem(self): # Try to import an rsakey from a valid PEM. private_pem = self.rsakey_dict['keyval']['private'] private_rsakey = KEYS.import_rsakey_from_private_pem(private_pem) # Test for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_private_pem, 123) def test_import_rsakey_from_public_pem(self): # Try to import an rsakey from a public PEM. pem = self.rsakey_dict['keyval']['public'] rsa_key = KEYS.import_rsakey_from_public_pem(pem) # Check if the format of the object returned by this function corresponds # to 'securesystemslib.formats.RSAKEY_SCHEMA' format. self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key)) # Verify whitespace is stripped. self.assertEqual(rsa_key, KEYS.import_rsakey_from_public_pem(pem + '\n')) # Supplying a 'bad_pem' argument. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_public_pem, 'bad_pem') # Supplying an improperly formatted PEM. # Strip the PEM header and footer. pem_header = '-----BEGIN PUBLIC KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_public_pem, pem[len(pem_header):]) pem_footer = '-----END PUBLIC KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_public_pem, pem[:-len(pem_footer)]) # Test for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_public_pem, 123) def test_import_rsakey_from_pem(self): # Try to import an rsakey from a public PEM. public_pem = self.rsakey_dict['keyval']['public'] private_pem = self.rsakey_dict['keyval']['private'] public_rsakey = KEYS.import_rsakey_from_pem(public_pem) private_rsakey = KEYS.import_rsakey_from_pem(private_pem) # Check if the format of the object returned by this function corresponds # to 'securesystemslib.formats.RSAKEY_SCHEMA' format. self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(public_rsakey)) self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(private_rsakey)) # Verify whitespace is stripped. self.assertEqual(public_rsakey, KEYS.import_rsakey_from_pem(public_pem + '\n')) self.assertEqual(private_rsakey, KEYS.import_rsakey_from_pem(private_pem + '\n')) # Supplying a 'bad_pem' argument. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_pem, 'bad_pem') # Supplying an improperly formatted public PEM. # Strip the PEM header and footer. pem_header = '-----BEGIN PUBLIC KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_pem, public_pem[len(pem_header):]) pem_footer = '-----END PUBLIC KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_pem, public_pem[:-len(pem_footer)]) # Supplying an improperly formatted private PEM. # Strip the PEM header and footer. pem_header = '-----BEGIN PRIVATE KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_pem, private_pem[len(pem_header):]) pem_footer = '-----END PRIVATE KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_pem, private_pem[:-len(pem_footer)]) # Test for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_rsakey_from_pem, 123) def test_import_ecdsakey_from_private_pem(self): # Try to import an ecdsakey from a valid PEM. private_pem = self.ecdsakey_dict['keyval']['private'] ecdsakey = KEYS.import_ecdsakey_from_private_pem(private_pem) # Test for an encrypted PEM. scheme = 'ecdsa-sha2-nistp256' encrypted_pem = \ securesystemslib.ecdsa_keys.create_ecdsa_encrypted_pem(private_pem, 'password') private_ecdsakey = KEYS.import_ecdsakey_from_private_pem(encrypted_pem.decode('utf-8'), scheme, 'password') # Test for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_ecdsakey_from_private_pem, 123) def test_import_ecdsakey_from_public_pem(self): # Try to import an ecdsakey from a public PEM. pem = self.ecdsakey_dict['keyval']['public'] ecdsa_key = KEYS.import_ecdsakey_from_public_pem(pem) # Check if the format of the object returned by this function corresponds # to 'securesystemslib.formats.ECDSAKEY_SCHEMA' format. self.assertTrue(securesystemslib.formats.ECDSAKEY_SCHEMA.matches(ecdsa_key)) # Verify whitespace is stripped. self.assertEqual(ecdsa_key, KEYS.import_ecdsakey_from_public_pem(pem + '\n')) # Supplying a 'bad_pem' argument. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_ecdsakey_from_public_pem, 'bad_pem') # Supplying an improperly formatted PEM. Strip the PEM header and footer. pem_header = '-----BEGIN PUBLIC KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_ecdsakey_from_public_pem, pem[len(pem_header):]) pem_footer = '-----END PUBLIC KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_ecdsakey_from_public_pem, pem[:-len(pem_footer)]) # Test for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_ecdsakey_from_public_pem, 123) def test_import_ecdsakey_from_pem(self): # Try to import an ecdsakey from a public PEM. public_pem = self.ecdsakey_dict['keyval']['public'] private_pem = self.ecdsakey_dict['keyval']['private'] public_ecdsakey = KEYS.import_ecdsakey_from_pem(public_pem) private_ecdsakey = KEYS.import_ecdsakey_from_pem(private_pem) # Check if the format of the object returned by this function corresponds # to 'securesystemslib.formats.ECDSAKEY_SCHEMA' format. self.assertTrue(securesystemslib.formats.ECDSAKEY_SCHEMA.matches(public_ecdsakey)) self.assertTrue(securesystemslib.formats.ECDSAKEY_SCHEMA.matches(private_ecdsakey)) # Verify whitespace is stripped. self.assertEqual(public_ecdsakey, KEYS.import_ecdsakey_from_pem(public_pem + '\n')) self.assertEqual(private_ecdsakey, KEYS.import_ecdsakey_from_pem(private_pem + '\n')) # Supplying a 'bad_pem' argument. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_ecdsakey_from_pem, 'bad_pem') # Supplying an improperly formatted public PEM. Strip the PEM header and # footer. pem_header = '-----BEGIN PUBLIC KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_ecdsakey_from_pem, public_pem[len(pem_header):]) pem_footer = '-----END PUBLIC KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_ecdsakey_from_pem, public_pem[:-len(pem_footer)]) # Supplying an improperly formatted private PEM. Strip the PEM header and # footer. pem_header = '-----BEGIN EC PRIVATE KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_ecdsakey_from_pem, private_pem[len(pem_header):]) pem_footer = '-----END EC PRIVATE KEY-----' self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_ecdsakey_from_pem, private_pem[:-len(pem_footer)]) # Test for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.import_ecdsakey_from_pem, 123) def test_decrypt_key(self): # Test valid arguments. passphrase = 'secret' encrypted_key = KEYS.encrypt_key(self.rsakey_dict, passphrase) decrypted_key = KEYS.decrypt_key(encrypted_key, passphrase) self.assertTrue(securesystemslib.formats.ANYKEY_SCHEMA.matches(decrypted_key)) # Test improperly formatted arguments. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.decrypt_key, 8, passphrase) self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.decrypt_key, encrypted_key, 8) def test_extract_pem(self): # Normal case. private_pem = KEYS.extract_pem(self.rsakey_dict['keyval']['private'], private_pem=True) self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches(private_pem)) public_pem = KEYS.extract_pem(self.rsakey_dict['keyval']['public'], private_pem=False) self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches(public_pem)) # Test encrypted private pem encrypted_private_pem = KEYS.create_rsa_encrypted_pem(private_pem, "pw") encrypted_private_pem_stripped = KEYS.extract_pem(encrypted_private_pem, private_pem=True) self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches( encrypted_private_pem_stripped)) # Test for an invalid PEM. pem_header = '-----BEGIN RSA PRIVATE KEY-----' pem_footer = '-----END RSA PRIVATE KEY-----' private_header_start = private_pem.index(pem_header) private_footer_start = private_pem.index(pem_footer, private_header_start + len(pem_header)) private_missing_header = private_pem[private_header_start + len(pem_header):private_footer_start + len(pem_footer)] private_missing_footer = private_pem[private_header_start:private_footer_start] pem_header = '-----BEGIN PUBLIC KEY-----' pem_footer = '-----END PUBLIC KEY-----' public_header_start = public_pem.index(pem_header) public_footer_start = public_pem.index(pem_footer, public_header_start + len(pem_header)) public_missing_header = public_pem[public_header_start + len(pem_header):public_footer_start + len(pem_footer)] public_missing_footer = public_pem[public_header_start:public_footer_start] self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.extract_pem, 'invalid_pem', private_pem=False) self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.extract_pem, public_missing_header, private_pem=False) self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.extract_pem, private_missing_header, private_pem=True) self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.extract_pem, public_missing_footer, private_pem=False) self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.extract_pem, private_missing_footer, private_pem=True) def test_is_pem_public(self): # Test for a valid PEM string. public_pem = self.rsakey_dict['keyval']['public'] self.assertTrue(KEYS.is_pem_public(public_pem)) # Test for a valid non-public PEM string. private_pem = self.rsakey_dict['keyval']['private'] self.assertFalse(KEYS.is_pem_public(private_pem)) # Test for an invalid PEM string. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.is_pem_public, 123) def test_is_pem_private(self): # Test for a valid PEM string. private_pem_rsa = self.rsakey_dict['keyval']['private'] private_pem_ec = self.ecdsakey_dict['keyval']['private'] encrypted_private_pem_rsa = KEYS.create_rsa_encrypted_pem( private_pem_rsa, "pw") self.assertTrue(KEYS.is_pem_private(private_pem_rsa)) self.assertTrue(KEYS.is_pem_private(private_pem_ec, 'ec')) self.assertTrue(KEYS.is_pem_private(encrypted_private_pem_rsa)) # Test for a valid non-private PEM string. public_pem = self.rsakey_dict['keyval']['public'] public_pem_ec = self.ecdsakey_dict['keyval']['public'] self.assertFalse(KEYS.is_pem_private(public_pem)) self.assertFalse(KEYS.is_pem_private(public_pem_ec, 'ec')) # Test for unsupported keytype. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.is_pem_private, private_pem_rsa, 'bad_keytype') # Test for an invalid PEM string. self.assertRaises(securesystemslib.exceptions.FormatError, KEYS.is_pem_private, 123) # Run the unit tests. if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_process.py0000644000076500000240000001020300000000000021522 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ test_process.py Lukas Puehringer Oct 4, 2018 See LICENSE for licensing information. Test subprocess interface. """ import os import tempfile import unittest import shlex import io import sys import securesystemslib.process import securesystemslib.settings class Test_Process(unittest.TestCase): """Test subprocess interface. """ def test_run_input_vs_stdin(self): """Test that stdin kwarg is only used if input kwarg is not supplied. """ # Create a temporary file, passed as `stdin` argument fd, path = tempfile.mkstemp(text=True) os.write(fd, b"use stdin kwarg") os.close(fd) stdin_file = open(path) cmd = \ "python -c \"import sys; assert(sys.stdin.read() == '{}')\"" # input is used in favor of stdin securesystemslib.process.run(cmd.format("use input kwarg"), input=b"use input kwarg", stdin=stdin_file) # stdin is only used if input is not supplied securesystemslib.process.run(cmd.format("use stdin kwarg"), stdin=stdin_file) # Clean up stdin_file.close() os.remove(path) def test_run_duplicate_streams(self): """Test output as streams and as returned. """ # Command that prints 'foo' to stdout and 'bar' to stderr. cmd = ("python -c \"" "import sys;" "sys.stdout.write('foo');" "sys.stderr.write('bar');\"") # Create and open fake targets for standard streams stdout_fd, stdout_fn = tempfile.mkstemp() stderr_fd, stderr_fn = tempfile.mkstemp() with io.open(stdout_fn, "r") as fake_stdout_reader, \ os.fdopen(stdout_fd, "w") as fake_stdout_writer, \ io.open(stderr_fn, "r") as fake_stderr_reader, \ os.fdopen(stderr_fd, "w") as fake_stderr_writer: # Backup original standard streams and redirect to fake targets real_stdout = sys.stdout real_stderr = sys.stderr sys.stdout = fake_stdout_writer sys.stderr = fake_stderr_writer # Run command ret_code, ret_stdout, ret_stderr = \ securesystemslib.process.run_duplicate_streams(cmd) # Rewind fake standard streams fake_stdout_reader.seek(0) fake_stderr_reader.seek(0) # Assert that what was printed and what was returned is correct self.assertTrue(ret_stdout == fake_stdout_reader.read() == "foo") self.assertTrue(ret_stderr == fake_stderr_reader.read() == "bar") # Also assert the default return value self.assertEqual(ret_code, 0) # Reset original streams sys.stdout = real_stdout sys.stderr = real_stderr # Remove fake standard streams os.remove(stdout_fn) os.remove(stderr_fn) def test_run_cmd_arg_return_code(self): """Test command arg as string and list using return code. """ cmd_str = ("python -c \"" "import sys;" "sys.exit(100)\"") cmd_list = shlex.split(cmd_str) for cmd in [cmd_str, cmd_list]: proc = securesystemslib.process.run(cmd, check=False) self.assertEqual(proc.returncode, 100) return_code, _, _ = securesystemslib.process.run_duplicate_streams(cmd) self.assertEqual(return_code, 100) def test_run_duplicate_streams_timeout(self): """Test raise TimeoutExpired. """ with self.assertRaises(securesystemslib.process.subprocess.TimeoutExpired): securesystemslib.process.run_duplicate_streams("python --version", timeout=-1) def test__default_timeout(self): """Test default timeout modification. """ # Backup timeout and check that it is what's returned by _default_timeout() timeout_old = securesystemslib.settings.SUBPROCESS_TIMEOUT self.assertEqual(securesystemslib.process._default_timeout(), timeout_old) # Modify timeout and check that _default_timeout() returns the same value timeout_new = timeout_old + 1 securesystemslib.settings.SUBPROCESS_TIMEOUT = timeout_new self.assertEqual(securesystemslib.process._default_timeout(), timeout_new) # Restore original timeout securesystemslib.settings.SUBPROCESS_TIMEOUT = timeout_old if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_rsa_keys.py0000755000076500000240000002331300000000000021675 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ test_rsa_keys.py Vladimir Diaz June 3, 2015. See LICENSE for licensing information. Test cases for 'rsa_keys.py'. """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import unittest import securesystemslib.exceptions import securesystemslib.formats import securesystemslib.keys import securesystemslib.rsa_keys from cryptography.hazmat.primitives import hashes public_rsa, private_rsa = securesystemslib.rsa_keys.generate_rsa_public_and_private() FORMAT_ERROR_MSG = 'securesystemslib.exceptions.FormatError raised. Check object\'s format.' class TestRSA_keys(unittest.TestCase): def setUp(self): pass def test_generate_rsa_public_and_private(self): pub, priv = securesystemslib.rsa_keys.generate_rsa_public_and_private() # Check format of 'pub' and 'priv'. self.assertEqual(None, securesystemslib.formats.PEMRSA_SCHEMA.check_match(pub), FORMAT_ERROR_MSG) self.assertEqual(None, securesystemslib.formats.PEMRSA_SCHEMA.check_match(priv), FORMAT_ERROR_MSG) # Check for an invalid "bits" argument. bits >= 2048. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.generate_rsa_public_and_private, 1024) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.generate_rsa_public_and_private, '2048') def test_create_rsa_signature(self): global private_rsa global public_rsa data = 'The quick brown fox jumps over the lazy dog'.encode('utf-8') for rsa_scheme in securesystemslib.keys.RSA_SIGNATURE_SCHEMES: signature, scheme = \ securesystemslib.rsa_keys.create_rsa_signature(private_rsa, data, rsa_scheme) # Verify format of returned values. self.assertNotEqual(None, signature) self.assertEqual(None, securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme), FORMAT_ERROR_MSG) self.assertEqual(rsa_scheme, scheme) # Check for improperly formatted arguments. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.create_rsa_signature, 123, data) # Check for an unset private key. self.assertRaises(ValueError, securesystemslib.rsa_keys.create_rsa_signature, '', data) # Check for an invalid PEM. self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.rsa_keys.create_rsa_signature, '123', data) # Check for invalid 'data'. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.create_rsa_signature, private_rsa, '') self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.create_rsa_signature, private_rsa, 123) # Check for a missing private key. self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.rsa_keys.create_rsa_signature, public_rsa, data) # Check for a TypeError by attempting to create a signature with an # encrypted key. encrypted_pem = securesystemslib.rsa_keys.create_rsa_encrypted_pem( private_rsa, 'pw') self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.rsa_keys.create_rsa_signature, encrypted_pem, data) def test_verify_rsa_signature(self): global public_rsa global private_rsa data = 'The quick brown fox jumps over the lazy dog'.encode('utf-8') for rsa_scheme in securesystemslib.keys.RSA_SIGNATURE_SCHEMES: signature, scheme = \ securesystemslib.rsa_keys.create_rsa_signature(private_rsa, data, rsa_scheme) valid_signature = \ securesystemslib.rsa_keys.verify_rsa_signature(signature, scheme, public_rsa, data) self.assertEqual(True, valid_signature) # Check for an invalid public key. self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.rsa_keys.verify_rsa_signature, signature, scheme, private_rsa, data) # Check for improperly formatted arguments. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.verify_rsa_signature, signature, 123, public_rsa, data) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.verify_rsa_signature, signature, scheme, 123, data) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.verify_rsa_signature, 123, scheme, public_rsa, data) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.verify_rsa_signature, signature, 'invalid_scheme', public_rsa, data) # Check for invalid 'signature' and 'data' arguments. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.verify_rsa_signature, signature, scheme, public_rsa, 123) self.assertEqual(False, securesystemslib.rsa_keys.verify_rsa_signature(signature, scheme, public_rsa, b'mismatched data')) mismatched_signature, scheme = \ securesystemslib.rsa_keys.create_rsa_signature(private_rsa, b'mismatched data') self.assertEqual(False, securesystemslib.rsa_keys.verify_rsa_signature(mismatched_signature, scheme, public_rsa, data)) def test_create_rsa_encrypted_pem(self): global public_rsa global private_rsa encrypted_pem = \ securesystemslib.rsa_keys.create_rsa_encrypted_pem(private_rsa, 'password') self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem)) # Test for invalid private key (via PEM). self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.rsa_keys.create_rsa_encrypted_pem, public_rsa, 'password') # Test for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.create_rsa_encrypted_pem, public_rsa, 123) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.create_rsa_encrypted_pem, 123, 'password') self.assertRaises(ValueError, securesystemslib.rsa_keys.create_rsa_encrypted_pem, '', 'password') def test_create_rsa_public_and_private_from_pem(self): global public_rsa global private_rsa public, private = \ securesystemslib.rsa_keys.create_rsa_public_and_private_from_pem( private_rsa) self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches(public)) self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches(private)) self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.rsa_keys.create_rsa_public_and_private_from_pem, public_rsa) def test_encrypt_key(self): global public_rsa global private_rsa key_object = {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', 'keyid': '1223', 'keyval': {'public': public_rsa, 'private': private_rsa}} encrypted_key = securesystemslib.rsa_keys.encrypt_key(key_object, 'password') self.assertTrue(securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key)) key_object['keyval']['private'] = '' self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.rsa_keys.encrypt_key, key_object, 'password') def test_decrypt_key(self): # Test for valid arguments. global public_rsa global private_rsa passphrase = 'pw' rsa_key = {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', 'keyid': 'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', 'keyval': {'public': public_rsa, 'private': private_rsa}} encrypted_rsa_key = securesystemslib.rsa_keys.encrypt_key(rsa_key, passphrase) decrypted_rsa_key = securesystemslib.rsa_keys.decrypt_key(encrypted_rsa_key, passphrase) # Test for invalid arguments. self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.rsa_keys.decrypt_key, 'bad', passphrase) # Test for invalid encrypted content (i.e., invalid hmac and ciphertext.) encryption_delimiter = securesystemslib.rsa_keys._ENCRYPTION_DELIMITER salt, iterations, hmac, iv, ciphertext = \ encrypted_rsa_key.split(encryption_delimiter) # Set an invalid hmac. The decryption routine sould raise a # securesystemslib.exceptions.CryptoError exception because 'hmac' does not # match the hmac calculated by the decryption routine. bad_hmac = '12345abcd' invalid_encrypted_rsa_key = \ salt + encryption_delimiter + iterations + encryption_delimiter + \ bad_hmac + encryption_delimiter + iv + encryption_delimiter + ciphertext self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.rsa_keys.decrypt_key, invalid_encrypted_rsa_key, passphrase) # Test for invalid 'ciphertext' bad_ciphertext = '12345abcde' invalid_encrypted_rsa_key = \ salt + encryption_delimiter + iterations + encryption_delimiter + \ hmac + encryption_delimiter + iv + encryption_delimiter + bad_ciphertext self.assertRaises(securesystemslib.exceptions.CryptoError, securesystemslib.rsa_keys.decrypt_key, invalid_encrypted_rsa_key, passphrase) # Run the unit tests. if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_schema.py0000755000076500000240000003663500000000000021330 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ test_schema.py Vladimir Diaz October 2012. See LICENSE for licensing information. Unit test for 'schema.py' """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import unittest import re import securesystemslib.exceptions import securesystemslib.schema as SCHEMA class TestSchema(unittest.TestCase): def setUp(self): pass def tearDown(self): pass def test_Schema(self): # Test conditions for the instantation of classes that inherit # from class Schema(). class NewSchema(SCHEMA.Schema): def __init__(self): pass new_schema = NewSchema() self.assertRaises(NotImplementedError, new_schema.matches, 'test') # Define a new schema. class NewSchema2(SCHEMA.Schema): def __init__(self, string): self._string = string def check_match(self, object): if self._string != object: message = 'Expected: '+repr(self._string) raise securesystemslib.exceptions.FormatError(message) new_schema2 = NewSchema2('test') self.assertRaises(securesystemslib.exceptions.FormatError, new_schema2.check_match, 'bad') self.assertFalse(new_schema2.matches('bad')) self.assertTrue(new_schema2.matches('test')) # Test conditions for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, new_schema2.check_match, True) self.assertRaises(securesystemslib.exceptions.FormatError, new_schema2.check_match, NewSchema2) self.assertRaises(securesystemslib.exceptions.FormatError, new_schema2.check_match, 123) self.assertFalse(new_schema2.matches(True)) self.assertFalse(new_schema2.matches(NewSchema2)) self.assertFalse(new_schema2.matches(123)) def test_Any(self): # Test conditions for valid arguments. any_schema = SCHEMA.Any() self.assertTrue(any_schema.matches('test')) self.assertTrue(any_schema.matches(123)) self.assertTrue(any_schema.matches(['test'])) self.assertTrue(any_schema.matches({'word':'definition'})) self.assertTrue(any_schema.matches(True)) def test_String(self): # Test conditions for valid arguments. string_schema = SCHEMA.String('test') self.assertTrue(string_schema.matches('test')) # Test conditions for invalid arguments. self.assertFalse(string_schema.matches(True)) self.assertFalse(string_schema.matches(['test'])) self.assertFalse(string_schema.matches(SCHEMA.Schema)) # Test conditions for invalid arguments in a schema definition. self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.String, 1) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.String, [1]) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.String, {'a': 1}) def test_AnyString(self): # Test conditions for valid arguments. anystring_schema = SCHEMA.AnyString() self.assertTrue(anystring_schema.matches('')) self.assertTrue(anystring_schema.matches('a string')) # Test conditions for invalid arguments. self.assertFalse(anystring_schema.matches(['a'])) self.assertFalse(anystring_schema.matches(3)) self.assertFalse(anystring_schema.matches({'a': 'string'})) def test_AnyNonemptyString(self): anynonemptystring_schema = SCHEMA.AnyNonemptyString() self.assertTrue(anynonemptystring_schema.matches("foo")) # Test conditions for invalid arguments. self.assertFalse(anynonemptystring_schema.matches('')) self.assertFalse(anynonemptystring_schema.matches(['a'])) self.assertFalse(anynonemptystring_schema.matches(3)) self.assertFalse(anynonemptystring_schema.matches({'a': 'string'})) def test_OneOf(self): # Test conditions for valid arguments. oneof_schema = SCHEMA.OneOf([SCHEMA.ListOf(SCHEMA.Integer()), SCHEMA.String('Hello'), SCHEMA.String('bye')]) self.assertTrue(oneof_schema.matches([])) self.assertTrue(oneof_schema.matches('bye')) self.assertTrue(oneof_schema.matches([1,2])) # Test conditions for invalid arguments. self.assertFalse(oneof_schema.matches(3)) self.assertFalse(oneof_schema.matches(['Hi'])) # Test conditions for invalid arguments in a schema definition. self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.OneOf, 1) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.OneOf, [1]) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.OneOf, {'a': 1}) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.OneOf, [SCHEMA.AnyString(), 1]) def test_AllOf(self): # Test conditions for valid arguments. allof_schema = SCHEMA.AllOf([SCHEMA.Any(), SCHEMA.AnyString(), SCHEMA.String('a')]) self.assertTrue(allof_schema.matches('a')) # Test conditions for invalid arguments. self.assertFalse(allof_schema.matches('b')) # Test conditions for invalid arguments in a schema definition. self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.AllOf, 1) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.AllOf, [1]) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.AllOf, {'a': 1}) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.AllOf, [SCHEMA.AnyString(), 1]) def test_Boolean(self): # Test conditions for valid arguments. boolean_schema = SCHEMA.Boolean() self.assertTrue(boolean_schema.matches(True) and boolean_schema.matches(False)) # Test conditions for invalid arguments. self.assertFalse(boolean_schema.matches(11)) def test_ListOf(self): # Test conditions for valid arguments. listof_schema = SCHEMA.ListOf(SCHEMA.RegularExpression('(?:..)*')) listof2_schema = SCHEMA.ListOf(SCHEMA.Integer(), min_count=3, max_count=10) self.assertTrue(listof_schema.matches([])) self.assertTrue(listof_schema.matches(['Hi', 'this', 'list', 'is', 'full', 'of', 'even', 'strs'])) self.assertTrue(listof2_schema.matches([3]*3)) self.assertTrue(listof2_schema.matches([3]*10)) # Test conditions for invalid arguments. self.assertFalse(listof_schema.matches('hi')) self.assertFalse(listof_schema.matches({})) self.assertFalse(listof_schema.matches(['This', 'one', 'is not'])) self.assertFalse(listof2_schema.matches([3]*2)) self.assertFalse(listof2_schema.matches(([3]*11))) # Test conditions for invalid arguments in a schema definition. self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.ListOf, 1) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.ListOf, [1]) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.ListOf, {'a': 1}) def test_Integer(self): # Test conditions for valid arguments. integer_schema = SCHEMA.Integer() self.assertTrue(integer_schema.matches(99)) self.assertTrue(SCHEMA.Integer(lo=10, hi=30).matches(25)) # Test conditions for invalid arguments. self.assertFalse(integer_schema.matches(False)) self.assertFalse(integer_schema.matches('a string')) self.assertFalse(SCHEMA.Integer(lo=10, hi=30).matches(5)) def test_DictOf(self): # Test conditions for valid arguments. dictof_schema = SCHEMA.DictOf(SCHEMA.RegularExpression(r'[aeiou]+'), SCHEMA.Struct([SCHEMA.AnyString(), SCHEMA.AnyString()])) self.assertTrue(dictof_schema.matches({})) self.assertTrue(dictof_schema.matches({'a': ['x', 'y'], 'e' : ['', '']})) # Test conditions for invalid arguments. self.assertFalse(dictof_schema.matches('')) self.assertFalse(dictof_schema.matches({'a': ['x', 3], 'e' : ['', '']})) self.assertFalse(dictof_schema.matches({'a': ['x', 'y'], 'e' : ['', ''], 'd' : ['a', 'b']})) # Test conditions for invalid arguments in a schema definition. self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.DictOf, 1, 1) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.DictOf, [1], [1]) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.DictOf, {'a': 1}, 1) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.DictOf, SCHEMA.AnyString(), 1) def test_Optional(self): # Test conditions for valid arguments. optional_schema = SCHEMA.Object(k1=SCHEMA.String('X'), k2=SCHEMA.Optional(SCHEMA.String('Y'))) self.assertTrue(optional_schema.matches({'k1': 'X', 'k2': 'Y'})) self.assertTrue(optional_schema.matches({'k1': 'X'})) # Test conditions for invalid arguments. self.assertFalse(optional_schema.matches({'k1': 'X', 'k2': 'Z'})) # Test conditions for invalid arguments in a schema definition. self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.Optional, 1) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.Optional, [1]) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.Optional, {'a': 1}) def test_Object(self): # Test conditions for valid arguments. object_schema = SCHEMA.Object(a=SCHEMA.AnyString(), bc=SCHEMA.Struct([SCHEMA.Integer(), SCHEMA.Integer()])) self.assertTrue(object_schema.matches({'a':'ZYYY', 'bc':[5,9]})) self.assertTrue(object_schema.matches({'a':'ZYYY', 'bc':[5,9], 'xx':5})) # Test conditions for invalid arguments. self.assertFalse(object_schema.matches({'a':'ZYYY', 'bc':[5,9,3]})) self.assertFalse(object_schema.matches({'a':'ZYYY'})) # Test conditions for invalid arguments in a schema definition. self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.Object, a='a') self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.Object, a=[1]) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.Object, a=SCHEMA.AnyString(), b=1) # Test condition for invalid non-dict arguments. self.assertFalse(object_schema.matches([{'a':'XYZ'}])) self.assertFalse(object_schema.matches(8)) def test_Struct(self): # Test conditions for valid arguments. struct_schema = SCHEMA.Struct([SCHEMA.ListOf(SCHEMA.AnyString()), SCHEMA.AnyString(), SCHEMA.String('X')]) struct2_schema = SCHEMA.Struct([SCHEMA.String('X')], allow_more=True) struct3_schema = SCHEMA.Struct([SCHEMA.String('X'), SCHEMA.Integer()], [SCHEMA.Integer()]) self.assertTrue(struct_schema.matches([[], 'Q', 'X'])) self.assertTrue(struct2_schema.matches(['X'])) self.assertTrue(struct2_schema.matches(['X', 'Y'])) self.assertTrue(struct2_schema.matches(['X', ['Y', 'Z']])) self.assertTrue(struct3_schema.matches(['X', 3])) self.assertTrue(struct3_schema.matches(['X', 3, 9])) # Test conditions for invalid arguments. self.assertFalse(struct_schema.matches(False)) self.assertFalse(struct_schema.matches('Foo')) self.assertFalse(struct_schema.matches([[], 'Q', 'D'])) self.assertFalse(struct_schema.matches([[3], 'Q', 'X'])) self.assertFalse(struct_schema.matches([[], 'Q', 'X', 'Y'])) self.assertFalse(struct2_schema.matches([])) self.assertFalse(struct2_schema.matches([['X']])) self.assertFalse(struct3_schema.matches([])) self.assertFalse(struct3_schema.matches({})) self.assertFalse(struct3_schema.matches(['X'])) self.assertFalse(struct3_schema.matches(['X', 3, 9, 11])) self.assertFalse(struct3_schema.matches(['X', 3, 'A'])) # Test conditions for invalid arguments in a schema definition. self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.Struct, 1) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.Struct, [1]) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.Struct, {'a': 1}) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.Struct, [SCHEMA.AnyString(), 1]) def test_RegularExpression(self): # Test conditions for valid arguments. # RegularExpression(pattern, modifiers, re_object, re_name). re_schema = SCHEMA.RegularExpression('h.*d') self.assertTrue(re_schema.matches('hello world')) # Provide a pattern that contains the trailing '$' re_schema_2 = SCHEMA.RegularExpression(pattern='abc$', modifiers=0, re_object=None, re_name='my_re') self.assertTrue(re_schema_2.matches('abc')) # Test for valid optional arguments. compiled_re = re.compile('^[a-z].*') re_schema_optional = SCHEMA.RegularExpression(pattern='abc', modifiers=0, re_object=compiled_re, re_name='my_re') self.assertTrue(re_schema_optional.matches('abc')) # Valid arguments, but the 'pattern' argument is unset (required if the # 're_object' is 'None'.) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.RegularExpression, None, 0, None, None) # Valid arguments, 're_name' is unset, and 'pattern' is None. An exception # is not raised, but 're_name' is set to 'pattern'. re_schema_optional = SCHEMA.RegularExpression(pattern=None, modifiers=0, re_object=compiled_re, re_name=None) self.assertTrue(re_schema_optional.matches('abc')) self.assertTrue(re_schema_optional._re_name == 'pattern') # Test conditions for invalid arguments. self.assertFalse(re_schema.matches('Hello World')) self.assertFalse(re_schema.matches('hello world!')) self.assertFalse(re_schema.matches([33, 'Hello'])) self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.RegularExpression, 8) def test_LengthString(self): # Test conditions for valid arguments. length_string = SCHEMA.LengthString(11) self.assertTrue(length_string.matches('Hello World')) self.assertTrue(length_string.matches('Hello Marty')) # Test conditions for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.LengthString, 'hello') self.assertFalse(length_string.matches('hello')) self.assertFalse(length_string.matches(8)) def test_LengthBytes(self): # Test conditions for valid arguments. length_bytes = SCHEMA.LengthBytes(11) self.assertTrue(length_bytes.matches(b'Hello World')) self.assertTrue(length_bytes.matches(b'Hello Marty')) # Test conditions for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.LengthBytes, 'hello') self.assertRaises(securesystemslib.exceptions.FormatError, SCHEMA.LengthBytes, True) self.assertFalse(length_bytes.matches(b'hello')) self.assertFalse(length_bytes.matches(8)) def test_AnyBytes(self): # Test conditions for valid arguments. anybytes_schema = SCHEMA.AnyBytes() self.assertTrue(anybytes_schema.matches(b'')) self.assertTrue(anybytes_schema.matches(b'a string')) # Test conditions for invalid arguments. self.assertFalse(anybytes_schema.matches('a string')) self.assertFalse(anybytes_schema.matches(['a'])) self.assertFalse(anybytes_schema.matches(3)) self.assertFalse(anybytes_schema.matches({'a': 'string'})) # Run the unit tests. if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_signer.py0000644000076500000240000000557300000000000021351 0ustar00lukpstaff00000000000000#!/usr/bin/env python """Test cases for "signer.py". """ import sys import unittest import unittest import securesystemslib.formats import securesystemslib.keys as KEYS from securesystemslib.exceptions import FormatError, UnsupportedAlgorithmError # TODO: Remove case handling when fully dropping support for versions < 3.6 IS_PY_VERSION_SUPPORTED = sys.version_info >= (3, 6) # Use setUpModule to tell unittest runner to skip this test module gracefully. def setUpModule(): if not IS_PY_VERSION_SUPPORTED: raise unittest.SkipTest("requires Python 3.6 or higher") # Since setUpModule is called after imports we need to import conditionally. if IS_PY_VERSION_SUPPORTED: from securesystemslib.signer import Signature, SSlibSigner class TestSSlibSigner(unittest.TestCase): @classmethod def setUpClass(cls): cls.rsakey_dict = KEYS.generate_rsa_key() cls.ed25519key_dict = KEYS.generate_ed25519_key() cls.ecdsakey_dict = KEYS.generate_ecdsa_key() cls.DATA_STR = "SOME DATA REQUIRING AUTHENTICITY." cls.DATA = securesystemslib.formats.encode_canonical( cls.DATA_STR).encode("utf-8") def test_sslib_sign(self): dicts = [self.rsakey_dict, self.ecdsakey_dict, self.ed25519key_dict] for scheme_dict in dicts: # Test generation of signatures. sslib_signer = SSlibSigner(scheme_dict) sig_obj = sslib_signer.sign(self.DATA) # Verify signature verified = KEYS.verify_signature(scheme_dict, sig_obj.to_dict(), self.DATA) self.assertTrue(verified, "Incorrect signature.") # Removing private key from "scheme_dict". private = scheme_dict["keyval"]["private"] scheme_dict["keyval"]["private"] = "" sslib_signer.key_dict = scheme_dict with self.assertRaises((ValueError, FormatError)): sslib_signer.sign(self.DATA) scheme_dict["keyval"]["private"] = private # Test for invalid signature scheme. valid_scheme = scheme_dict["scheme"] scheme_dict["scheme"] = "invalid_scheme" sslib_signer = SSlibSigner(scheme_dict) with self.assertRaises((UnsupportedAlgorithmError, FormatError)): sslib_signer.sign(self.DATA) scheme_dict["scheme"] = valid_scheme def test_signature_from_to_dict(self): signature_dict = { "sig": "30460221009342e4566528fcecf6a7a5d53ebacdb1df151e242f55f8775883469cb01dbc6602210086b426cc826709acfa2c3f9214610cb0a832db94bbd266fd7c5939a48064a851", "keyid": "11fa391a0ed7a447cbfeb4b2667e286fc248f64d5e6d0eeed2e5e23f97f9f714" } sig_obj = Signature.from_dict(signature_dict) self.assertDictEqual(signature_dict, sig_obj.to_dict()) # Run the unit tests. if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_storage.py0000644000076500000240000000716400000000000021524 0ustar00lukpstaff00000000000000""" test_storage.py Joshua Lock April 17, 2020 See LICENSE for licensing information. Unit test for 'storage.py' """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import os import tempfile import shutil import unittest import securesystemslib.exceptions import securesystemslib.storage import six class TestStorage(unittest.TestCase): def setUp(self): self.storage_backend = securesystemslib.storage.FilesystemBackend() self.temp_dir = tempfile.mkdtemp(dir=os.getcwd()) self.filepath = os.path.join(self.temp_dir, 'testfile') with open(self.filepath, 'wb') as test: test.write(b'testing') self.fileobj = open(self.filepath, 'rb') def tearDown(self): self.fileobj.close() shutil.rmtree(self.temp_dir) def test_exceptions(self): try: with self.storage_backend.get('/none/existent/path') as file_object: file_object.read() except Exception as exc: self.assertIsInstance(exc, securesystemslib.exceptions.StorageError) self.assertRaises(securesystemslib.exceptions.StorageError, self.storage_backend.put, self.fileobj, '/none/existent/path') self.assertRaises(securesystemslib.exceptions.StorageError, self.storage_backend.getsize, '/none/existent/path') self.assertRaises(securesystemslib.exceptions.StorageError, self.storage_backend.create_folder, '/none/existent/path') self.assertRaises(securesystemslib.exceptions.StorageError, self.storage_backend.create_folder, '') self.assertRaises(securesystemslib.exceptions.StorageError, self.storage_backend.list_folder, '/none/existent/path') def test_files(self): with self.storage_backend.get(self.filepath) as get_fileobj: self.assertEqual(get_fileobj.read(), self.fileobj.read()) self.assertEqual(self.storage_backend.getsize(self.filepath), os.path.getsize(self.filepath)) put_path = os.path.join(self.temp_dir, 'put') with self.storage_backend.get(self.filepath) as get_fileobj: self.storage_backend.put(get_fileobj, put_path) self.fileobj.seek(0) with open(put_path, 'rb') as put_file: self.assertEqual(put_file.read(), self.fileobj.read()) self.assertTrue(os.path.exists(put_path)) self.storage_backend.remove(put_path) self.assertFalse(os.path.exists(put_path)) def test_folders(self): leaves = ['test1', 'test2', 'test3'] folder = os.path.join(self.temp_dir, 'test_dir') self.storage_backend.create_folder(folder) for leaf in leaves: with open(os.path.join(folder, leaf), 'wb') as fi: fi.write(leaf.encode('utf-8')) found_leaves = self.storage_backend.list_folder(folder) self.assertListEqual(leaves, sorted(found_leaves)) def test_singleton(self): # There should only ever be a single instance of FilesystemBackend. # An object's id is unique and constant for the object during its # lifetime. Therefore create more than one instance of FilesystemBackend # and compare their id's fb1 = securesystemslib.storage.FilesystemBackend() fb2 = securesystemslib.storage.FilesystemBackend() self.assertEqual(id(fb1), id(fb2)) self.assertEqual(id(self.storage_backend), id(fb1)) self.assertEqual(id(fb2), id(self.storage_backend)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tests/test_util.py0000755000076500000240000002700500000000000021034 0ustar00lukpstaff00000000000000#!/usr/bin/env python """ test_util.py Konstantin Andrianov. February 1, 2013. See LICENSE for licensing information. Unit test for 'util.py' """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import os import sys import shutil import logging import tempfile import unittest import timeit import securesystemslib.settings import securesystemslib.hash import securesystemslib.util import securesystemslib.unittest_toolbox as unittest_toolbox import six logger = logging.getLogger(__name__) class TestUtil(unittest_toolbox.Modified_TestCase): def setUp(self): unittest_toolbox.Modified_TestCase.setUp(self) self.temp_fileobj = tempfile.TemporaryFile() def tearDown(self): unittest_toolbox.Modified_TestCase.tearDown(self) self.temp_fileobj.close() def test_B1_get_file_details(self): # Goal: Verify proper output given certain expected/unexpected input. # Making a temporary file. filepath = self.make_temp_data_file() # Computing the hash and length of the tempfile. digest_object = securesystemslib.hash.digest_filename(filepath, algorithm='sha256') file_hash = {'sha256' : digest_object.hexdigest()} file_length = os.path.getsize(filepath) # Test: Expected input. self.assertEqual(securesystemslib.util.get_file_details(filepath), (file_length, file_hash)) # Test: Incorrect input. bogus_inputs = [self.random_string(), 1234, [self.random_string()], {'a': 'b'}, None] for bogus_input in bogus_inputs: if isinstance(bogus_input, six.string_types): self.assertRaises(securesystemslib.exceptions.Error, securesystemslib.util.get_file_details, bogus_input) else: self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.util.get_file_details, bogus_input) def test_B2_get_file_hashes(self): # Goal: Verify proper output given certain expected/unexpected input. # Making a temporary file. filepath = self.make_temp_data_file() # Computing the hash of the tempfile. digest_object = securesystemslib.hash.digest_filename(filepath, algorithm='sha256') file_hash = {'sha256' : digest_object.hexdigest()} # Test: Expected input. self.assertEqual(securesystemslib.util.get_file_hashes(filepath), file_hash) # Test: Incorrect input. bogus_inputs = [self.random_string(), 1234, [self.random_string()], {'a': 'b'}, None] for bogus_input in bogus_inputs: if isinstance(bogus_input, six.string_types): self.assertRaises(securesystemslib.exceptions.Error, securesystemslib.util.get_file_hashes, bogus_input) else: self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.util.get_file_hashes, bogus_input) def test_B3_get_file_length(self): # Goal: Verify proper output given certain expected/unexpected input. # Making a temporary file. filepath = self.make_temp_data_file() # Computing the length of the tempfile. digest_object = securesystemslib.hash.digest_filename(filepath, algorithm='sha256') file_length = os.path.getsize(filepath) # Test: Expected input. self.assertEqual(securesystemslib.util.get_file_length(filepath), file_length) # Test: Incorrect input. bogus_inputs = [self.random_string(), 1234, [self.random_string()], {'a': 'b'}, None] for bogus_input in bogus_inputs: if isinstance(bogus_input, six.string_types): self.assertRaises(securesystemslib.exceptions.Error, securesystemslib.util.get_file_length, bogus_input) else: self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.util.get_file_length, bogus_input) def test_B4_ensure_parent_dir(self): existing_parent_dir = self.make_temp_directory() non_existing_parent_dir = os.path.join(existing_parent_dir, 'a', 'b') for parent_dir in [existing_parent_dir, non_existing_parent_dir, 12, [3]]: if isinstance(parent_dir, six.string_types): securesystemslib.util.ensure_parent_dir(os.path.join(parent_dir, 'a.txt')) self.assertTrue(os.path.isdir(parent_dir)) else: self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.util.ensure_parent_dir, parent_dir) # When we call ensure_parent_dir with filepath arg like "a.txt", # then the directory of that filepath will be an empty string. # We want to make sure that securesyslib.storage.create_folder() # won't be called with an empty string and thus raise an exception. # If an exception is thrown the test will fail. securesystemslib.util.ensure_parent_dir('a.txt') def test_B5_file_in_confined_directories(self): # Goal: Provide invalid input for 'filepath' and 'confined_directories'. # Include inputs like: '[1, 2, "a"]' and such... # Reference to 'file_in_confined_directories()' to improve readability. in_confined_directory = securesystemslib.util.file_in_confined_directories list_of_confined_directories = ['a', 12, {'a':'a'}, [1]] list_of_filepaths = [12, ['a'], {'a':'a'}, 'a'] for bogus_confined_directory in list_of_confined_directories: for filepath in list_of_filepaths: self.assertRaises(securesystemslib.exceptions.FormatError, in_confined_directory, filepath, bogus_confined_directory) # Test: Inputs that evaluate to False. confined_directories = ['a/b/', 'a/b/c/d/'] self.assertFalse(in_confined_directory('a/b/c/1.txt', confined_directories)) confined_directories = ['a/b/c/d/e/'] self.assertFalse(in_confined_directory('a', confined_directories)) self.assertFalse(in_confined_directory('a/b', confined_directories)) self.assertFalse(in_confined_directory('a/b/c', confined_directories)) self.assertFalse(in_confined_directory('a/b/c/d', confined_directories)) # Below, 'e' is a file in the 'a/b/c/d/' directory. self.assertFalse(in_confined_directory('a/b/c/d/e', confined_directories)) # Test: Inputs that evaluate to True. self.assertTrue(in_confined_directory('a/b/c.txt', [''])) self.assertTrue(in_confined_directory('a/b/c.txt', ['a/b/'])) self.assertTrue(in_confined_directory('a/b/c.txt', ['x', ''])) self.assertTrue(in_confined_directory('a/b/c/..', ['a/'])) def test_B6_import_json(self): self.assertTrue('json' in sys.modules) json_module = securesystemslib.util.import_json() self.assertTrue(json_module is not None) # Test import_json() when 'util._json_moduel' is non-None. securesystemslib.util._json_module = 'junk_module' self.assertEqual(securesystemslib.util.import_json(), 'junk_module') def test_B7_load_json_string(self): # Test normal case. data = ['a', {'b': ['c', None, 30.3, 29]}] json_string = securesystemslib.util.json.dumps(data) self.assertEqual(data, securesystemslib.util.load_json_string(json_string)) # Test invalid arguments. self.assertRaises(securesystemslib.exceptions.Error, securesystemslib.util.load_json_string, 8) invalid_json_string = json_string + '.' self.assertRaises(securesystemslib.exceptions.Error, securesystemslib.util.load_json_string, invalid_json_string) def test_B8_load_json_file(self): data = ['a', {'b': ['c', None, 30.3, 29]}] filepath = self.make_temp_file() fileobj = open(filepath, 'wt') securesystemslib.util.json.dump(data, fileobj) fileobj.close() self.assertEqual(data, securesystemslib.util.load_json_file(filepath)) # Improperly formatted arguments. for bogus_arg in [1, [b'a'], {'a':b'b'}]: self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.util.load_json_file, bogus_arg) # Non-existent path. self.assertRaises(securesystemslib.exceptions.StorageError, securesystemslib.util.load_json_file, 'non-existent.json') # Invalid JSON content. filepath_bad_data = self.make_temp_file() fileobj = open(filepath_bad_data, 'wt') fileobj.write('junk data') fileobj.close() self.assertRaises(securesystemslib.exceptions.Error, securesystemslib.util.load_json_file, filepath_bad_data) def test_B9_persist_temp_file(self): # Destination directory to save the temporary file in. dest_temp_dir = self.make_temp_directory() # Test the default of persisting the file and closing the tmpfile dest_path = os.path.join(dest_temp_dir, self.random_string()) tmpfile = tempfile.TemporaryFile() tmpfile.write(self.random_string().encode('utf-8')) securesystemslib.util.persist_temp_file(tmpfile, dest_path) self.assertTrue(dest_path) self.assertTrue(tmpfile.closed) # Test persisting a file without automatically closing the tmpfile dest_path2 = os.path.join(dest_temp_dir, self.random_string()) tmpfile = tempfile.TemporaryFile() tmpfile.write(self.random_string().encode('utf-8')) securesystemslib.util.persist_temp_file(tmpfile, dest_path2, should_close=False) self.assertFalse(tmpfile.closed) tmpfile.close() def test_C5_unittest_toolbox_make_temp_directory(self): # Verify that the tearDown function does not fail when # unittest_toolbox.make_temp_directory deletes the generated temp directory # here. temp_directory = self.make_temp_directory() os.rmdir(temp_directory) def test_c5_unittest_toolbox_random_path(self): # Verify that a random path can be generated with unittest_toolbox. random_path = self.random_path(length=10) self.assertTrue(securesystemslib.formats.PATH_SCHEMA.matches(random_path)) self.assertTrue(10, len(random_path)) def test_digests_are_equal(self): digest = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' # Normal case: test for digests that are equal. self.assertTrue(securesystemslib.util.digests_are_equal(digest, digest)) # Normal case: test for digests that are unequal. self.assertFalse(securesystemslib.util.digests_are_equal(digest, '0a8df1')) # Test for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.util.digests_are_equal, 7, digest) self.assertRaises(securesystemslib.exceptions.FormatError, securesystemslib.util.digests_are_equal, digest, 7) # Test that digests_are_equal() takes the same amount of time to compare # equal and unequal arguments. runtime = timeit.timeit('digests_are_equal("ab8df", "ab8df")', setup='from securesystemslib.util import digests_are_equal', number=100000) runtime2 = timeit.timeit('digests_are_equal("ab8df", "1b8df")', setup='from securesystemslib.util import digests_are_equal', number=100000) runtime3 = timeit.timeit('"ab8df" == "ab8df"', number=100000) runtime4 = timeit.timeit('"ab8df" == "1b8df"', number=1000000) # The ratio for the 'digest_are_equal' runtimes should be at or near 1. ratio_digests_are_equal = abs(runtime2 / runtime) # The ratio for the variable-time runtimes should be (>1) & at or near 10? ratio_variable_compare = abs(runtime4 / runtime3) self.assertTrue(ratio_digests_are_equal < ratio_variable_compare) # Run unit test. if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1614341139.0 securesystemslib-0.20.0/tox.ini0000644000076500000240000000217500000000000016615 0ustar00lukpstaff00000000000000# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py27, py36, py37, py38, py39, purepy27, purepy38, py27-no-gpg, py38-no-gpg skipsdist = True [testenv] install_command = pip install --pre {opts} {packages} deps = -r{toxinidir}/requirements-pinned.txt -r{toxinidir}/requirements-test.txt commands = coverage run tests/aggregate_tests.py coverage report -m --fail-under 97 [testenv:purepy27] deps = -r{toxinidir}/requirements-min.txt -r{toxinidir}/requirements-test.txt commands = python -m tests.check_public_interfaces [testenv:purepy38] deps = -r{toxinidir}/requirements-min.txt commands = python -m tests.check_public_interfaces [testenv:py27-no-gpg] setenv = GNUPG = nonexisting-gpg-for-testing commands = python -m tests.check_public_interfaces_gpg [testenv:py38-no-gpg] setenv = GNUPG = nonexisting-gpg-for-testing commands = python -m tests.check_public_interfaces_gpg