securesystemslib-0.11.3/0000755000076600000240000000000013353243474014577 5ustar sstaff00000000000000securesystemslib-0.11.3/PKG-INFO0000644000076600000240000003267213353243474015706 0ustar sstaff00000000000000Metadata-Version: 2.1 Name: securesystemslib Version: 0.11.3 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: UNKNOWN Description: Secure Systems Library ---------------------- .. image:: https://travis-ci.org/secure-systems-lab/securesystemslib.svg?branch=master :target: https://travis-ci.org/secure-systems-lab/securesystemslib .. image:: https://coveralls.io/repos/github/secure-systems-lab/securesystemslib/badge.svg?branch=master :target: https://coveralls.io/github/secure-systems-lab/securesystemslib?branch=master .. image:: https://pyup.io/repos/github/secure-systems-lab/securesystemslib/shield.svg :target: https://pyup.io/repos/github/secure-systems-lab/securesystemslib/ :alt: Updates 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. 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] 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("rsa_key1", bits=2048, password="password") # If the key length is unspecified, it defaults to 3072 bits. A length of # less than 2048 bits raises an exception. A password may be supplied as an # argument, otherwise a user prompt is presented. If the password is an # empty string, the private key is saved unencrypted. >>> generate_and_write_rsa_keypair("rsa_key2") Enter a password for the RSA key: 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 a password for the encrypted RSA key: **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 . . . # Generate and write an Ed25519 key pair. The private key is saved # encrypted. A 'password' argument may be supplied, otherwise a prompt is # presented. >>> generate_and_write_ed25519_keypair('ed25519_key') Enter a password for the Ed25519 key: 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') Enter a password for the encrypted Ed25519 key: Create and Import ECDSA Keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # continuing from the previous sections . . . >>> generate_and_write_ecdsa_keypair('ecdsa_key') Enter a password for the ECDSA key: Confirm: >>> public_ecdsa_key = import_ecdsa_publickey_from_file('ecdsa_key.pub') >>> private_ecdsa_key = import_ecdsa_privatekey_from_file('ecdsa_key') Enter a password for the encrypted ECDSA key: Generate ECDSA, Ed25519, and RSA Signatures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note: Users may also access the crypto functions directly to perform cryptographic operations. :: >>> from securesystemslib.keys import * >>> data = '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 = '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) 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.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Security Classifier: Topic :: Software Development Provides-Extra: pynacl Provides-Extra: crypto securesystemslib-0.11.3/securesystemslib.egg-info/0000755000076600000240000000000013353243474021676 5ustar sstaff00000000000000securesystemslib-0.11.3/securesystemslib.egg-info/PKG-INFO0000644000076600000240000003267213353243473023004 0ustar sstaff00000000000000Metadata-Version: 2.1 Name: securesystemslib Version: 0.11.3 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: UNKNOWN Description: Secure Systems Library ---------------------- .. image:: https://travis-ci.org/secure-systems-lab/securesystemslib.svg?branch=master :target: https://travis-ci.org/secure-systems-lab/securesystemslib .. image:: https://coveralls.io/repos/github/secure-systems-lab/securesystemslib/badge.svg?branch=master :target: https://coveralls.io/github/secure-systems-lab/securesystemslib?branch=master .. image:: https://pyup.io/repos/github/secure-systems-lab/securesystemslib/shield.svg :target: https://pyup.io/repos/github/secure-systems-lab/securesystemslib/ :alt: Updates 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. 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] 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("rsa_key1", bits=2048, password="password") # If the key length is unspecified, it defaults to 3072 bits. A length of # less than 2048 bits raises an exception. A password may be supplied as an # argument, otherwise a user prompt is presented. If the password is an # empty string, the private key is saved unencrypted. >>> generate_and_write_rsa_keypair("rsa_key2") Enter a password for the RSA key: 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 a password for the encrypted RSA key: **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 . . . # Generate and write an Ed25519 key pair. The private key is saved # encrypted. A 'password' argument may be supplied, otherwise a prompt is # presented. >>> generate_and_write_ed25519_keypair('ed25519_key') Enter a password for the Ed25519 key: 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') Enter a password for the encrypted Ed25519 key: Create and Import ECDSA Keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # continuing from the previous sections . . . >>> generate_and_write_ecdsa_keypair('ecdsa_key') Enter a password for the ECDSA key: Confirm: >>> public_ecdsa_key = import_ecdsa_publickey_from_file('ecdsa_key.pub') >>> private_ecdsa_key = import_ecdsa_privatekey_from_file('ecdsa_key') Enter a password for the encrypted ECDSA key: Generate ECDSA, Ed25519, and RSA Signatures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note: Users may also access the crypto functions directly to perform cryptographic operations. :: >>> from securesystemslib.keys import * >>> data = '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 = '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) 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.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Security Classifier: Topic :: Software Development Provides-Extra: pynacl Provides-Extra: crypto securesystemslib-0.11.3/securesystemslib.egg-info/SOURCES.txt0000644000076600000240000000154713353243473023570 0ustar sstaff00000000000000README.rst setup.py 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/pyca_crypto_keys.py securesystemslib/schema.py securesystemslib/settings.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.pysecuresystemslib-0.11.3/securesystemslib.egg-info/requires.txt0000644000076600000240000000012113353243473024267 0ustar sstaff00000000000000six>=1.11.0 [crypto] cryptography>=2.2.2 colorama>=0.3.9 [pynacl] pynacl>1.2.0 securesystemslib-0.11.3/securesystemslib.egg-info/top_level.txt0000644000076600000240000000002113353243473024420 0ustar sstaff00000000000000securesystemslib securesystemslib-0.11.3/securesystemslib.egg-info/dependency_links.txt0000644000076600000240000000000113353243473025743 0ustar sstaff00000000000000 securesystemslib-0.11.3/securesystemslib/0000755000076600000240000000000013353243474020204 5ustar sstaff00000000000000securesystemslib-0.11.3/securesystemslib/_vendor/0000755000076600000240000000000013353243474021640 5ustar sstaff00000000000000securesystemslib-0.11.3/securesystemslib/_vendor/__init__.py0000755000076600000240000000000013342052762023736 0ustar sstaff00000000000000securesystemslib-0.11.3/securesystemslib/_vendor/ed25519/0000755000076600000240000000000013353243474022636 5ustar sstaff00000000000000securesystemslib-0.11.3/securesystemslib/_vendor/ed25519/__init__.py0000644000076600000240000000000013342052762024731 0ustar sstaff00000000000000securesystemslib-0.11.3/securesystemslib/_vendor/ed25519/test_ed25519.py0000644000076600000240000000744513342052762025253 0ustar sstaff00000000000000# 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) securesystemslib-0.11.3/securesystemslib/_vendor/ed25519/ed25519.py0000644000076600000240000001670213342052762024210 0ustar sstaff00000000000000# 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 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): """$= 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") securesystemslib-0.11.3/securesystemslib/_vendor/ed25519/science.py0000644000076600000240000000220113342052762024610 0ustar sstaff00000000000000# 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, ) ) securesystemslib-0.11.3/securesystemslib/_vendor/ssl_match_hostname.py0000644000076600000240000000424313342052762026064 0ustar sstaff00000000000000""" 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") securesystemslib-0.11.3/securesystemslib/util.py0000755000076600000240000007224313342052762021542 0ustar sstaff00000000000000""" 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, and a TempFile class that generates a file-like object for temporary storage, 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 sys import gzip import shutil import logging import tempfile import fnmatch import securesystemslib.exceptions import securesystemslib.settings import securesystemslib.hash import securesystemslib.formats import six # The algorithm used by the repository to generate the digests of the # target filepaths, which are included in metadata files and may be prepended # to the filenames of consistent snapshots. HASH_FUNCTION = 'sha256' # See 'log.py' to learn how logging is handled in TUF. logger = logging.getLogger('securesystemslib_util') class TempFile(object): """ A high-level temporary file that cleans itself up or can be manually cleaned up. This isn't a complete file-like object. The file functions that are supported make additional common-case safe assumptions. There are additional functions that aren't part of file-like objects. TempFile is used in the download.py module to temporarily store downloaded data while all security checks (file hashes/length) are performed. """ def _default_temporary_directory(self, prefix): """__init__ helper.""" try: self.temporary_file = tempfile.NamedTemporaryFile(prefix=prefix) except OSError as err: # pragma: no cover logger.critical('Cannot create a system temporary directory: '+repr(err)) raise securesystemslib.exceptions.Error(err) def __init__(self, prefix='tuf_temp_'): """ Initializes TempFile. prefix: A string argument to be used with tempfile.NamedTemporaryFile function. securesystemslib.exceptions.Error on failure to load temp dir. None. """ self._compression = None # If compression is set then the original file is saved in 'self._orig_file'. self._orig_file = None temp_dir = securesystemslib.settings.temporary_directory if temp_dir is not None and securesystemslib.formats.PATH_SCHEMA.matches(temp_dir): try: self.temporary_file = tempfile.NamedTemporaryFile(prefix=prefix, dir=temp_dir) except OSError as err: logger.error('Temp file in ' + temp_dir + ' failed: ' +repr(err)) logger.error('Will attempt to use system default temp dir.') self._default_temporary_directory(prefix) else: self._default_temporary_directory(prefix) def get_compressed_length(self): """ Get the compressed length of the file. This will be correct information even when the file is read as an uncompressed one. None. OSError. Nonnegative integer representing compressed file size. """ # Even if we read a compressed file with the gzip standard library module, # the original file will remain compressed. return os.stat(self.temporary_file.name).st_size def flush(self): """ Flushes buffered output for the file. None. None. None. """ self.temporary_file.flush() def read(self, size=None): """ Read specified number of bytes. If size is not specified then the whole file is read and the file pointer is placed at the beginning of the file. size: Number of bytes to be read. securesystemslib.exceptions.FormatError: if 'size' is invalid. String of data. """ if size is None: self.temporary_file.seek(0) data = self.temporary_file.read() self.temporary_file.seek(0) return data else: if not (isinstance(size, int) and size > 0): raise securesystemslib.exceptions.FormatError return self.temporary_file.read(size) def write(self, data, auto_flush=True): """ Writes a data string to the file. data: A string containing some data. auto_flush: Boolean argument, if set to 'True', all data will be flushed from internal buffer. None. None. """ self.temporary_file.write(data) if auto_flush: self.flush() def move(self, destination_path): """ Copies 'self.temporary_file' to a non-temp file at 'destination_path' and closes 'self.temporary_file' so that it is removed. destination_path: Path to store the file in. None. None. """ self.flush() self.seek(0) destination_file = open(destination_path, 'wb') shutil.copyfileobj(self.temporary_file, destination_file) # Force the destination file to be written to disk from Python's internal # and the operation system's buffers. os.fsync() should follow flush(). destination_file.flush() os.fsync(destination_file.fileno()) destination_file.close() # 'self.close()' closes temporary file which destroys itself. self.close_temp_file() def seek(self, *args): """ Set file's current position. *args: (*-operator): unpacking argument list is used because seek method accepts two args: offset and whence. If whence is not specified, its default is 0. Indicate offset to set the file's current position. Refer to the python manual for more info. None. None. """ self.temporary_file.seek(*args) def decompress_temp_file_object(self, compression): """ To decompress a compressed temp file object. Decompression is performed on a temp file object that is compressed, this occurs after downloading a compressed file. For instance if a compressed version of some meta file in the repository is downloaded, the temp file containing the compressed meta file will be decompressed using this function. Note that after calling this method, write() can no longer be called. meta.json.gz |...[download] temporary_file (containing meta.json.gz) / \ temporary_file _orig_file containing meta.json containing meta.json.gz (decompressed data) compression: A string indicating the type of compression that was used to compress a file. Only gzip is allowed. securesystemslib.exceptions.FormatError: If 'compression' is improperly formatted. securesystemslib.exceptions.Error: If an invalid compression is given. securesystemslib.exceptions.DecompressionError: If the compression failed for any reason. 'self._orig_file' is used to store the original data of 'temporary_file'. None. """ # Does 'compression' have the correct format? # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.NAME_SCHEMA.check_match(compression) if self._orig_file is not None: raise securesystemslib.exceptions.Error('Can only set compression on a' ' TempFile once.') if compression != 'gzip': raise securesystemslib.exceptions.Error('Only gzip compression is' ' supported.') self.seek(0) self._compression = compression self._orig_file = self.temporary_file try: gzip_file_object = gzip.GzipFile(fileobj=self.temporary_file, mode='rb') uncompressed_content = gzip_file_object.read() self.temporary_file = tempfile.NamedTemporaryFile() self.temporary_file.write(uncompressed_content) self.flush() except Exception as exception: raise securesystemslib.exceptions.DecompressionError(exception) def close_temp_file(self): """ Closes the temporary file object. 'close_temp_file' mimics usual file.close(), however temporary file destroys itself when 'close_temp_file' is called. Further if compression is set, second temporary file instance 'self._orig_file' is also closed so that no open temporary files are left open. None. None. Closes 'self._orig_file'. None. """ self.temporary_file.close() # If compression has been set, we need to explicitly close the original # file object. if self._orig_file is not None: self._orig_file.close() def get_file_details(filepath, hash_algorithms=['sha256']): """ 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: 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. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) securesystemslib.formats.HASHALGORITHMS_SCHEMA.check_match(hash_algorithms) # The returned file hashes of 'filepath'. file_hashes = {} # Does the path exists? if not os.path.exists(filepath): raise securesystemslib.exceptions.Error('Path ' + repr(filepath) + ' doest' ' not exist.') filepath = os.path.abspath(filepath) # Obtaining length of the file. file_length = os.path.getsize(filepath) # Obtaining hash of the file. for algorithm in hash_algorithms: digest_object = securesystemslib.hash.digest_filename(filepath, 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. securesystemslib.formats.HASHDICT_SCHEMA.check_match(file_hashes) return file_length, file_hashes def ensure_parent_dir(filename): """ 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. 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. securesystemslib.formats.PATH_SCHEMA.check_match(filename) # Split 'filename' into head and tail, check if head exists. directory = os.path.split(filename)[0] if directory and not os.path.exists(directory): # mode = 'rwx------'. 448 (decimal) is 700 in octal. os.makedirs(directory, 448) 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. securesystemslib.formats.RELPATH_SCHEMA.check_match(filepath) securesystemslib.formats.RELPATHS_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. # TUF clients 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 TUF client 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 def find_delegated_role(roles, delegated_role): """ Find the index, if any, of a role with a given name in a list of roles. roles: The list of roles, each of which must have a 'name' attribute. delegated_role: The name of the role to be found in the list of roles. securesystemslib.exceptions.RepositoryError, if the list of roles has invalid data. No known side effects. The unique index, an interger, in the list of roles. if 'delegated_role' does not exist, 'None' is returned. """ # 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 any are improperly formatted. securesystemslib.formats.ROLELIST_SCHEMA.check_match(roles) securesystemslib.formats.ROLENAME_SCHEMA.check_match(delegated_role) # The index of a role, if any, with the same name. role_index = None for index in six.moves.xrange(len(roles)): role = roles[index] name = role.get('name') # This role has no name. if name is None: no_name_message = 'Role with no name.' raise securesystemslib.exceptions.RepositoryError(no_name_message) # Does this role have the same name? else: # This role has the same name, and... if name == delegated_role: # ...it is the only known role with the same name. if role_index is None: role_index = index # ...there are at least two roles with the same name. else: duplicate_role_message = 'Duplicate role (' + str(delegated_role) + ').' raise securesystemslib.exceptions.RepositoryError( 'Duplicate role (' + str(delegated_role) + ').') # This role has a different name. else: logger.debug('Skipping delegated role: ' + repr(delegated_role)) return role_index def ensure_all_targets_allowed(rolename, list_of_targets, parent_delegations): """ Ensure that the list of targets specified by 'rolename' are allowed; this is determined by inspecting the 'delegations' field of the parent role of 'rolename'. If a target specified by 'rolename' is not found in the delegations field of 'metadata_object_of_parent', raise an exception. The top-level role 'targets' is allowed to list any target file, so this function does not raise an exception if 'rolename' is 'targets'. Targets allowed are either exlicitly listed under the 'paths' field, or implicitly exist under a subdirectory of a parent directory listed under 'paths'. A parent role may delegate trust to all files under a particular directory, including files in subdirectories, by simply listing the directory (e.g., '/packages/source/Django/', the equivalent of '/packages/source/Django/*'). Targets listed in hashed bins are also validated (i.e., its calculated path hash prefix must be delegated by the parent role). TODO: Should the TUF spec restrict the repository to one particular algorithm when calcutating path hash prefixes (currently restricted to SHA256)? Should we allow the repository to specify in the role dictionary the algorithm used for these generated hashed paths? rolename: The name of the role whose targets must be verified. This is a role name and should not end in '.json'. Examples: 'root', 'targets', 'targets/linux/x86'. list_of_targets: The targets of 'rolename', as listed in targets field of the 'rolename' metadata. 'list_of_targets' are target paths relative to the targets directory of the repository. The delegations of the parent role are checked to verify that the targets of 'list_of_targets' are valid. parent_delegations: The parent delegations of 'rolename'. The metadata object stores the allowed paths and path hash prefixes of child delegations in its 'delegations' attribute. securesystemslib.exceptions.FormatError: If any of the arguments are improperly formatted. securesystemslib.exceptions.ForbiddenTargetError: If the targets of 'metadata_role' are not allowed according to the parent's metadata file. The 'paths' and 'path_hash_prefixes' attributes are verified. securesystemslib.exceptions.RepositoryError: If the parent of 'rolename' has not made a delegation to 'rolename'. None. None. """ # 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 any are improperly formatted. securesystemslib.formats.ROLENAME_SCHEMA.check_match(rolename) securesystemslib.formats.RELPATHS_SCHEMA.check_match(list_of_targets) securesystemslib.formats.DELEGATIONS_SCHEMA.check_match(parent_delegations) # Return if 'rolename' is 'targets'. 'targets' is not a delegated role. Any # target file listed in 'targets' is allowed. if rolename == 'targets': return # The allowed targets of delegated roles are stored in the parent's metadata # file. Iterate 'list_of_targets' and confirm they are trusted, or their # root parent directory exists in the role delegated paths, or path hash # prefixes, of the parent role. First, locate 'rolename' in the 'roles' # attribute of 'parent_delegations'. roles = parent_delegations['roles'] role_index = find_delegated_role(roles, rolename) # Ensure the delegated role exists prior to extracting trusted paths from # the parent's 'paths', or trusted path hash prefixes from the parent's # 'path_hash_prefixes'. if role_index is not None: role = roles[role_index] allowed_child_paths = role.get('paths') allowed_child_path_hash_prefixes = role.get('path_hash_prefixes') actual_child_targets = list_of_targets if allowed_child_path_hash_prefixes is not None: consistent = paths_are_consistent_with_hash_prefixes # 'actual_child_tarets' (i.e., 'list_of_targets') should have lenth # greater than zero due to the format check above. if not consistent(actual_child_targets, allowed_child_path_hash_prefixes): message = repr(rolename) + ' specifies a target that does not' + \ ' have a path hash prefix listed in its parent role.' raise securesystemslib.exceptions.ForbiddenTargetError(message) elif allowed_child_paths is not None: # Check that each delegated target is either explicitly listed or a # parent directory is found under role['paths'], otherwise raise an # exception. If the parent role explicitly lists target file paths in # 'paths', this loop will run in O(n^2), the worst-case. The repository # maintainer will likely delegate entire directories, and opt for # explicit file paths if the targets in a directory are delegated to # different roles/developers. for child_target in actual_child_targets: for allowed_child_path in allowed_child_paths: if fnmatch.fnmatch(child_target, allowed_child_path): break else: raise securesystemslib.exceptions.ForbiddenTargetError( 'Role ' + repr(rolename) + ' specifies' ' target' + repr(child_target) + ',' + ' which is not an allowed' ' path according to the delegations set by its parent role.') else: # 'role' should have been validated when it was downloaded. # The 'paths' or 'path_hash_prefixes' attributes should not be missing, # so raise an error in case this clause is reached. raise securesystemslib.exceptions.FormatError(repr(role) + ' did not' ' contain one of the required fields ("paths" or' ' "path_hash_prefixes").') # Raise an exception if the parent has not delegated to the specified # 'rolename' child role. else: raise securesystemslib.exceptions.RepositoryError('The parent role has' ' not delegated to ' + repr(rolename) + '.') def paths_are_consistent_with_hash_prefixes(paths, path_hash_prefixes): """ Determine whether a list of paths are consistent with their alleged path hash prefixes. By default, the SHA256 hash function is used. paths: A list of paths for which their hashes will be checked. path_hash_prefixes: The list of path hash prefixes with which to check the list of paths. securesystemslib.exceptions.FormatError: If the arguments are improperly formatted. No known side effects. A Boolean indicating whether or not the paths are consistent with the hash prefix. """ # 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 any are improperly formatted. securesystemslib.formats.RELPATHS_SCHEMA.check_match(paths) securesystemslib.formats.PATH_HASH_PREFIXES_SCHEMA.check_match(path_hash_prefixes) # Assume that 'paths' and 'path_hash_prefixes' are inconsistent until # proven otherwise. consistent = False # The format checks above ensure the 'paths' and 'path_hash_prefix' lists # have lengths greater than zero. for path in paths: path_hash = get_target_hash(path) # Assume that every path is inconsistent until proven otherwise. consistent = False for path_hash_prefix in path_hash_prefixes: if path_hash.startswith(path_hash_prefix): consistent = True break # This path has no matching path_hash_prefix. Stop looking further. if not consistent: break return consistent def get_target_hash(target_filepath): """ Compute the hash of 'target_filepath'. This is useful in conjunction with the "path_hash_prefixes" attribute in a delegated targets role, which tells us which paths it is implicitly responsible for. The repository may optionally organize targets into hashed bins to ease target delegations and role metadata management. The use of consistent hashing allows for a uniform distribution of targets into bins. target_filepath: The path to the target file on the repository. This will be relative to the 'targets' (or equivalent) directory on a given mirror. None. None. The hash of 'target_filepath'. """ # Does 'target_filepath' 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 there is a mismatch. securesystemslib.formats.RELPATH_SCHEMA.check_match(target_filepath) # Calculate the hash of the filepath to determine which bin to find the # target. The client currently assumes the repository uses # 'HASH_FUNCTION' to generate hashes and 'utf-8'. digest_object = securesystemslib.hash.digest(HASH_FUNCTION) encoded_target_filepath = target_filepath.encode('utf-8') digest_object.update(encoded_target_filepath) target_filepath_hash = digest_object.hexdigest() return target_filepath_hash _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: 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) of TUF. 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 securesystemslib.exceptions.Error(message) except ValueError: message = 'Cannot deserialize to a Python object: ' + repr(data) raise securesystemslib.exceptions.Error(message) else: return deserialized_object def load_json_file(filepath): """ Deserialize a JSON object from a file containing the object. filepath: Absolute path of JSON file. 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. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) deserialized_object = None # The file is mostly likely gzipped. if filepath.endswith('.gz'): logger.debug('gzip.open(' + str(filepath) + ')') fileobject = six.StringIO(gzip.open(filepath).read().decode('utf-8')) else: logger.debug('open(' + str(filepath) + ')') fileobject = open(filepath) try: deserialized_object = json.load(fileobject) except (ValueError, TypeError) as e: raise securesystemslib.exceptions.Error('Cannot deserialize to a' ' Python object: ' + repr(filepath)) else: fileobject.close() return deserialized_object finally: fileobject.close() 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. securesystemslib.formats.HEX_SCHEMA.check_match(digest1) securesystemslib.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 securesystemslib-0.11.3/securesystemslib/pyca_crypto_keys.py0000755000076600000240000011736513342052762024161 0ustar sstaff00000000000000#!/usr/bin/env python """ pyca_crypto_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 TUF 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 # Import pyca/cryptography routines needed to generate and load cryptographic # keys in PEM format. from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends.interfaces import PEMSerializationBackend 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. import cryptography.exceptions # '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 TUF 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 import securesystemslib.exceptions import securesystemslib.formats import securesystemslib.util # Extract/reference the cryptography library settings. import securesystemslib.settings # 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 = securesystemslib.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. 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. """ # 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. securesystemslib.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. 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'. """ # Does the arguments have the correct format? # If not, raise 'securesystemslib.exceptions.FormatError' if any of the # checks fail. securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_key) securesystemslib.formats.DATA_SCHEMA.check_match(data) securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme) # Signing 'data' requires a private key. 'rsassa-pss-sha256' is the only # currently supported signature scheme. 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 len(private_key): # An if-clause isn't strictly needed here, since 'rsasssa-pss-sha256' is # the only currently supported RSA scheme. Nevertheless, include the # conditional statement to accomodate future schemes that might be added. if scheme == 'rsassa-pss-sha256': # Generate an RSSA-PSS signature. Raise # 'securesystemslib.exceptions.CryptoError' for any of the expected # exceptions raised by pyca/cryptography. 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()) signature = private_key_object.sign( data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=hashes.SHA256().digest_size), hashes.SHA256()) # If the PEM data could not be decrypted, or if its structure could not # be decoded successfully. except ValueError: raise securesystemslib.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 securesystemslib.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 cryptography.exceptions.UnsupportedAlgorithm: #pragma: no cover raise securesystemslib.exceptions.CryptoError('The private key is' ' encrypted with an unsupported algorithm.') # The RSA_SCHEME_SCHEMA.check_match() above should have validated 'scheme'. # This is a defensive check check.. else: #pragma: no cover raise securesystemslib.exceptions.UnsupportedAlgorithmError('Unsupported' ' signature scheme is specified: ' + repr(scheme)) else: raise ValueError('The required private key is unset.') 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'. 'rsassa-pss-sha256' is currently supported. 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. pyca/cryptography's RSAPublicKey.verifier() called to do the actual 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.PEMRSA_SCHEMA'. Raise # 'securesystemslib.exceptions.FormatError' if the check fails. securesystemslib.formats.PEMRSA_SCHEMA.check_match(public_key) # Does 'signature_scheme' have the correct format? securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(signature_scheme) # Does 'signature' have the correct format? securesystemslib.formats.PYCACRYPTOSIGNATURE_SCHEMA.check_match(signature) # What about 'data'? securesystemslib.formats.DATA_SCHEMA.check_match(data) # Verify whether the private key of 'public_key' produced 'signature'. # Before returning the 'valid_signature' Boolean result, ensure 'RSASSA-PSS' # was used as the signature scheme. valid_signature = False # 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()) # verify() raises 'cryptography.exceptions.InvalidSignature' if the # signature is invalid. 'salt_length' is set to the digest size of the # hashing algorithm. try: public_key_object.verify(signature, data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=hashes.SHA256().digest_size), hashes.SHA256()) return True except cryptography.exceptions.InvalidSignature: return False # Raised by load_pem_public_key(). except (ValueError, cryptography.exceptions.UnsupportedAlgorithm) as e: raise securesystemslib.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. A string in PEM format (TraditionalOpenSSL), where the private RSA key is encrypted. Conforms to 'securesystemslib.formats.PEMRSA_SCHEMA'. """ # 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. securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_key) # Does 'passphrase' have the correct format? securesystemslib.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 securesystemslib.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 TUF 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. 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. """ # 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. securesystemslib.formats.PEMRSA_SCHEMA.check_match(pem) # If passed, does 'passphrase' have the correct format? if passphrase is not None: securesystemslib.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, cryptography.exceptions.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 securesystemslib.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 TUF 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 TUF 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 TUF 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 TUF format cannot be created. 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. """ # 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. securesystemslib.formats.ANYKEY_SCHEMA.check_match(key_object) # Does 'password' have the correct format? securesystemslib.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 securesystemslib.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 TUF key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA). This function calls the appropriate cryptography module (i.e., pyca_crypto_keys.py) to perform the decryption. Encrypted TUF 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 TUF 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 TUF key cannot be decrypted from 'encrypted_key'. securesystemslib.exceptions.Error, if a valid TUF key object is not found in 'encrypted_key'. 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. """ # 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. securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.check_match(encrypted_key) # Does 'password' have the correct format? securesystemslib.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 = securesystemslib.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 # 'pyca_crypto_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 = \ cryptography.hazmat.primitives.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, hmac, iv, ciphertext = \ file_contents.split(_ENCRYPTION_DELIMITER) except ValueError: raise securesystemslib.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 = \ cryptography.hazmat.primitives.hmac.HMAC(symmetric_key, hashes.SHA256(), backend=default_backend()) generated_hmac_object.update(ciphertext) generated_hmac = binascii.hexlify(generated_hmac_object.finalize()) if not securesystemslib.util.digests_are_equal(generated_hmac.decode(), hmac): raise securesystemslib.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 'pyca_crypto_keys.py' as a standalone module: # $ python pyca_crypto_keys.py import doctest doctest.testmod() securesystemslib-0.11.3/securesystemslib/interface.py0000755000076600000240000007777713342052762022546 0ustar sstaff00000000000000#!/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 errno import sys import time import datetime import getpass import logging import tempfile import shutil import json import gzip import random import securesystemslib.formats import securesystemslib.settings import securesystemslib.util import securesystemslib.keys import six from colorama import Fore # See 'log.py' to learn how logging is handled in securesystemslib. logger = logging.getLogger('securesystemslib_interface') # 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 # Supported key types. SUPPORTED_KEY_TYPES = ['rsa', 'ed25519'] def _prompt(message, result_type=str): """ Non-public function that prompts the user for input by logging 'message', converting the input to 'result_type', and returning the value to the caller. """ return result_type(six.moves.input(message)) def get_password(prompt='Password: ', confirm=False): """ Return the password entered by the user. If 'confirm' is True, the user is asked to enter the previously entered password once again. If they match, the password is returned to the caller. prompt: The text of the password prompt that is displayed to the user. confirm: Boolean indicating whether the user should be prompted for the password a second time. The two entered password must match, otherwise the user is again prompted for a password. None. None. The password entered by the user. """ # Are the arguments the expected type? # If not, raise 'securesystemslib.exceptions.FormatError'. securesystemslib.formats.TEXT_SCHEMA.check_match(prompt) securesystemslib.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 generate_and_write_rsa_keypair(filepath=None, bits=DEFAULT_RSA_KEY_BITS, password=None): """ Generate an RSA key pair. The public portion of the generated RSA key is saved to <'filepath'>.pub, whereas the private key portion is saved to <'filepath'>. If no password is given, the user is prompted for one. If the 'password' is an empty string, the private key is saved unencrypted to <'filepath'>. If the filepath is not given, the KEYID is used as the filename and the keypair saved to the current working directory. The best available form of encryption, for a given key's backend, is used with pyca/cryptography. According to their documentation, "it is a curated encryption choice and the algorithm may change over time." filepath: The public and private key files are saved to .pub and , respectively. If the filepath is not given, the public and private keys are saved to the current working directory as .pub and . KEYID is the generated key's KEYID. bits: The number of bits of the generated RSA key. password: The password to encrypt 'filepath'. If None, the user is prompted for a password. If an empty string is given, the private key is written to disk unencrypted. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. Writes key files to '' and '.pub'. The 'filepath' of the written key. """ # Does 'bits' have the correct format? # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(bits) # Generate the public and private RSA keys. rsa_key = securesystemslib.keys.generate_rsa_key(bits) public = rsa_key['keyval']['public'] private = rsa_key['keyval']['private'] if not filepath: filepath = os.path.join(os.getcwd(), rsa_key['keyid']) else: logger.debug('The filepath has been specified. Not using the key\'s' ' KEYID as the default filepath.') # Does 'filepath' have the correct format? securesystemslib.formats.PATH_SCHEMA.check_match(filepath) # If the caller does not provide a password argument, prompt for one. if password is None: # pragma: no cover # It is safe to specify the full path of 'filepath' in the prompt and not # worry about leaking sensitive information about the key's location. # However, care should be taken when including the full path in exceptions # and log files. password = get_password('Enter a password for the encrypted RSA' ' key (' + Fore.RED + filepath + Fore.RESET + '): ', confirm=True) else: logger.debug('The password has been specified. Not prompting for one') # Does 'password' have the correct format? securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) # Encrypt the private key if 'password' is set. if len(password): private = securesystemslib.keys.create_rsa_encrypted_pem(private, password) else: logger.debug('An empty password was given. Not encrypting the private key.') # If the parent directory of filepath does not exist, # create it (and all its parent directories, if necessary). securesystemslib.util.ensure_parent_dir(filepath) # Write the public key (i.e., 'public', which is in PEM format) to # '.pub'. (1) Create a temporary file, (2) write the contents of # the public key, and (3) move to final destination. file_object = securesystemslib.util.TempFile() file_object.write(public.encode('utf-8')) # The temporary file is closed after the final move. file_object.move(filepath + '.pub') # Write the private key in encrypted PEM format to ''. # Unlike the public key file, the private key does not have a file # extension. file_object = securesystemslib.util.TempFile() file_object.write(private.encode('utf-8')) file_object.move(filepath) return filepath def import_rsa_privatekey_from_file(filepath, password=None, scheme='rsassa-pss-sha256', prompt=False): """ Import the PEM file in 'filepath' containing the private key. If password is passed use passed password for decryption. If prompt is True use entered password for decryption. If no password is passed and either prompt is False or if the password entered at the prompt is an empty string, omit decryption, treating the key as if it is not encrypted. If password is passed and prompt is True, an error is raised. (See below.) The returned key is an object in the 'securesystemslib.formats.RSAKEY_SCHEMA' format. filepath: file, an RSA encrypted PEM file. Unlike the public RSA PEM key file, 'filepath' does not have an extension. password: The passphrase to decrypt 'filepath'. scheme: The signature scheme used by the imported key. prompt: If True the user is prompted for a passphrase to decrypt 'filepath'. Default is False. ValueError, if 'password' is passed and 'prompt' is True. ValueError, if 'password' is passed and it is an empty string. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.FormatError, if the entered password is improperly formatted. IOError, if 'filepath' can't be loaded. securesystemslib.exceptions.CryptoError, if a password is available and 'filepath' is not a valid key file encrypted using that password. securesystemslib.exceptions.CryptoError, if no password is available and 'filepath' is not a valid non-encrypted key file. The contents of 'filepath' are read, optionally decrypted, and returned. An RSA key object, conformant to 'securesystemslib.formats.RSAKEY_SCHEMA'. """ # Does 'filepath' 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 there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) # Is 'scheme' properly formatted? securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme) if password and prompt: raise ValueError("Passing 'password' and 'prompt' True is not allowed.") # If 'password' was passed check format and that it is not empty. if password is not None: securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) # TODO: PASSWORD_SCHEMA should be securesystemslib.schema.AnyString(min=1) if not len(password): raise ValueError('Password must be 1 or more characters') elif prompt: # Password confirmation disabled here, which should ideally happen only # when creating encrypted key files (i.e., improve usability). # It is safe to specify the full path of 'filepath' in the prompt and not # worry about leaking sensitive information about the key's location. # However, care should be taken when including the full path in exceptions # and log files. # NOTE: A user who gets prompted for a password, can only signal that the # key is not encrypted by entering no password in the prompt, as opposed # to a programmer who can call the function with or without a 'password'. # Hence, we treat an empty password here, as if no 'password' was passed. password = get_password('Enter a password for an encrypted RSA' ' file \'' + Fore.RED + filepath + Fore.RESET + '\': ', confirm=False) or 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. securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) else: logger.debug('No password was given. Attempting to import an' ' unencrypted file.') # Read the contents of 'filepath' that should be a PEM formatted private key. with open(filepath, 'rb') as file_object: pem_key = file_object.read().decode('utf-8') # Convert 'pem_key' to 'securesystemslib.formats.RSAKEY_SCHEMA' format. # Raise 'securesystemslib.exceptions.CryptoError' if 'pem_key' is invalid. # If 'password' is None decryption will be omitted. rsa_key = securesystemslib.keys.import_rsakey_from_private_pem(pem_key, scheme, password) return rsa_key def import_rsa_publickey_from_file(filepath): """ Import the RSA key stored in 'filepath'. The key object returned is in the format 'securesystemslib.formats.RSAKEY_SCHEMA'. If the RSA PEM in 'filepath' contains a private key, it is discarded. filepath: .pub file, an RSA PEM file. securesystemslib.exceptions.FormatError, if 'filepath' is improperly formatted. securesystemslib.exceptions.Error, if a valid RSA key object cannot be generated. This may be caused by an improperly formatted PEM file. 'filepath' is read and its contents extracted. An RSA key object conformant to 'securesystemslib.formats.RSAKEY_SCHEMA'. """ # Does 'filepath' 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 there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) # Read the contents of the key file that should be in PEM format and contains # the public portion of the RSA key. with open(filepath, 'rb') as file_object: rsa_pubkey_pem = file_object.read().decode('utf-8') # Convert 'rsa_pubkey_pem' to 'securesystemslib.formats.RSAKEY_SCHEMA' format. try: rsakey_dict = securesystemslib.keys.import_rsakey_from_public_pem(rsa_pubkey_pem) except securesystemslib.exceptions.FormatError as e: raise securesystemslib.exceptions.Error('Cannot import improperly formatted' ' PEM file.' + repr(str(e))) return rsakey_dict def generate_and_write_ed25519_keypair(filepath=None, password=None): """ Generate an Ed25519 keypair, where the encrypted key (using 'password' as the passphrase) is saved to <'filepath'>. The public key portion of the generated Ed25519 key is saved to <'filepath'>.pub. If the filepath is not given, the KEYID is used as the filename and the keypair saved to the current working directory. The private key is encrypted according to 'cryptography's approach: "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." filepath: The public and private key files are saved to .pub and , respectively. If the filepath is not given, the public and private keys are saved to the current working directory as .pub and . KEYID is the generated key's KEYID. password: The password, or passphrase, to encrypt the private portion of the generated Ed25519 key. A symmetric encryption key is derived from 'password', so it is not directly used. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.CryptoError, if 'filepath' cannot be encrypted. Writes key files to '' and '.pub'. The 'filepath' of the written key. """ # Generate a new Ed25519 key object. ed25519_key = securesystemslib.keys.generate_ed25519_key() if not filepath: filepath = os.path.join(os.getcwd(), ed25519_key['keyid']) else: logger.debug('The filepath has been specified. Not using the key\'s' ' KEYID as the default filepath.') # Does 'filepath' 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 there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) # If the caller does not provide a password argument, prompt for one. if password is None: # pragma: no cover # It is safe to specify the full path of 'filepath' in the prompt and not # worry about leaking sensitive information about the key's location. # However, care should be taken when including the full path in exceptions # and log files. password = get_password('Enter a password for the Ed25519' ' key (' + Fore.RED + filepath + Fore.RESET + '): ', confirm=True) else: logger.debug('The password has been specified. Not prompting for one.') # Does 'password' have the correct format? securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) # If the parent directory of filepath does not exist, # create it (and all its parent directories, if necessary). securesystemslib.util.ensure_parent_dir(filepath) # Create a temporary file, write the contents of the public key, and move # to final destination. file_object = securesystemslib.util.TempFile() # Generate the ed25519 public key file contents in metadata format (i.e., # does not include the keyid portion). keytype = ed25519_key['keytype'] keyval = ed25519_key['keyval'] scheme = ed25519_key['scheme'] ed25519key_metadata_format = securesystemslib.keys.format_keyval_to_metadata( keytype, scheme, keyval, private=False) file_object.write(json.dumps(ed25519key_metadata_format).encode('utf-8')) # Write the public key (i.e., 'public', which is in PEM format) to # '.pub'. (1) Create a temporary file, (2) write the contents of # the public key, and (3) move to final destination. # The temporary file is closed after the final move. file_object.move(filepath + '.pub') # Write the encrypted key string, conformant to # 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA', to ''. file_object = securesystemslib.util.TempFile() # Encrypt the private key if 'password' is set. if len(password): ed25519_key = securesystemslib.keys.encrypt_key(ed25519_key, password) else: logger.debug('An empty password was given. ' 'Not encrypting the private key.') ed25519_key = json.dumps(ed25519_key) # Raise 'securesystemslib.exceptions.CryptoError' if 'ed25519_key' cannot be # encrypted. file_object.write(ed25519_key.encode('utf-8')) file_object.move(filepath) return filepath def import_ed25519_publickey_from_file(filepath): """ Load the ED25519 public key object (conformant to 'securesystemslib.formats.KEY_SCHEMA') stored in 'filepath'. Return 'filepath' in securesystemslib.formats.ED25519KEY_SCHEMA format. If the key object in 'filepath' contains a private key, it is discarded. filepath: .pub file, a public key file. securesystemslib.exceptions.FormatError, if 'filepath' is improperly formatted or is an unexpected key type. The contents of 'filepath' is read and saved. An ED25519 key object conformant to 'securesystemslib.formats.ED25519KEY_SCHEMA'. """ # Does 'filepath' 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 there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) # ED25519 key objects are saved in json and metadata format. Return the # loaded key object in securesystemslib.formats.ED25519KEY_SCHEMA' format that # also includes the keyid. ed25519_key_metadata = securesystemslib.util.load_json_file(filepath) ed25519_key, junk = \ securesystemslib.keys.format_metadata_to_key(ed25519_key_metadata) # Raise an exception if an unexpected key type is imported. Redundant # validation of 'keytype'. 'securesystemslib.keys.format_metadata_to_key()' # should have fully validated 'ed25519_key_metadata'. if ed25519_key['keytype'] != 'ed25519': # pragma: no cover message = 'Invalid key type loaded: ' + repr(ed25519_key['keytype']) raise securesystemslib.exceptions.FormatError(message) return ed25519_key def import_ed25519_privatekey_from_file(filepath, password=None, prompt=False): """ Import the encrypted ed25519 key file in 'filepath', decrypt it, and return the key object in 'securesystemslib.formats.ED25519KEY_SCHEMA' format. The private key (may also contain the public part) is encrypted with AES 256 and CTR the mode of operation. The password is strengthened with PBKDF2-HMAC-SHA256. filepath: file, an RSA encrypted key file. password: The password, or passphrase, to import the private key (i.e., the encrypted key file 'filepath' must be decrypted before the ed25519 key object can be returned. prompt: If True the user is prompted for a passphrase to decrypt 'filepath'. Default is False. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted or the imported key object contains an invalid key type (i.e., not 'ed25519'). securesystemslib.exceptions.CryptoError, if 'filepath' cannot be decrypted. 'password' is used to decrypt the 'filepath' key file. An ed25519 key object of the form: 'securesystemslib.formats.ED25519KEY_SCHEMA'. """ # Does 'filepath' 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 there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) if password and prompt: raise ValueError("Passing 'password' and 'prompt' True is not allowed.") # If 'password' was passed check format and that it is not empty. if password is not None: securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) # TODO: PASSWORD_SCHEMA should be securesystemslib.schema.AnyString(min=1) if not len(password): raise ValueError('Password must be 1 or more characters') elif prompt: # Password confirmation disabled here, which should ideally happen only # when creating encrypted key files (i.e., improve usability). # It is safe to specify the full path of 'filepath' in the prompt and not # worry about leaking sensitive information about the key's location. # However, care should be taken when including the full path in exceptions # and log files. # NOTE: A user who gets prompted for a password, can only signal that the # key is not encrypted by entering no password in the prompt, as opposed # to a programmer who can call the function with or without a 'password'. # Hence, we treat an empty password here, as if no 'password' was passed. password = get_password('Enter a password for an encrypted RSA' ' file \'' + Fore.RED + filepath + Fore.RESET + '\': ', confirm=False) # If user sets an empty string for the password, explicitly set the # password to None, because some functions may expect this later. if len(password) == 0: # pragma: no cover password = None # Finally, regardless of password, try decrypting the key, if necessary. # Otherwise, load it straight from the disk. with open(filepath, 'rb') as file_object: json_str = file_object.read() return securesystemslib.keys.\ import_ed25519key_from_private_json(json_str, password=password) def generate_and_write_ecdsa_keypair(filepath=None, password=None): """ Generate an ECDSA keypair, where the encrypted key (using 'password' as the passphrase) is saved to <'filepath'>. The public key portion of the generated ECDSA key is saved to <'filepath'>.pub. If the filepath is not given, the KEYID is used as the filename and the keypair saved to the current working directory. The 'cryptography' library is currently supported. The private key is encrypted according to 'cryptography's approach: "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." filepath: The public and private key files are saved to .pub and , respectively. If the filepath is not given, the public and private keys are saved to the current working directory as .pub and . KEYID is the generated key's KEYID. password: The password, or passphrase, to encrypt the private portion of the generated ECDSA key. A symmetric encryption key is derived from 'password', so it is not directly used. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.CryptoError, if 'filepath' cannot be encrypted. Writes key files to '' and '.pub'. The 'filepath' of the written key. """ # Generate a new ECDSA key object. The 'cryptography' library is currently # supported and performs the actual cryptographic operations. ecdsa_key = securesystemslib.keys.generate_ecdsa_key() if not filepath: filepath = os.path.join(os.getcwd(), ecdsa_key['keyid']) else: logger.debug('The filepath has been specified. Not using the key\'s' ' KEYID as the default filepath.') # Does 'filepath' have the correct format? # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) # If the caller does not provide a password argument, prompt for one. if password is None: # pragma: no cover # It is safe to specify the full path of 'filepath' in the prompt and not # worry about leaking sensitive information about the key's location. # However, care should be taken when including the full path in exceptions # and log files. password = get_password('Enter a password for the ECDSA' ' key (' + Fore.RED + filepath + Fore.RESET + '): ', confirm=True) else: logger.debug('The password has been specified. Not prompting for one') # Does 'password' have the correct format? securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) # If the parent directory of filepath does not exist, # create it (and all its parent directories, if necessary). securesystemslib.util.ensure_parent_dir(filepath) # Create a temporary file, write the contents of the public key, and move # to final destination. file_object = securesystemslib.util.TempFile() # Generate the ECDSA public key file contents in metadata format (i.e., does # not include the keyid portion). keytype = ecdsa_key['keytype'] keyval = ecdsa_key['keyval'] scheme = ecdsa_key['scheme'] ecdsakey_metadata_format = securesystemslib.keys.format_keyval_to_metadata( keytype, scheme, keyval, private=False) file_object.write(json.dumps(ecdsakey_metadata_format).encode('utf-8')) # Write the public key (i.e., 'public', which is in PEM format) to # '.pub'. (1) Create a temporary file, (2) write the contents of # the public key, and (3) move to final destination. file_object.move(filepath + '.pub') # Write the encrypted key string, conformant to # 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA', to ''. file_object = securesystemslib.util.TempFile() # Raise 'securesystemslib.exceptions.CryptoError' if 'ecdsa_key' cannot be # encrypted. encrypted_key = securesystemslib.keys.encrypt_key(ecdsa_key, password) file_object.write(encrypted_key.encode('utf-8')) file_object.move(filepath) return filepath def import_ecdsa_publickey_from_file(filepath): """ Load the ECDSA public key object (conformant to 'securesystemslib.formats.KEY_SCHEMA') stored in 'filepath'. Return 'filepath' in securesystemslib.formats.ECDSAKEY_SCHEMA format. If the key object in 'filepath' contains a private key, it is discarded. filepath: .pub file, a public key file. securesystemslib.exceptions.FormatError, if 'filepath' is improperly formatted or is an unexpected key type. The contents of 'filepath' is read and saved. An ECDSA key object conformant to 'securesystemslib.formats.ECDSAKEY_SCHEMA'. """ # Does 'filepath' 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 there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) # ECDSA key objects are saved in json and metadata format. Return the # loaded key object in securesystemslib.formats.ECDSAKEY_SCHEMA' format that # also includes the keyid. ecdsa_key_metadata = securesystemslib.util.load_json_file(filepath) ecdsa_key, junk = \ securesystemslib.keys.format_metadata_to_key(ecdsa_key_metadata) # Raise an exception if an unexpected key type is imported. Redundant # validation of 'keytype'. 'securesystemslib.keys.format_metadata_to_key()' # should have fully validated 'ecdsa_key_metadata'. if ecdsa_key['keytype'] != 'ecdsa-sha2-nistp256': # pragma: no cover message = 'Invalid key type loaded: ' + repr(ecdsa_key['keytype']) raise securesystemslib.exceptions.FormatError(message) return ecdsa_key def import_ecdsa_privatekey_from_file(filepath, password=None): """ Import the encrypted ECDSA key file in 'filepath', decrypt it, and return the key object in 'securesystemslib.formats.ECDSAKEY_SCHEMA' format. The 'cryptography' library is currently supported and performs the actual cryptographic routine. filepath: file, an ECDSA encrypted key file. password: The password, or passphrase, to import the private key (i.e., the encrypted key file 'filepath' must be decrypted before the ECDSA key object can be returned. securesystemslib.exceptions.FormatError, if the arguments are improperly formatted or the imported key object contains an invalid key type (i.e., not 'ecdsa-sha2-nistp256'). securesystemslib.exceptions.CryptoError, if 'filepath' cannot be decrypted. 'password' is used to decrypt the 'filepath' key file. An ECDSA key object of the form: 'securesystemslib.formats.ECDSAKEY_SCHEMA'. """ # Does 'filepath' 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 there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) # If the caller does not provide a password argument, prompt for one. # Password confirmation disabled here, which should ideally happen only # when creating encrypted key files (i.e., improve usability). if password is None: # pragma: no cover # It is safe to specify the full path of 'filepath' in the prompt and not # worry about leaking sensitive information about the key's location. # However, care should be taken when including the full path in exceptions # and log files. password = get_password('Enter a password for the encrypted ECDSA' ' key (' + Fore.RED + filepath + Fore.RESET + '): ', confirm=False) # Does 'password' have the correct format? securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) # Store the encrypted contents of 'filepath' prior to calling the decryption # routine. encrypted_key = None with open(filepath, 'rb') as file_object: encrypted_key = file_object.read() # 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 = securesystemslib.keys.decrypt_key(encrypted_key.decode('utf-8'), password) # Raise an exception if an unexpected key type is imported. if key_object['keytype'] != 'ecdsa-sha2-nistp256': message = 'Invalid key type loaded: ' + repr(key_object['keytype']) raise securesystemslib.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'] = \ securesystemslib.settings.HASH_ALGORITHMS return key_object 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() securesystemslib-0.11.3/securesystemslib/ecdsa_keys.py0000755000076600000240000003743513342052762022703 0ustar sstaff00000000000000""" 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 # 'binascii' required for hexadecimal conversions. Signatures and # public/private keys are hexlified. import binascii import logging # Import cryptography modules to support ecdsa keys and signatures. 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.backends.interfaces import PEMSerializationBackend from cryptography.hazmat.primitives.serialization import load_pem_public_key from cryptography.hazmat.primitives.serialization import load_pem_private_key import cryptography.exceptions # Perform object format-checking and add ability to handle/raise exceptions. import securesystemslib.formats import securesystemslib.exceptions _SUPPORTED_ECDSA_SCHEMES = ['ecdsa-sha2-nistp256'] logger = logging.getLogger('securesystemslib_ecdsa_keys') 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. None. A (public, private) tuple that conform to 'securesystemslib.formats.PEMECDSA_SCHEMA' and 'securesystemslib.formats.PEMECDSA_SCHEMA', respectively. """ # 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. securesystemslib.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 securesystemslib.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. 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. """ # 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. securesystemslib.formats.PEMECDSA_SCHEMA.check_match(public_key) # Is 'private_key' properly formatted? securesystemslib.formats.PEMECDSA_SCHEMA.check_match(private_key) # Is 'scheme' properly formatted? securesystemslib.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 securesystemslib.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 securesystemslib.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. None. Boolean, indicating whether the 'signature' of data was generated by the private key associated with 'public_key'. """ # Are the arguments properly formatted? # If not, raise 'securesystemslib.exceptions.FormatError'. securesystemslib.formats.PEMECDSA_SCHEMA.check_match(public_key) securesystemslib.formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) securesystemslib.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 securesystemslib.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, ec.ECDSA(hashes.SHA256())) return True except (TypeError, cryptography.exceptions.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. 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'. securesystemslib.formats.PEMECDSA_SCHEMA.check_match(pem) if password is not None: securesystemslib.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, cryptography.exceptions.UnsupportedAlgorithm) as e: raise securesystemslib.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. None. A string in PEM format, where the private RSA portion is encrypted. Conforms to 'securesystemslib.formats.PEMECDSA_SCHEMA'. """ # Does 'private_key' have the correct format? # Raise 'securesystemslib.exceptions.FormatError' if the check fails. securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_pem) # Does 'passphrase' have the correct format? securesystemslib.formats.PASSWORD_SCHEMA.check_match(passphrase) encrypted_pem = None 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() securesystemslib-0.11.3/securesystemslib/__init__.py0000755000076600000240000000000013342052762022302 0ustar sstaff00000000000000securesystemslib-0.11.3/securesystemslib/keys.py0000755000076600000240000020347113342052762021537 0ustar sstaff00000000000000#!/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 # NOTE: 'warnings' needed to temporarily suppress user warnings raised by # 'pynacl' (as of version 0.2.3). # http://docs.python.org/2/library/warnings.html#temporarily-suppressing-warnings import warnings import logging # Try to import the pyca/Cryptography module (pyca_crypto_keys.py), which is # used for general-purpose cryptography and generation of RSA keys and # signatures. try: import securesystemslib.pyca_crypto_keys except ImportError: #pragma: no cover pass # 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. # NOTE: Version 0.2.3 of 'pynacl' prints: "UserWarning: reimporting '...' might # overwrite older definitions." when importing 'nacl.signing' below. Suppress # user warnings temporarily (at least until this issue is fixed). with warnings.catch_warnings(): warnings.simplefilter('ignore') try: import nacl import nacl.signing USE_PYNACL = True # PyNaCl's 'cffi' dependency may raise an 'IOError' exception when importing # 'nacl.signing'. except (ImportError, IOError): # pragma: no cover USE_PYNACL = False pass # The optimized version of the Ed25519 library provided by default is imported # regardless of the availability of PyNaCl. import securesystemslib.ed25519_keys try: import securesystemslib.ecdsa_keys except ImportError: #pragma: no cover pass import securesystemslib.exceptions # Digest objects needed to generate hashes. import securesystemslib.hash # Perform format checks of argument objects. import securesystemslib.formats # 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 logger = logging.getLogger('securesystemslib_keys') 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 of ['rsassa-pss-sha256']. 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. securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(bits) securesystemslib.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 = securesystemslib.pyca_crypto_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'] = securesystemslib.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-sha2-nistp256', '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. securesystemslib.formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) # Begin building the ECDSA key dictionary. ecdsa_key = {} keytype = 'ecdsa-sha2-nistp256' public = None private = None # Generate the public and private ECDSA keys with one of the supported # libraries. public, private = \ securesystemslib.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'] = \ securesystemslib.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. securesystemslib.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 = \ securesystemslib.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'] = securesystemslib.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. securesystemslib.formats.KEYTYPE_SCHEMA.check_match(keytype) # Does 'scheme' have the correct format? securesystemslib.formats.SCHEME_SCHEMA.check_match(scheme) # Does 'key_value' have the correct format? securesystemslib.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 securesystemslib.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': securesystemslib.settings.HASH_ALGORITHMS, 'keyval': public_key_value} def format_metadata_to_key(key_metadata): """ 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': '...'}} 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. securesystemslib.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. default_keyid = _get_keyid(keytype, scheme, key_value) keyids = set() keyids.add(default_keyid) for hash_algorithm in securesystemslib.settings.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'] = securesystemslib.settings.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 = securesystemslib.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 = securesystemslib.hash.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 object used by create_signature() to generate 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'. securesystemslib.formats.ANYKEY_SCHEMA.check_match(key_dict) # Signing the 'data' object requires a private key. 'rsassa-pss-sha256', # 'ed25519', and 'ecdsa-sha2-nistp256' are the only signing schemes currently # supported. RSASSA-PSS keys and signatures can be generated and verified by # pyca_crypto_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 # Convert 'data' to canonical JSON format so that repeatable signatures are # generated across different platforms and Python key dictionaries. The # resulting 'data' is a string encoded in UTF-8 and compatible with the input # expected by the cryptography functions called below. data = securesystemslib.formats.encode_canonical(data) if keytype == 'rsa': if scheme == 'rsassa-pss-sha256': private = private.replace('\r\n', '\n') sig, scheme = securesystemslib.pyca_crypto_keys.create_rsa_signature(private, data.encode('utf-8'), scheme) else: raise securesystemslib.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 = securesystemslib.ed25519_keys.create_signature(public, private, data.encode('utf-8'), scheme) elif keytype == 'ecdsa-sha2-nistp256': sig, scheme = securesystemslib.ecdsa_keys.create_signature(public, private, data.encode('utf-8'), 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 object used by securesystemslib.rsa_key.create_signature() to generate 'signature'. 'data' is needed here to verify 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. securesystemslib.formats.ANYKEY_SCHEMA.check_match(key_dict) # Does 'signature' have the correct format? securesystemslib.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 securesystemslib.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 # Convert 'data' to canonical JSON format so that repeatable signatures are # generated across different platforms and Python key dictionaries. The # resulting 'data' is a string encoded in UTF-8 and compatible with the input # expected by the cryptography functions called below. data = securesystemslib.formats.encode_canonical(data).encode('utf-8') if keytype == 'rsa': if scheme == 'rsassa-pss-sha256': valid_signature = securesystemslib.pyca_crypto_keys.verify_rsa_signature(sig, scheme, public, data) else: raise securesystemslib.exceptions.UnsupportedAlgorithmError('Unsupported' ' signature scheme is specified: ' + repr(scheme)) elif keytype == 'ed25519': if scheme == 'ed25519': public = binascii.unhexlify(public.encode('utf-8')) valid_signature = securesystemslib.ed25519_keys.verify_signature(public, scheme, sig, data, use_pynacl=USE_PYNACL) else: raise securesystemslib.exceptions.UnsupportedAlgorithmError('Unsupported' ' signature scheme is specified: ' + repr(scheme)) elif keytype == 'ecdsa-sha2-nistp256': if scheme == 'ecdsa-sha2-nistp256': valid_signature = securesystemslib.ecdsa_keys.verify_signature(public, scheme, sig, data) else: raise securesystemslib.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'. securesystemslib.formats.PEMRSA_SCHEMA.check_match(pem) # Is 'scheme' properly formatted? securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme) if password is not None: securesystemslib.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 = \ securesystemslib.pyca_crypto_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. securesystemslib.formats.PEMRSA_SCHEMA.check_match(pem) # Does 'scheme' have the correct format? securesystemslib.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 securesystemslib.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'] = \ securesystemslib.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. securesystemslib.formats.PEMRSA_SCHEMA.check_match(pem) # Is 'scheme' properly formatted? securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme) public_pem = '' private_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, password=None) else: raise securesystemslib.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'] = \ securesystemslib.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 securesystemslib.exceptions.FormatError('Required PEM' ' header ' + repr(pem_header) + '\n not found in PEM' ' string: ' + repr(pem)) else: raise securesystemslib.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 securesystemslib.exceptions.FormatError('Required PEM' ' footer ' + repr(pem_footer) + '\n not found in PEM' ' string ' + repr(pem)) else: raise securesystemslib.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 pyca_crypto_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. securesystemslib.formats.ANYKEY_SCHEMA.check_match(key_object) # Does 'password' have the correct format? securesystemslib.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 = securesystemslib.pyca_crypto_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 pyca_crypto_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. securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.check_match(encrypted_key) # Does 'passphrase' have the correct format? securesystemslib.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 = \ securesystemslib.pyca_crypto_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. securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_key) # Does 'passphrase' have the correct format? securesystemslib.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 = securesystemslib.pyca_crypto_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. securesystemslib.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. securesystemslib.formats.PEMRSA_SCHEMA.check_match(pem) securesystemslib.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 securesystemslib.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. securesystemslib.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 = securesystemslib.keys.\ decrypt_key(json_str.decode('utf-8'), password) else: logger.debug('No password was given. Attempting to import an' ' unencrypted file.') try: key_object = \ securesystemslib.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 securesystemslib.exceptions.Error: raise securesystemslib.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 securesystemslib.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'] = \ securesystemslib.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-sha2-nistp256', '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'. securesystemslib.formats.PEMECDSA_SCHEMA.check_match(pem) # Is 'scheme' properly formatted? securesystemslib.formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) if password is not None: securesystemslib.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-sha2-nistp256' public = None private = None public, private = \ securesystemslib.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'] = \ securesystemslib.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-sha2-nistp256', '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. securesystemslib.formats.PEMECDSA_SCHEMA.check_match(pem) # Is 'scheme' properly formatted? securesystemslib.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 securesystemslib.exceptions.FormatError('Invalid public' ' pem: ' + repr(pem)) # Begin building the ECDSA key dictionary. ecdsakey_dict = {} keytype = 'ecdsa-sha2-nistp256' # 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'] = \ securesystemslib.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. securesystemslib.formats.PEMECDSA_SCHEMA.check_match(pem) # Is 'scheme' properly formatted? securesystemslib.formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) public_pem = '' private_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 securesystemslib.exceptions.FormatError('PEM contains neither a public' ' nor private key: ' + repr(pem)) # Begin building the ECDSA key dictionary. ecdsakey_dict = {} keytype = 'ecdsa-sha2-nistp256' # 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() securesystemslib-0.11.3/securesystemslib/unittest_toolbox.py0000755000076600000240000001007213342052762024202 0ustar sstaff00000000000000""" 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 sys 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 securesystemslib-0.11.3/securesystemslib/hash.py0000755000076600000240000002131613353237104021500 0ustar sstaff00000000000000#!/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 TUF 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 logging import hashlib import six import securesystemslib.exceptions import securesystemslib.formats # Import securesystemslib logger to log warning messages. logger = logging.getLogger('securesystemslib.hash') DEFAULT_CHUNK_SIZE = 4096 DEFAULT_HASH_ALGORITHM = 'sha256' DEFAULT_HASH_LIBRARY = 'hashlib' SUPPORTED_LIBRARIES = ['hashlib'] 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)). """ # Are the arguments properly formatted? If not, raise # 'securesystemslib.exceptions.FormatError'. securesystemslib.formats.NAME_SCHEMA.check_match(algorithm) securesystemslib.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: return hashlib.new(algorithm) except ValueError: raise securesystemslib.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: #pragma: no cover # TODO: Add support for pyca/cryptography's hashing routines. pass # The requested hash library is not supported. else: raise securesystemslib.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)). """ # Are the arguments properly formatted? If not, raise # 'securesystemslib.exceptions.FormatError'. securesystemslib.formats.NAME_SCHEMA.check_match(algorithm) securesystemslib.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): """ 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. 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)). """ # Are the arguments properly formatted? If not, raise # 'securesystemslib.exceptions.FormatError'. securesystemslib.formats.RELPATH_SCHEMA.check_match(filename) securesystemslib.formats.NAME_SCHEMA.check_match(algorithm) securesystemslib.formats.NAME_SCHEMA.check_match(hash_library) digest_object = None # Open 'filename' in read+binary mode. with open(filename, 'rb') 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 securesystemslib-0.11.3/securesystemslib/formats.py0000755000076600000240000006635213342052762022244 0ustar sstaff00000000000000#!/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 TUF 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 TUF 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 TUF 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 string import datetime import time import six import securesystemslib.schema as SCHEMA import securesystemslib.exceptions # 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. # 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. HASH_SCHEMA = SCHEMA.RegularExpression(r'[a-fA-F0-9]+') # A dict in {'sha256': '23432df87ab..', 'sha512': '34324abc34df..', ...} format. HASHDICT_SCHEMA = SCHEMA.DictOf( key_schema = SCHEMA.AnyString(), value_schema = HASH_SCHEMA) # A hexadecimal value in '23432df87ab..' format. HEX_SCHEMA = SCHEMA.RegularExpression(r'[a-fA-F0-9]+') # 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 relative file path (e.g., 'metadata/root/'). RELPATH_SCHEMA = SCHEMA.AnyString() RELPATHS_SCHEMA = SCHEMA.ListOf(RELPATH_SCHEMA) # An absolute path. PATH_SCHEMA = SCHEMA.AnyString() PATHS_SCHEMA = SCHEMA.ListOf(PATH_SCHEMA) # Uniform Resource Locator identifier (e.g., 'https://www.updateframework.com/'). URL_SCHEMA = SCHEMA.AnyString() # A dictionary holding version information. VERSION_SCHEMA = SCHEMA.Object( object_name = 'VERSION_SCHEMA', major = SCHEMA.Integer(lo=0), minor = SCHEMA.Integer(lo=0), fix = SCHEMA.Integer(lo=0)) # An integer representing the numbered version of a metadata file. # Must be 1, or greater. METADATAVERSION_SCHEMA = SCHEMA.Integer(lo=0) # An integer representing length. Must be 0, or greater. LENGTH_SCHEMA = SCHEMA.Integer(lo=0) # 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')])) # The contents of an encrypted TUF key. Encrypted TUF 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() # A role's threshold value (i.e., the minimum number # of signatures required to sign a metadata file). # Must be 1 and greater. THRESHOLD_SCHEMA = SCHEMA.Integer(lo=1) # A string representing a role's name. ROLENAME_SCHEMA = SCHEMA.AnyString() # 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-sha2-nistp256 is supported by # default). ECDSA_SCHEME_SCHEMA = SCHEMA.OneOf([SCHEMA.String('ecdsa-sha2-nistp256')]) # The number of hashed bins, or the number of delegated roles. See # delegate_hashed_bins() in 'repository_tool.py' for an example. Note: # Tools may require further restrictions on the number of bins, such # as requiring them to be a power of 2. NUMBINS_SCHEMA = SCHEMA.Integer(lo=1) # 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 TUF key types. KEYTYPE_SCHEMA = SCHEMA.OneOf( [SCHEMA.String('rsa'), SCHEMA.String('ed25519'), SCHEMA.String('ecdsa-sha2-nistp256')]) # A generic TUF key. All TUF 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 TUF 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 TUF key objects. ANYKEYLIST_SCHEMA = SCHEMA.ListOf(ANYKEY_SCHEMA) # RSA signature schemes. RSA_SCHEME_SCHEMA = SCHEMA.OneOf([SCHEMA.String('rsassa-pss-sha256')]) # An RSA TUF 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 TUF key. ECDSAKEY_SCHEMA = SCHEMA.Object( object_name = 'ECDSAKEY_SCHEMA', keytype = SCHEMA.String('ecdsa-sha2-nistp256'), 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() # Required installation libraries expected by the repository tools and other # cryptography modules. REQUIRED_LIBRARIES_SCHEMA = SCHEMA.ListOf(SCHEMA.OneOf( [SCHEMA.String('general'), SCHEMA.String('ed25519'), SCHEMA.String('rsa'), SCHEMA.String('ecdsa-sha2-nistp256')])) # Ed25519 signature schemes. The vanilla Ed25519 signature scheme is currently # supported. ED25519_SIG_SCHEMA = SCHEMA.OneOf([SCHEMA.String('ed25519')]) # An ed25519 TUF 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) # Information about target files, like file length and file hash(es). This # schema allows the storage of multiple hashes for the same file (e.g., sha256 # and sha512 may be computed for the same file and stored). FILEINFO_SCHEMA = SCHEMA.Object( object_name = 'FILEINFO_SCHEMA', length = LENGTH_SCHEMA, hashes = HASHDICT_SCHEMA, version = SCHEMA.Optional(METADATAVERSION_SCHEMA), custom = SCHEMA.Optional(SCHEMA.Object())) # Version information specified in "snapshot.json" for each role available on # the TUF repository. The 'FILEINFO_SCHEMA' object was previously listed in # the snapshot role, but was switched to this object format to reduce the # amount of metadata that needs to be downloaded. Listing version numbers in # "snapshot.json" also prevents rollback attacks for roles that clients have # not downloaded. VERSIONINFO_SCHEMA = SCHEMA.Object( object_name = 'VERSIONINFO_SCHEMA', version = METADATAVERSION_SCHEMA) # A dict holding the version information for a particular metadata role. The # dict keys hold the relative file paths, and the dict values the corresponding # version numbers. VERSIONDICT_SCHEMA = SCHEMA.DictOf( key_schema = RELPATH_SCHEMA, value_schema = VERSIONINFO_SCHEMA) # A dict holding the information for a particular target / file. The dict keys # hold the relative file paths, and the dict values the corresponding file # information. FILEDICT_SCHEMA = SCHEMA.DictOf( key_schema = RELPATH_SCHEMA, value_schema = FILEINFO_SCHEMA) # 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) # List of SIGNATURE_SCHEMA. SIGNATURES_SCHEMA = SCHEMA.ListOf(SIGNATURE_SCHEMA) # A schema holding the result of checking the signatures of a particular # 'SIGNABLE_SCHEMA' role. # For example, how many of the signatures for the 'Target' role are # valid? This SCHEMA holds this information. See 'sig.py' for # more information. SIGNATURESTATUS_SCHEMA = SCHEMA.Object( object_name = 'SIGNATURESTATUS_SCHEMA', threshold = SCHEMA.Integer(), good_sigs = KEYIDS_SCHEMA, bad_sigs = KEYIDS_SCHEMA, unknown_sigs = KEYIDS_SCHEMA, untrusted_sigs = KEYIDS_SCHEMA) # A signable object. Holds the signing role and its associated signatures. SIGNABLE_SCHEMA = SCHEMA.Object( object_name = 'SIGNABLE_SCHEMA', signed = SCHEMA.Any(), signatures = SCHEMA.ListOf(SIGNATURE_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) # The format used by the key database to store keys. The dict keys hold a key # identifier and the dict values any object. The key database should store # key objects in the values (e.g., 'RSAKEY_SCHEMA', 'DSAKEY_SCHEMA'). KEYDB_SCHEMA = SCHEMA.DictOf( key_schema = KEYID_SCHEMA, value_schema = SCHEMA.Any()) # A path hash prefix is a hexadecimal string. PATH_HASH_PREFIX_SCHEMA = HEX_SCHEMA # A list of path hash prefixes. PATH_HASH_PREFIXES_SCHEMA = SCHEMA.ListOf(PATH_HASH_PREFIX_SCHEMA) # Role object in {'keyids': [keydids..], 'name': 'ABC', 'threshold': 1, # 'paths':[filepaths..]} format. ROLE_SCHEMA = SCHEMA.Object( object_name = 'ROLE_SCHEMA', name = SCHEMA.Optional(ROLENAME_SCHEMA), keyids = KEYIDS_SCHEMA, threshold = THRESHOLD_SCHEMA, backtrack = SCHEMA.Optional(BOOLEAN_SCHEMA), paths = SCHEMA.Optional(RELPATHS_SCHEMA), path_hash_prefixes = SCHEMA.Optional(PATH_HASH_PREFIXES_SCHEMA)) # A dict of roles where the dict keys are role names and the dict values holding # the role data/information. ROLEDICT_SCHEMA = SCHEMA.DictOf( key_schema = ROLENAME_SCHEMA, value_schema = ROLE_SCHEMA) # Like ROLEDICT_SCHEMA, except that ROLE_SCHEMA instances are stored in order. ROLELIST_SCHEMA = SCHEMA.ListOf(ROLE_SCHEMA) # The delegated roles of a Targets role (a parent). DELEGATIONS_SCHEMA = SCHEMA.Object( keys = KEYDICT_SCHEMA, roles = ROLELIST_SCHEMA) # The fileinfo format of targets specified in the repository and # developer tools. The second element of this list holds custom data about the # target, such as file permissions, author(s), last modified, etc. CUSTOM_SCHEMA = SCHEMA.Object() PATH_FILEINFO_SCHEMA = SCHEMA.DictOf( key_schema = RELPATH_SCHEMA, value_schema = CUSTOM_SCHEMA) # TUF roledb ROLEDB_SCHEMA = SCHEMA.Object( object_name = 'ROLEDB_SCHEMA', keyids = SCHEMA.Optional(KEYIDS_SCHEMA), signing_keyids = SCHEMA.Optional(KEYIDS_SCHEMA), previous_keyids = SCHEMA.Optional(KEYIDS_SCHEMA), threshold = SCHEMA.Optional(THRESHOLD_SCHEMA), previous_threshold = SCHEMA.Optional(THRESHOLD_SCHEMA), version = SCHEMA.Optional(METADATAVERSION_SCHEMA), expires = SCHEMA.Optional(ISO8601_DATETIME_SCHEMA), signatures = SCHEMA.Optional(SIGNATURES_SCHEMA), paths = SCHEMA.Optional(SCHEMA.OneOf([RELPATHS_SCHEMA, PATH_FILEINFO_SCHEMA])), path_hash_prefixes = SCHEMA.Optional(PATH_HASH_PREFIXES_SCHEMA), delegations = SCHEMA.Optional(DELEGATIONS_SCHEMA), partial_loaded = SCHEMA.Optional(BOOLEAN_SCHEMA)) # Root role: indicates root keys and top-level roles. ROOT_SCHEMA = SCHEMA.Object( object_name = 'ROOT_SCHEMA', _type = SCHEMA.String('root'), version = METADATAVERSION_SCHEMA, consistent_snapshot = BOOLEAN_SCHEMA, expires = ISO8601_DATETIME_SCHEMA, keys = KEYDICT_SCHEMA, roles = ROLEDICT_SCHEMA) # Targets role: Indicates targets and delegates target paths to other roles. TARGETS_SCHEMA = SCHEMA.Object( object_name = 'TARGETS_SCHEMA', _type = SCHEMA.String('targets'), version = METADATAVERSION_SCHEMA, expires = ISO8601_DATETIME_SCHEMA, targets = FILEDICT_SCHEMA, delegations = SCHEMA.Optional(DELEGATIONS_SCHEMA)) # Snapshot role: indicates the latest versions of all metadata (except # timestamp). SNAPSHOT_SCHEMA = SCHEMA.Object( object_name = 'SNAPSHOT_SCHEMA', _type = SCHEMA.String('snapshot'), version = METADATAVERSION_SCHEMA, expires = ISO8601_DATETIME_SCHEMA, meta = VERSIONDICT_SCHEMA) # Timestamp role: indicates the latest version of the snapshot file. TIMESTAMP_SCHEMA = SCHEMA.Object( object_name = 'TIMESTAMP_SCHEMA', _type = SCHEMA.String('timestamp'), version = METADATAVERSION_SCHEMA, expires = ISO8601_DATETIME_SCHEMA, meta = FILEDICT_SCHEMA) # project.cfg file: stores information about the project in a json dictionary PROJECT_CFG_SCHEMA = SCHEMA.Object( object_name = 'PROJECT_CFG_SCHEMA', project_name = SCHEMA.AnyString(), layout_type = SCHEMA.OneOf([SCHEMA.String('repo-like'), SCHEMA.String('flat')]), targets_location = PATH_SCHEMA, metadata_location = PATH_SCHEMA, prefix = PATH_SCHEMA, public_keys = KEYDICT_SCHEMA, threshold = SCHEMA.Integer(lo = 0, hi = 2) ) # A schema containing information a repository mirror may require, # such as a url, the path of the directory metadata files, etc. MIRROR_SCHEMA = SCHEMA.Object( object_name = 'MIRROR_SCHEMA', url_prefix = URL_SCHEMA, metadata_path = RELPATH_SCHEMA, targets_path = RELPATH_SCHEMA, confined_target_dirs = RELPATHS_SCHEMA, custom = SCHEMA.Optional(SCHEMA.Object())) # A dictionary of mirrors where the dict keys hold the mirror's name and # and the dict values the mirror's data (i.e., 'MIRROR_SCHEMA'). # The repository class of 'updater.py' accepts dictionaries # of this type provided by the TUF client. MIRRORDICT_SCHEMA = SCHEMA.DictOf( key_schema = SCHEMA.AnyString(), value_schema = MIRROR_SCHEMA) # A Mirrorlist: indicates all the live mirrors, and what documents they # serve. MIRRORLIST_SCHEMA = SCHEMA.Object( object_name = 'MIRRORLIST_SCHEMA', _type = SCHEMA.String('mirrors'), version = METADATAVERSION_SCHEMA, expires = ISO8601_DATETIME_SCHEMA, mirrors = SCHEMA.ListOf(MIRROR_SCHEMA)) # Any of the role schemas (e.g., TIMESTAMP_SCHEMA, SNAPSHOT_SCHEMA, etc.) ANYROLE_SCHEMA = SCHEMA.OneOf([ROOT_SCHEMA, TARGETS_SCHEMA, SNAPSHOT_SCHEMA, TIMESTAMP_SCHEMA, MIRROR_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 securesystemslib.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. securesystemslib.formats.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 securesystemslib.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 securesystemslib.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 securesystemslib.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 securesystemslib.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 TUF. 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, securesystemslib.exceptions.FormatError) as e: message = 'Could not encode ' + repr(object) + ': ' + str(e) raise securesystemslib.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) securesystemslib-0.11.3/securesystemslib/settings.py0000755000076600000240000000327213342052762022421 0ustar sstaff00000000000000#!/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'] securesystemslib-0.11.3/securesystemslib/exceptions.py0000755000076600000240000001077013342052762022743 0ustar sstaff00000000000000""" 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 import logging import six logger = logging.getLogger('securesystemslib.exceptions') 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 BadVersionNumberError(Error): """Indicate an error for metadata that contains an invalid version number.""" class BadPasswordError(Error): """Indicate an error after encountering an invalid password.""" pass class UnknownKeyError(Error): """Indicate an error while verifying key-like objects (e.g., keyids).""" pass class RepositoryError(Error): """Indicate an error with a repository's state, such as a missing file.""" pass class InsufficientKeysError(Error): """Indicate that metadata role lacks a threshold of pubic or private keys.""" pass class ForbiddenTargetError(RepositoryError): """Indicate that a role signed for a target that it was not delegated to.""" pass class ExpiredMetadataError(Error): """Indicate that a Metadata file has expired.""" pass class CryptoError(Error): """Indicate any cryptography-related errors.""" pass class BadSignatureError(CryptoError): """Indicate that some metadata file 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 DecompressionError(Error): """Indicate that some error happened while decompressing a file.""" def __init__(self, exception): # Store the original exception. self.exception = exception def __str__(self): # Show the original exception. return repr(self.exception) class DownloadError(Error): """Indicate an error occurred while attempting to download a file.""" pass class KeyAlreadyExistsError(Error): """Indicate that a key already exists and cannot be added.""" pass class RoleAlreadyExistsError(Error): """Indicate that a role already exists and cannot be added.""" pass class UnknownRoleError(Error): """Indicate an error trying to locate or identify a specified role.""" pass class UnknownTargetError(Error): """Indicate an error trying to locate or identify a specified target.""" 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 securesystemslib-0.11.3/securesystemslib/schema.py0000755000076600000240000006751213342052762022030 0ustar sstaff00000000000000#!/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 TUF 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 import securesystemslib.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 securesystemslib.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 securesystemslib.exceptions.FormatError('Expected a string but' ' got ' + repr(string)) self._string = string def check_match(self, object): if self._string != object: raise securesystemslib.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 securesystemslib.exceptions.FormatError('Expected a string' ' 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 securesystemslib.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 securesystemslib.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 securesystemslib.exceptions.FormatError('Expected a string but' ' got ' + repr(object)) if len(object) != self._string_length: raise securesystemslib.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 securesystemslib.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 securesystemslib.exceptions.FormatError('Expected a byte but' ' got ' + repr(object)) if len(object) != self._bytes_length: raise securesystemslib.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 securesystemslib.exceptions.FormatError('Expected a list but' ' got ' + repr(alternatives)) for alternative in alternatives: if not isinstance(alternative, Schema): raise securesystemslib.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 securesystemslib.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 securesystemslib.exceptions.FormatError('Expected a list but' ' got' + repr(required_schemas)) for schema in required_schemas: if not isinstance(schema, Schema): raise securesystemslib.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 securesystemslib.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 securesystemslib.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 securesystemslib.exceptions.FormatError( 'Expected ' + repr(self._list_name) + ' but got ' + repr(object)) # Check if all the items in the 'object' list # match 'schema'. for item in object: try: self._schema.check_match(item) except securesystemslib.exceptions.FormatError as e: raise securesystemslib.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 securesystemslib.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 securesystemslib.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 securesystemslib.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 securesystemslib.exceptions.FormatError('Expected Schema but' ' got ' + repr(key_schema)) if not isinstance(value_schema, Schema): raise securesystemslib.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 securesystemslib.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 securesystemslib.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 securesystemslib.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 securesystemslib.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): message = 'Missing key ' + repr(key) + ' in ' + repr(self._object_name) raise securesystemslib.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 securesystemslib.exceptions.FormatError as e: raise securesystemslib.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=[], allow_more=False, struct_name='list'): """ Create a new Struct schema. sub_schemas: The sub-schemas recognized. optional_schemas: The optional list of schemas. allow_more: Specifies that an optional list of types is allowed. struct_name: A string identifier for the Struct object. """ # Ensure each item of the list contains the expected object type. if not isinstance(sub_schemas, (list, tuple)): raise securesystemslib.exceptions.FormatError( 'Expected Schema but got ' + repr(sub_schemas)) for schema in sub_schemas: if not isinstance(schema, Schema): raise securesystemslib.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 securesystemslib.exceptions.FormatError( 'Expected ' + repr(self._struct_name) + '; but got ' + repr(object)) elif len(object) < self._min: raise securesystemslib.exceptions.FormatError( 'Too few fields in ' + self._struct_name) elif len(object) > len(self._sub_schemas) and not self._allow_more: raise securesystemslib.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 securesystemslib.exceptions.FormatError( repr(pattern) + ' is not a string.') if re_object is None: if pattern is None: raise securesystemslib.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 securesystemslib.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() securesystemslib-0.11.3/securesystemslib/ed25519_keys.py0000755000076600000240000003613613342052762022617 0ustar sstaff00000000000000""" 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 # 'binascii' required for hexadecimal conversions. Signatures and # public/private keys are hexlified. import binascii # TODO: The 'warnings' module needed to temporarily suppress user warnings # raised by 'pynacl' (as of version 0.2.3). Warnings temporarily suppressed # here to avoid confusing users with an unexpected error message that gives # no indication of its source. These warnings are printed when using # the repository tools, including for clients that request an update. # http://docs.python.org/2/library/warnings.html#temporarily-suppressing-warnings import warnings # '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. # # TODO: Version 0.2.3 of 'pynacl' prints: "UserWarning: reimporting '...' might # overwrite older definitions." when importing 'nacl.signing'. Suppress user # warnings temporarily (at least until this issue is fixed by PyNaCl). # # Note: A 'pragma: no cover' comment is intended for test 'coverage'. Lines # or code blocks with this comment should not be flagged as uncovered. # pynacl will always be install prior to running the unit tests. with warnings.catch_warnings(): warnings.simplefilter('ignore') try: import nacl.signing import nacl.encoding # PyNaCl's 'cffi' dependency may raise an 'IOError' exception when importing # 'nacl.signing'. except (ImportError, IOError): # pragma: no cover pass # The optimized pure Python implementation of Ed25519 provided by TUF. If # PyNaCl cannot be imported and an attempt to use is made in this module, a # 'securesystemslib.exceptions.UnsupportedLibraryError' exception is raised. import securesystemslib._vendor.ed25519.ed25519 import securesystemslib.formats import securesystemslib.exceptions # 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.ED25519PULIC_SCHEMA' and 'securesystemslib.formats.ED25519SEED_SCHEMA', respectively, and have the form: '\xa2F\x99\xe0\x86\x80%\xc8\xee\x11\xb95T\xd9\...' 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. """ # 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. try: nacl_key = nacl.signing.SigningKey(seed) public = nacl_key.verify_key.encode(encoder=nacl.encoding.RawEncoder()) except NameError: # pragma: no cover raise securesystemslib.exceptions.UnsupportedLibraryError('The PyNaCl' ' library and/or its dependencies unavailable.') 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. 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. """ # 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. securesystemslib.formats.ED25519PUBLIC_SCHEMA.check_match(public_key) # Is 'private_key' properly formatted? securesystemslib.formats.ED25519SEED_SCHEMA.check_match(private_key) # Is 'scheme' properly formatted? securesystemslib.formats.ED25519_SIG_SCHEMA.check_match(scheme) # Signing the 'data' object requires a seed and public key. # nacl.signing.SigningKey.sign() generates the signature. public = public_key private = private_key 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 = nacl.signing.SigningKey(private) nacl_sig = nacl_key.sign(data) signature = nacl_sig.signature # The unit tests expect required libraries to be installed. except NameError: # pragma: no cover raise securesystemslib.exceptions.UnsupportedLibraryError('The PyNaCl' ' library and/or its dependencies unavailable.') except (ValueError, TypeError, nacl.exceptions.CryptoError) as e: raise securesystemslib.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 securesystemslib.exceptions.UnsupportedAlgorithmError('Unsupported' ' signature scheme is specified: ' + repr(scheme)) return signature, scheme def verify_signature(public_key, scheme, signature, data, use_pynacl=False): """ 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, use_pynacl=False) True >>> verify_signature(public, scheme, signature, data, use_pynacl=True) 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, use_pynacl=False) 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. use_pynacl: True, if the ed25519 signature should be verified by PyNaCl. False, if the signature should be verified with the pure Python implementation of ed25519 (slower). 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. securesystemslib._vendor.ed25519.ed25519.checkvalid() called to do the actual verification. nacl.signing.VerifyKey.verify() called if 'use_pynacl' is True. 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. securesystemslib.formats.ED25519PUBLIC_SCHEMA.check_match(public_key) # Is 'scheme' properly formatted? securesystemslib.formats.ED25519_SIG_SCHEMA.check_match(scheme) # Is 'signature' properly formatted? securesystemslib.formats.ED25519SIGNATURE_SCHEMA.check_match(signature) # Is 'use_pynacl' properly formatted? securesystemslib.formats.BOOLEAN_SCHEMA.check_match(use_pynacl) # Verify 'signature'. Before returning the Boolean result, ensure 'ed25519' # was used as the signature scheme. Raise # 'securesystemslib.exceptions.UnsupportedLibraryError' if 'use_pynacl' is # True but 'nacl' is unavailable. public = public_key valid_signature = False if scheme in _SUPPORTED_ED25519_SIGNING_SCHEMES: if use_pynacl: try: nacl_verify_key = nacl.signing.VerifyKey(public) nacl_message = nacl_verify_key.verify(data, signature) valid_signature = True # The unit tests expect PyNaCl to be installed. except NameError: # pragma: no cover raise securesystemslib.exceptions.UnsupportedLibraryError('The PyNaCl' ' library and/or its dependencies unavailable.') except nacl.exceptions.BadSignatureError: pass # Verify 'ed25519' signature with the pure Python implementation. else: try: securesystemslib._vendor.ed25519.ed25519.checkvalid(signature, data, public) valid_signature = True # The pure Python implementation raises 'Exception' if 'signature' is # invalid. except Exception as e: 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 securesystemslib.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() securesystemslib-0.11.3/setup.py0000755000076600000240000000615213353240552016312 0ustar sstaff00000000000000#! /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 TUF 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 TUF 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.11.3', description = 'A library that provides cryptographic and general-purpose' ' routines for Secure Systems Lab projects at NYU', long_description = long_description, 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.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Security', 'Topic :: Software Development' ], install_requires = ['six>=1.11.0'], extras_require = {'crypto': ['cryptography>=2.2.2', 'colorama>=0.3.9'], 'pynacl': ['pynacl>1.2.0']}, packages = find_packages(exclude=['tests', 'debian']), scripts = [] ) securesystemslib-0.11.3/setup.cfg0000644000076600000240000000004613353243474016420 0ustar sstaff00000000000000[egg_info] tag_build = tag_date = 0 securesystemslib-0.11.3/README.rst0000644000076600000240000002307613342052762016272 0ustar sstaff00000000000000Secure Systems Library ---------------------- .. image:: https://travis-ci.org/secure-systems-lab/securesystemslib.svg?branch=master :target: https://travis-ci.org/secure-systems-lab/securesystemslib .. image:: https://coveralls.io/repos/github/secure-systems-lab/securesystemslib/badge.svg?branch=master :target: https://coveralls.io/github/secure-systems-lab/securesystemslib?branch=master .. image:: https://pyup.io/repos/github/secure-systems-lab/securesystemslib/shield.svg :target: https://pyup.io/repos/github/secure-systems-lab/securesystemslib/ :alt: Updates 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. 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] 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("rsa_key1", bits=2048, password="password") # If the key length is unspecified, it defaults to 3072 bits. A length of # less than 2048 bits raises an exception. A password may be supplied as an # argument, otherwise a user prompt is presented. If the password is an # empty string, the private key is saved unencrypted. >>> generate_and_write_rsa_keypair("rsa_key2") Enter a password for the RSA key: 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 a password for the encrypted RSA key: **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 . . . # Generate and write an Ed25519 key pair. The private key is saved # encrypted. A 'password' argument may be supplied, otherwise a prompt is # presented. >>> generate_and_write_ed25519_keypair('ed25519_key') Enter a password for the Ed25519 key: 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') Enter a password for the encrypted Ed25519 key: Create and Import ECDSA Keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: # continuing from the previous sections . . . >>> generate_and_write_ecdsa_keypair('ecdsa_key') Enter a password for the ECDSA key: Confirm: >>> public_ecdsa_key = import_ecdsa_publickey_from_file('ecdsa_key.pub') >>> private_ecdsa_key = import_ecdsa_privatekey_from_file('ecdsa_key') Enter a password for the encrypted ECDSA key: Generate ECDSA, Ed25519, and RSA Signatures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note: Users may also access the crypto functions directly to perform cryptographic operations. :: >>> from securesystemslib.keys import * >>> data = '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 = '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)