mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
Initial commit.
This commit is contained in:
commit
0fb43a5e36
106 changed files with 23846 additions and 0 deletions
30
src/MANIFEST
Executable file
30
src/MANIFEST
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
quickstart.py
|
||||
setup.py
|
||||
simplejson/__init__.py
|
||||
simplejson/decoder.py
|
||||
simplejson/encoder.py
|
||||
simplejson/ordered_dict.py
|
||||
simplejson/scanner.py
|
||||
simplejson/tool.py
|
||||
tuf/__init__.py
|
||||
tuf/checkjson.py
|
||||
tuf/conf.py
|
||||
tuf/download.py
|
||||
tuf/formats.py
|
||||
tuf/hash.py
|
||||
tuf/keydb.py
|
||||
tuf/keys.py
|
||||
tuf/log.py
|
||||
tuf/mirrors.py
|
||||
tuf/sig.py
|
||||
tuf/util.py
|
||||
tuf/client/__init__.py
|
||||
tuf/client/updater.py
|
||||
tuf/pushtools/push.py
|
||||
tuf/pushtools/receivetools/receive.py
|
||||
tuf/pushtools/transfer/__init__.py
|
||||
tuf/pushtools/transfer/scp.py
|
||||
tuf/repo/__init__.py
|
||||
tuf/repo/keystore.py
|
||||
tuf/repo/signercli.py
|
||||
tuf/repo/signerlib.py
|
||||
0
src/evpy/__init__.py
Executable file
0
src/evpy/__init__.py
Executable file
200
src/evpy/cipher.py
Executable file
200
src/evpy/cipher.py
Executable file
|
|
@ -0,0 +1,200 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""
|
||||
cipher.py
|
||||
|
||||
Written by Geremy Condra
|
||||
Released on 18 March 2010
|
||||
Licensed under MIT License
|
||||
|
||||
This module provides a basic interface to OpenSSL's EVP
|
||||
cipher functions.
|
||||
|
||||
All the functions in this module raise CipherError on
|
||||
malfunction.
|
||||
|
||||
From an end-user perspective, this module should be used
|
||||
in situations where you want to have a single generally
|
||||
human-readable or human-generated key used for both
|
||||
encryption and decryption.
|
||||
|
||||
This means that as a general rule, if your application
|
||||
involves transmitting this key over an insecure channel
|
||||
you should not be using this module, but rather
|
||||
evpy.envelope.
|
||||
|
||||
Usage:
|
||||
|
||||
>>> from evpy import cipher
|
||||
>>> message = b"this is data"
|
||||
>>> pw = b"mypassword"
|
||||
>>> salt, iv, enc = cipher.encrypt(message, pw)
|
||||
>>> cipher.decrypt(salt, iv, enc, pw)
|
||||
'this is data'
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import time
|
||||
|
||||
import evp
|
||||
|
||||
|
||||
class CipherError(evp.SSLError):
|
||||
pass
|
||||
|
||||
def _strengthen_password(pw, iv, salt=None):
|
||||
# add the hash
|
||||
evp.OpenSSL_add_all_digests()
|
||||
# build the key buffer
|
||||
key = ctypes.create_string_buffer(24)
|
||||
# either take the existing salt or build a new one
|
||||
if not salt:
|
||||
salt = ctypes.create_string_buffer(8)
|
||||
# get the needed entropy, bailing if it doesn't work in
|
||||
# the first thousand tries
|
||||
for i in range(1000):
|
||||
if evp.RAND_bytes(salt, 8): break
|
||||
else:
|
||||
raise CipherError("Could not generate enough entropy")
|
||||
# extract the salt
|
||||
salt = salt.raw
|
||||
# get the hash
|
||||
evp_hash = evp.EVP_get_digestbyname("sha512")
|
||||
if not evp_hash:
|
||||
raise CipherError("Could not create hash object")
|
||||
# fill the key
|
||||
if not evp.EVP_BytesToKey(evp.EVP_aes_192_cbc(), evp_hash, salt, pw, len(pw), 1000, key, iv):
|
||||
raise CipherError("Could not strengthen key")
|
||||
# go home
|
||||
return salt, key.raw
|
||||
|
||||
|
||||
def encrypt(data, password):
|
||||
"""Encrypts the given data, raising CipherError on failure.
|
||||
|
||||
This uses AES192 to encrypt and strengthens the given
|
||||
passphrase using SHA512.
|
||||
|
||||
Usage:
|
||||
>>> from evpy import cipher
|
||||
>>> f = open("test/short.txt", "rb")
|
||||
>>> data = f.read()
|
||||
>>> pw = b"mypassword"
|
||||
>>> salt, iv, enc = cipher.encrypt(data, pw)
|
||||
>>> cipher.decrypt(salt, iv, enc, pw) == data
|
||||
True
|
||||
"""
|
||||
# ensure data exists
|
||||
if not len(data):
|
||||
raise CipherError("Data must actually exist")
|
||||
if not len(password):
|
||||
raise CipherError("Password must actually exist")
|
||||
|
||||
# build and initialize the context
|
||||
ctx = evp.EVP_CIPHER_CTX_new()
|
||||
if not ctx:
|
||||
raise CipherError("Could not create context")
|
||||
evp.EVP_CIPHER_CTX_init(ctx)
|
||||
|
||||
# get the cipher object
|
||||
cipher_object = evp.EVP_aes_192_cbc()
|
||||
if not cipher_object:
|
||||
raise CipherError("Could not create cipher object")
|
||||
|
||||
# finish the context and cipher object
|
||||
if not evp.EVP_EncryptInit_ex(ctx, cipher_object, None, None, None):
|
||||
raise CipherError("Could not finish context")
|
||||
|
||||
# build the randomized iv
|
||||
iv_length = evp.EVP_CIPHER_CTX_iv_length(ctx)
|
||||
iv = ctypes.create_string_buffer(iv_length)
|
||||
# get the needed entropy, bailing if it doesn't work in
|
||||
# the first thousand tries
|
||||
for i in range(1000):
|
||||
if evp.RAND_bytes(iv, iv_length): break
|
||||
else:
|
||||
raise CipherError("Not enough entropy for IV")
|
||||
output_iv = iv.raw
|
||||
|
||||
# strengthen the password into an honest-to-goodness key
|
||||
salt, aes_key = _strengthen_password(password, iv)
|
||||
|
||||
# initialize the encryption operation
|
||||
if not evp.EVP_EncryptInit_ex(ctx, None, None, aes_key, iv):
|
||||
raise CipherError("Could not start encryption operation")
|
||||
|
||||
# build the output buffer
|
||||
buf = ctypes.create_string_buffer(len(data) + 16)
|
||||
written = ctypes.c_int(0)
|
||||
final = ctypes.c_int(0)
|
||||
|
||||
# update
|
||||
if not evp.EVP_EncryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)):
|
||||
raise CipherError("Could not update ciphertext")
|
||||
output = buf.raw[:written.value]
|
||||
|
||||
# finalize
|
||||
if not evp.EVP_EncryptFinal_ex(ctx, buf, ctypes.byref(final)):
|
||||
raise CipherError("Could not finalize ciphertext")
|
||||
output += buf.raw[:final.value]
|
||||
|
||||
# ...and go home
|
||||
return salt, output_iv, output
|
||||
|
||||
|
||||
def decrypt(salt, iv, data, password):
|
||||
"""Decrypts the given data, raising CipherError on failure.
|
||||
|
||||
Usage:
|
||||
>>> from evpy import cipher
|
||||
>>> f = open("test/short.txt", "rb")
|
||||
>>> data = f.read()
|
||||
>>> pw = b"mypassword"
|
||||
>>> salt, iv, enc = cipher.encrypt(data, pw)
|
||||
>>> cipher.decrypt(salt, iv, enc, pw) == data
|
||||
True
|
||||
"""
|
||||
# ensure inputs are the correct size
|
||||
if not len(data):
|
||||
raise CipherError("Data must actually exist")
|
||||
if not len(password):
|
||||
raise CipherError("Password must actually exist")
|
||||
if len(salt) != 8:
|
||||
raise CipherError("Incorrect salt size")
|
||||
if len(iv) != 16:
|
||||
raise CipherError("Incorrect iv size")
|
||||
|
||||
# build and initialize the context
|
||||
ctx = evp.EVP_CIPHER_CTX_new()
|
||||
if not ctx:
|
||||
raise CipherError("Could not create context")
|
||||
evp.EVP_CIPHER_CTX_init(ctx)
|
||||
|
||||
# get the cipher object
|
||||
cipher_object = evp.EVP_aes_192_cbc()
|
||||
if not cipher_object:
|
||||
raise CipherError("Could not create cipher object")
|
||||
|
||||
# build the key
|
||||
salt, key = _strengthen_password(password, iv, salt)
|
||||
|
||||
# start decrypting the ciphertext
|
||||
if not evp.EVP_DecryptInit_ex(ctx, cipher_object, None, key, iv):
|
||||
raise CipherError("Could not open envelope")
|
||||
|
||||
# build the output buffers
|
||||
buf = ctypes.create_string_buffer(len(data) + 16)
|
||||
written = ctypes.c_int(0)
|
||||
final = ctypes.c_int(0)
|
||||
|
||||
# update
|
||||
if not evp.EVP_DecryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)):
|
||||
raise CipherError("Could not update plaintext")
|
||||
output = buf.raw[:written.value]
|
||||
|
||||
# finalize
|
||||
if not evp.EVP_DecryptFinal_ex(ctx, buf, ctypes.byref(final)):
|
||||
raise CipherError("Could not finalize decryption")
|
||||
output += buf.raw[:final.value]
|
||||
|
||||
return output
|
||||
326
src/evpy/envelope.py
Executable file
326
src/evpy/envelope.py
Executable file
|
|
@ -0,0 +1,326 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""
|
||||
envelope.py
|
||||
|
||||
Written by Geremy Condra
|
||||
Released on 18 March 2010
|
||||
Licensed under MIT License
|
||||
|
||||
This module provides a basic interface to OpenSSL's EVP
|
||||
envelope functions.
|
||||
|
||||
In a nutshell, these functions are designed to provide
|
||||
the primary benefit of public key cryptography (the
|
||||
ability to provide secrecy without first sharing a
|
||||
secret) without its primary downside (small message
|
||||
length). It does this by generating a random AES key
|
||||
with which to encrypt the data, then encrypting that
|
||||
key against the provided RSA key.
|
||||
|
||||
This means that if you have an application in which
|
||||
you wish to share sensitive data but do not wish to
|
||||
share a common secret, this is your module. Be aware
|
||||
that compromising your private key is effectively
|
||||
game over with this scheme.
|
||||
|
||||
If you require a shared secret and want the key to
|
||||
be human readable, then you will probably want to
|
||||
use the cipher module instead.
|
||||
|
||||
All the functions in this module raise EnvelopeError on
|
||||
malfunction.
|
||||
|
||||
Usage:
|
||||
|
||||
>>> from evpy import envelope
|
||||
>>> f = open("test/short.txt", "rb")
|
||||
>>> data = f.read()
|
||||
>>> public_key = "test/keys/public1.pem"
|
||||
>>> private_key = "test/keys/private1.pem"
|
||||
>>> iv, key, ciphertext = envelope.encrypt(data, public_key)
|
||||
>>> envelope.decrypt(iv, key, ciphertext, private_key) == data
|
||||
True
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
|
||||
import evp
|
||||
from signature import _string_to_bio
|
||||
|
||||
class EnvelopeError(evp.SSLError):
|
||||
pass
|
||||
|
||||
def _build_dkey_from_file(keyfile):
|
||||
fp = evp.fopen(keyfile, "r")
|
||||
if not fp:
|
||||
raise EnvelopeError("Could not open keyfile")
|
||||
# get the decryption key
|
||||
skey = evp.PEM_read_PrivateKey(fp, None, None, None)
|
||||
if not skey:
|
||||
evp.fclose(fp)
|
||||
raise EnvelopeError("Could not read decryption key")
|
||||
# close the file
|
||||
evp.fclose(fp)
|
||||
return skey
|
||||
|
||||
def _build_dkey_from_string(key):
|
||||
bio = _string_to_bio(key)
|
||||
dkey = evp.PEM_read_bio_PrivateKey(bio, None, None, None)
|
||||
if not dkey:
|
||||
raise EnvelopeError("Could not build decryption key from string")
|
||||
evp.BIO_free(bio)
|
||||
return dkey
|
||||
|
||||
def _build_ekey_from_file(keyfile):
|
||||
fp = evp.fopen(keyfile, "r")
|
||||
if not fp:
|
||||
raise EnvelopeError("Could not open keyfile")
|
||||
# get the encryption key
|
||||
ekey = evp.PEM_read_PUBKEY(fp, None, None, None)
|
||||
if not ekey:
|
||||
evp.fclose(fp)
|
||||
raise EnvelopeError("Could not read encryption key")
|
||||
# close the file
|
||||
evp.fclose(fp)
|
||||
return ekey
|
||||
|
||||
def _build_ekey_from_string(key):
|
||||
bio = _string_to_bio(key)
|
||||
ekey = evp.PEM_read_bio_PUBKEY(bio, None, None, None)
|
||||
if not ekey:
|
||||
raise EnvelopeError("Could not create encryption key from string")
|
||||
evp.BIO_free(bio)
|
||||
return ekey
|
||||
|
||||
def _build_bio():
|
||||
method = evp.BIO_s_mem()
|
||||
return evp.BIO_new(method);
|
||||
|
||||
def _asn1_hex_to_int(value):
|
||||
print(value)
|
||||
return int(''.join(value.split(':')), 16)
|
||||
|
||||
def _parse_printed_key(k):
|
||||
attrs = {}
|
||||
current = ""
|
||||
current_attr = ""
|
||||
for line in k.splitlines()[1:]:
|
||||
# its a continuation of the current block
|
||||
if line.startswith(' '):
|
||||
current += line.strip()
|
||||
else:
|
||||
# special case the public exponent
|
||||
if "publicExponent" in current_attr:
|
||||
attrs['publicExponent'] = int(current_attr.split()[1])
|
||||
elif current_attr:
|
||||
attrs[current_attr] = _asn1_hex_to_int(current)
|
||||
current_attr = line.strip(':')
|
||||
current = ""
|
||||
translator = {'publicExponent': 'e', 'privateExponent': 'd', 'modulus': 'n', 'prime1': 'p', 'prime2': 'q'}
|
||||
translated_attrs = {}
|
||||
for key, value in attrs.items():
|
||||
try:
|
||||
translated_attrs[translator[key]] = value
|
||||
except: pass
|
||||
return translated_attrs
|
||||
|
||||
|
||||
def keygen(bitlength=1024, e=65537, pem=True):
|
||||
key = evp.RSA_generate_key(bitlength, e, None, None)
|
||||
if not key:
|
||||
raise EnvelopeError("Could not generate key")
|
||||
if pem:
|
||||
private_bio = evp.BIO_new(evp.BIO_s_mem())
|
||||
if not private_bio:
|
||||
raise KeygenError("Could not create temporary storage")
|
||||
public_bio = evp.BIO_new(evp.BIO_s_mem())
|
||||
if not public_bio:
|
||||
raise KeygenError("Could not create temporary storage")
|
||||
private_buf = ctypes.create_string_buffer('', 65537)
|
||||
if not private_buf:
|
||||
raise MemoryError("Could not allocate key storage")
|
||||
public_buf = ctypes.create_string_buffer('', 65537)
|
||||
if not public_buf:
|
||||
raise MemoryError("Could not allocate key storage")
|
||||
if not evp.PEM_write_bio_RSAPrivateKey(private_bio, key, None, None, 0, 0, None):
|
||||
raise KeygenError("Could not write private key")
|
||||
if not evp.PEM_write_bio_RSA_PUBKEY(public_bio, key):
|
||||
raise KeygenError("Could not write public key")
|
||||
public_len = evp.BIO_read(public_bio, public_buf, 65537)
|
||||
private_len = evp.BIO_read(private_bio, private_buf, 65537)
|
||||
evp.BIO_free(public_bio)
|
||||
evp.BIO_free(private_bio)
|
||||
return public_buf.value, private_buf.value
|
||||
else:
|
||||
# we go through this rigamarole because if there's an engine
|
||||
# in place it won't populate the RSA key's values properly.
|
||||
key_bio = evp.BIO_new(evp.BIO_s_mem())
|
||||
if not key_bio:
|
||||
raise KeygenError("Could not create temporary storage")
|
||||
if not evp.RSA_print(key_bio, key, 0):
|
||||
raise KeygenError("Could not stringify key")
|
||||
key_buf = ctypes.create_string_buffer('', 65537)
|
||||
if not key_buf:
|
||||
raise MemoryError("Could not allocate key storage")
|
||||
evp.BIO_read(key_bio, key_buf, 65537)
|
||||
evp.BIO_free(key_bio)
|
||||
key_string = key_buf.value
|
||||
return key, _parse_printed_key(key_string)
|
||||
|
||||
|
||||
|
||||
|
||||
def encrypt(data, keyfile=None, key=None):
|
||||
"""Encrypts the given data, raising EnvelopeError on failure.
|
||||
|
||||
This uses AES192 to do bulk encryption and RSA to encrypt
|
||||
the given public key.
|
||||
|
||||
Usage:
|
||||
>>> from evpy import envelope
|
||||
>>> f = open("test/short.txt", "rb")
|
||||
>>> data = f.read()
|
||||
>>> public_key = "test/keys/public1.pem"
|
||||
>>> private_key = "test/keys/private1.pem"
|
||||
>>> iv, key, ciphertext = envelope.encrypt(data, public_key)
|
||||
>>> envelope.decrypt(iv, key, ciphertext, private_key) == data
|
||||
True
|
||||
"""
|
||||
# validate the incoming data
|
||||
if not data:
|
||||
raise EnvelopeError("Incoming data must be bytes")
|
||||
if not len(data):
|
||||
raise EnvelopeError("Data must actually exist")
|
||||
|
||||
# build and initialize the context
|
||||
ctx = evp.EVP_CIPHER_CTX_new()
|
||||
if not ctx:
|
||||
raise EnvelopeError("Could not create context")
|
||||
evp.EVP_CIPHER_CTX_init(ctx)
|
||||
|
||||
# get the key from the keyfile
|
||||
if key and not keyfile:
|
||||
ekey = _build_ekey_from_string(key)
|
||||
elif keyfile and not key:
|
||||
ekey = _build_ekey_from_file(keyfile)
|
||||
else:
|
||||
raise EnvelopeError("Must specify exactly one key or keyfile")
|
||||
|
||||
# get the cipher object
|
||||
cipher_object = evp.EVP_aes_192_cbc()
|
||||
if not cipher_object:
|
||||
raise EnvelopeError("Could not create cipher object")
|
||||
|
||||
# finish the context and cipher object
|
||||
if not evp.EVP_EncryptInit_ex(ctx, cipher_object, None, None, None):
|
||||
raise EnvelopeError("Could not finish context")
|
||||
|
||||
# build the randomized iv
|
||||
iv_length = evp.EVP_CIPHER_CTX_iv_length(ctx)
|
||||
iv = ctypes.create_string_buffer(iv_length)
|
||||
for i in range(1000):
|
||||
if evp.RAND_bytes(iv, iv_length): break
|
||||
else:
|
||||
raise EnvelopeError("Could not generate enough entropy for IV")
|
||||
output_iv = iv.raw
|
||||
|
||||
# build the randomized AES key
|
||||
keysize = evp.EVP_CIPHER_key_length(cipher_object)
|
||||
aes_key = ctypes.create_string_buffer(keysize)
|
||||
for i in range(1000):
|
||||
if evp.RAND_bytes(aes_key, keysize): break
|
||||
else:
|
||||
raise EnvelopeError("Could not generate enough entropy for AES key")
|
||||
|
||||
# extract the RSA key
|
||||
rsa_key = evp.EVP_PKEY_get1_RSA(ekey)
|
||||
if not rsa_key:
|
||||
raise EnvelopeError("Could not get RSA key")
|
||||
|
||||
# encrypt it
|
||||
buf_size = evp.RSA_size(rsa_key)
|
||||
if not buf_size:
|
||||
raise EnvelopeError("Invalid RSA keysize")
|
||||
encrypted_aes_key = ctypes.create_string_buffer(buf_size)
|
||||
# RSA_PKCS1_PADDING is defined as 1
|
||||
written = evp.RSA_public_encrypt(keysize, aes_key, encrypted_aes_key, rsa_key, 1)
|
||||
if not written:
|
||||
raise EnvelopeError("Could not encrypt AES key")
|
||||
output_key = encrypted_aes_key.raw[:written]
|
||||
|
||||
# initialize the encryption operation
|
||||
if not evp.EVP_EncryptInit_ex(ctx, None, None, aes_key, iv):
|
||||
raise EnvelopeError("Could not start encryption operation")
|
||||
|
||||
# build the output buffer
|
||||
buf = ctypes.create_string_buffer(len(data) + 16)
|
||||
written = ctypes.c_int(0)
|
||||
final = ctypes.c_int(0)
|
||||
|
||||
# update
|
||||
if not evp.EVP_EncryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)):
|
||||
raise EnvelopeError("Could not update ciphertext")
|
||||
output = buf.raw[:written.value]
|
||||
|
||||
# finalize
|
||||
if not evp.EVP_EncryptFinal_ex(ctx, buf, ctypes.byref(final)):
|
||||
raise EnvelopeError("Could not finalize ciphertext")
|
||||
output += buf.raw[:final.value]
|
||||
|
||||
# ...and go home
|
||||
return output_iv, output_key, output
|
||||
|
||||
|
||||
def decrypt(iv, encrypted_aes_key, data, keyfile=None, key=None):
|
||||
"""Decrypts the given ciphertext, raising EnvelopeError on failure.
|
||||
|
||||
Usage:
|
||||
>>> from evpy import envelope
|
||||
>>> f = open("test/short.txt", "rb")
|
||||
>>> data = f.read()
|
||||
>>> public_key = "test/keys/public1.pem"
|
||||
>>> private_key = "test/keys/private1.pem"
|
||||
>>> iv, key, ciphertext = envelope.encrypt(data, public_key)
|
||||
>>> envelope.decrypt(iv, key, ciphertext, private_key) == data
|
||||
True
|
||||
"""
|
||||
# build and initialize the context
|
||||
ctx = evp.EVP_CIPHER_CTX_new()
|
||||
if not ctx:
|
||||
raise EnvelopeError("Could not create context")
|
||||
evp.EVP_CIPHER_CTX_init(ctx)
|
||||
|
||||
# get the cipher object
|
||||
cipher_object = evp.EVP_aes_192_cbc()
|
||||
if not cipher_object:
|
||||
raise EnvelopeError("Could not create cipher object")
|
||||
|
||||
# get the key from the keyfile
|
||||
if key and not keyfile:
|
||||
dkey = _build_dkey_from_string(key)
|
||||
elif keyfile and not key:
|
||||
dkey = _build_dkey_from_file(keyfile)
|
||||
else:
|
||||
raise EnvelopeError("Must specify exactly one key or keyfile")
|
||||
|
||||
# open the envelope
|
||||
if not evp.EVP_OpenInit(ctx, cipher_object, encrypted_aes_key, len(encrypted_aes_key), iv, dkey):
|
||||
raise EnvelopeError("Could not open envelope")
|
||||
|
||||
# build the output buffer
|
||||
buf = ctypes.create_string_buffer(len(data) + 16)
|
||||
written = ctypes.c_int(0)
|
||||
final = ctypes.c_int(0)
|
||||
|
||||
# update
|
||||
if not evp.EVP_DecryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)):
|
||||
raise EnvelopeError("Could not update envelope")
|
||||
output = buf.raw[:written.value]
|
||||
|
||||
# finalize
|
||||
if not evp.EVP_DecryptFinal_ex(ctx, buf, ctypes.byref(final)):
|
||||
raise EnvelopeError("Could not finalize envelope")
|
||||
output += buf.raw[:final.value]
|
||||
|
||||
return output
|
||||
318
src/evpy/evp.py
Executable file
318
src/evpy/evp.py
Executable file
|
|
@ -0,0 +1,318 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import platform
|
||||
from os import linesep
|
||||
|
||||
def handle_errors():
|
||||
ERR_load_crypto_strings()
|
||||
errno = ERR_get_error()
|
||||
errbuf = ctypes.create_string_buffer(1024)
|
||||
ERR_error_string_n(errno, errbuf, 1024)
|
||||
return errbuf.value.decode("ascii")
|
||||
|
||||
class SSLError(Exception):
|
||||
def __init__(self, msg):
|
||||
sslerr = handle_errors()
|
||||
Exception.__init__(self, msg + linesep + sslerr)
|
||||
|
||||
libraries = {}
|
||||
|
||||
libraries["c"] = ctypes.CDLL(ctypes.util.find_library("c"))
|
||||
|
||||
if platform.system() != "Windows":
|
||||
libraries["ssl"] = ctypes.CDLL(ctypes.util.find_library("ssl"))
|
||||
else:
|
||||
libraries["ssl"] = ctypes.CDLL(ctypes.util.find_library("libeay32"))
|
||||
|
||||
|
||||
class RSA(ctypes.Structure):
|
||||
_fields_ = [ ("n", ctypes.c_void_p),
|
||||
("e", ctypes.c_void_p),
|
||||
("d", ctypes.c_void_p),
|
||||
("p", ctypes.c_void_p),
|
||||
("q", ctypes.c_void_p),
|
||||
("dmp1", ctypes.c_void_p),
|
||||
("iqmp", ctypes.c_void_p)]
|
||||
|
||||
def handle_errors():
|
||||
ERR_load_crypto_strings()
|
||||
errno = ERR_get_error()
|
||||
errbuf = ctypes.create_string_buffer(1024)
|
||||
ERR_error_string_n(errno, errbuf, 1024)
|
||||
return errbuf.value.decode("ascii")
|
||||
|
||||
|
||||
fopen = libraries['c'].fopen
|
||||
fopen.restype = ctypes.c_void_p
|
||||
fopen.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
|
||||
|
||||
|
||||
fclose = libraries['c'].fclose
|
||||
fclose.restype = ctypes.c_int
|
||||
fclose.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
ERR_load_crypto_strings = libraries['ssl'].ERR_load_crypto_strings
|
||||
ERR_load_crypto_strings.restype = ctypes.c_int
|
||||
ERR_load_crypto_strings.argtypes = []
|
||||
|
||||
|
||||
ERR_print_errors_fp = libraries['ssl'].ERR_print_errors_fp
|
||||
ERR_print_errors_fp.restype = ctypes.c_int
|
||||
ERR_print_errors_fp.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
OpenSSL_add_all_digests = libraries['ssl'].OpenSSL_add_all_digests
|
||||
OpenSSL_add_all_digests.restype = ctypes.c_int
|
||||
OpenSSL_add_all_digests.argtypes = []
|
||||
|
||||
|
||||
EVP_MD_CTX_create = libraries['ssl'].EVP_MD_CTX_create
|
||||
EVP_MD_CTX_create.restype = ctypes.c_void_p
|
||||
EVP_MD_CTX_create.argtypes = []
|
||||
|
||||
|
||||
PEM_read_PrivateKey = libraries['ssl'].PEM_read_PrivateKey
|
||||
PEM_read_PrivateKey.restype = ctypes.c_void_p
|
||||
PEM_read_PrivateKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
|
||||
PEM_read_X509 = libraries['ssl'].PEM_read_X509
|
||||
PEM_read_X509.restype = ctypes.c_void_p
|
||||
PEM_read_X509.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
|
||||
PEM_read_PUBKEY = libraries['ssl'].PEM_read_PUBKEY
|
||||
PEM_read_PUBKEY.restype = ctypes.c_void_p
|
||||
PEM_read_PUBKEY.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
|
||||
PEM_read_bio_PUBKEY = libraries['ssl'].PEM_read_bio_PUBKEY
|
||||
PEM_read_bio_PUBKEY.restype = ctypes.c_void_p
|
||||
PEM_read_bio_PUBKEY.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
|
||||
BIO_free = libraries['ssl'].BIO_free
|
||||
BIO_free.restype = ctypes.c_int
|
||||
BIO_free.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
PEM_read_bio_PrivateKey = libraries['ssl'].PEM_read_bio_PrivateKey
|
||||
PEM_read_bio_PrivateKey.restype = ctypes.c_void_p
|
||||
PEM_read_bio_PrivateKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
|
||||
EVP_get_digestbyname = libraries['ssl'].EVP_get_digestbyname
|
||||
EVP_get_digestbyname.restype = ctypes.c_void_p
|
||||
EVP_get_digestbyname.argtypes = [ctypes.c_char_p]
|
||||
|
||||
|
||||
EVP_DigestInit = libraries['ssl'].EVP_DigestInit
|
||||
EVP_DigestInit.restype = ctypes.c_int
|
||||
EVP_DigestInit.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
|
||||
EVP_DigestUpdate = libraries['ssl'].EVP_DigestUpdate
|
||||
EVP_DigestUpdate.restype = ctypes.c_int
|
||||
EVP_DigestUpdate.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int]
|
||||
|
||||
|
||||
EVP_SignFinal = libraries['ssl'].EVP_SignFinal
|
||||
EVP_SignFinal.restype = ctypes.c_int
|
||||
EVP_SignFinal.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int), ctypes.c_void_p]
|
||||
|
||||
|
||||
EVP_VerifyFinal = libraries['ssl'].EVP_VerifyFinal
|
||||
EVP_VerifyFinal.restype = ctypes.c_int
|
||||
EVP_VerifyFinal.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p]
|
||||
|
||||
|
||||
EVP_PKEY_free = libraries['ssl'].EVP_PKEY_free
|
||||
EVP_PKEY_free.restype = ctypes.c_int
|
||||
EVP_PKEY_free.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
EVP_MD_CTX_cleanup = libraries['ssl'].EVP_MD_CTX_cleanup
|
||||
EVP_MD_CTX_cleanup.restype = ctypes.c_int
|
||||
EVP_MD_CTX_cleanup.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
EVP_MD_CTX_destroy = libraries['ssl'].EVP_MD_CTX_destroy
|
||||
EVP_MD_CTX_destroy.restype = ctypes.c_int
|
||||
EVP_MD_CTX_destroy.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
EVP_CIPHER_CTX_new = libraries['ssl'].EVP_CIPHER_CTX_new
|
||||
EVP_CIPHER_CTX_new.restype = ctypes.c_void_p
|
||||
EVP_CIPHER_CTX_new.argtypes = []
|
||||
|
||||
|
||||
EVP_CIPHER_CTX_init = libraries['ssl'].EVP_CIPHER_CTX_init
|
||||
EVP_CIPHER_CTX_init.restype = ctypes.c_int
|
||||
EVP_CIPHER_CTX_init.argtypes = [ctypes.c_void_p]
|
||||
|
||||
try:
|
||||
EVP_CIPHER_CTX_iv_length = libraries['ssl'].EVP_CIPHER_CTX_iv_length
|
||||
EVP_CIPHER_CTX_iv_length.restype = ctypes.c_int
|
||||
EVP_CIPHER_CTX_iv_length.argtypes = [ctypes.c_void_p]
|
||||
except:
|
||||
EVP_CIPHER_CTX_iv_length = lambda(x): 16
|
||||
|
||||
|
||||
EVP_aes_192_cbc = libraries['ssl'].EVP_aes_192_cbc
|
||||
EVP_aes_192_cbc.restype = ctypes.c_void_p
|
||||
EVP_aes_192_cbc.argtypes = []
|
||||
|
||||
|
||||
EVP_PKEY_size = libraries['ssl'].EVP_PKEY_size
|
||||
EVP_PKEY_size.restype = ctypes.c_int
|
||||
EVP_PKEY_size.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
EVP_SealInit = libraries['ssl'].EVP_SealInit
|
||||
EVP_SealInit.restype = ctypes.c_int
|
||||
EVP_SealInit.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int), ctypes.c_char_p, ctypes.c_void_p, ctypes.c_int]
|
||||
|
||||
|
||||
EVP_EncryptInit_ex = libraries['ssl'].EVP_EncryptInit_ex
|
||||
EVP_EncryptInit_ex.restype = ctypes.c_int
|
||||
EVP_EncryptInit_ex.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
|
||||
|
||||
|
||||
EVP_EncryptUpdate = libraries['ssl'].EVP_EncryptUpdate
|
||||
EVP_EncryptUpdate.restype = ctypes.c_int
|
||||
EVP_EncryptUpdate.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int), ctypes.c_char_p, ctypes.c_int]
|
||||
|
||||
|
||||
EVP_DecryptUpdate = libraries['ssl'].EVP_DecryptUpdate
|
||||
EVP_DecryptUpdate.restype = ctypes.c_int
|
||||
EVP_DecryptUpdate.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int), ctypes.c_char_p, ctypes.c_int]
|
||||
|
||||
|
||||
EVP_EncryptFinal_ex = libraries['ssl'].EVP_EncryptFinal_ex
|
||||
EVP_EncryptFinal_ex.restype = ctypes.c_int
|
||||
EVP_EncryptFinal_ex.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int)]
|
||||
|
||||
|
||||
EVP_SealFinal = libraries['ssl'].EVP_SealFinal
|
||||
EVP_SealFinal.restype = ctypes.c_int
|
||||
EVP_SealFinal.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int)]
|
||||
|
||||
|
||||
EVP_DecryptFinal_ex = libraries['ssl'].EVP_DecryptFinal_ex
|
||||
EVP_DecryptFinal_ex.restype = ctypes.c_int
|
||||
EVP_DecryptFinal_ex.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int)]
|
||||
|
||||
|
||||
RAND_bytes = libraries['ssl'].RAND_bytes
|
||||
RAND_bytes.restype = ctypes.c_int
|
||||
RAND_bytes.argtypes = [ctypes.c_char_p, ctypes.c_int]
|
||||
|
||||
|
||||
BIO_new_mem_buf = libraries['ssl'].BIO_new_mem_buf
|
||||
BIO_new_mem_buf.restype = ctypes.c_void_p
|
||||
BIO_new_mem_buf.argtypes = [ctypes.c_char_p, ctypes.c_int]
|
||||
|
||||
|
||||
EVP_CIPHER_CTX_rand_key = libraries['ssl'].EVP_CIPHER_CTX_rand_key
|
||||
EVP_CIPHER_CTX_rand_key.restype = ctypes.c_int
|
||||
EVP_CIPHER_CTX_rand_key.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
|
||||
|
||||
|
||||
try:
|
||||
EVP_CIPHER_key_length = libraries['ssl'].EVP_CIPHER_key_length
|
||||
EVP_CIPHER_key_length.restype = ctypes.c_int
|
||||
EVP_CIPHER_key_length.argtypes = [ctypes.c_void_p]
|
||||
except:
|
||||
EVP_CIPHER_key_length = lambda(x): 24
|
||||
|
||||
|
||||
EVP_PKEY_encrypt = libraries['ssl'].EVP_PKEY_encrypt
|
||||
EVP_PKEY_encrypt.restype = ctypes.c_int
|
||||
EVP_PKEY_encrypt.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p]
|
||||
|
||||
|
||||
EVP_OpenInit = libraries['ssl'].EVP_OpenInit
|
||||
EVP_OpenInit.restype = ctypes.c_int
|
||||
EVP_OpenInit.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p]
|
||||
|
||||
|
||||
EVP_PKEY_size = libraries['ssl'].EVP_PKEY_size
|
||||
EVP_PKEY_size.restype = ctypes.c_int
|
||||
EVP_PKEY_size.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
RSA_public_encrypt = libraries['ssl'].RSA_public_encrypt
|
||||
RSA_public_encrypt.restype = ctypes.c_int
|
||||
RSA_public_encrypt.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_int]
|
||||
|
||||
|
||||
EVP_PKEY_get1_RSA = libraries['ssl'].EVP_PKEY_get1_RSA
|
||||
EVP_PKEY_get1_RSA.restype = ctypes.c_void_p
|
||||
EVP_PKEY_get1_RSA.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
EVP_DecryptInit_ex = libraries['ssl'].EVP_DecryptInit_ex
|
||||
EVP_DecryptInit_ex.restype = ctypes.c_int
|
||||
EVP_DecryptInit_ex.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
|
||||
|
||||
|
||||
EVP_CIPHER_CTX_set_key_length = libraries['ssl'].EVP_CIPHER_CTX_set_key_length
|
||||
EVP_CIPHER_CTX_set_key_length.restype = ctypes.c_int
|
||||
EVP_CIPHER_CTX_set_key_length.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
||||
|
||||
|
||||
EVP_BytesToKey = libraries['ssl'].EVP_BytesToKey
|
||||
EVP_BytesToKey.restype = ctypes.c_int
|
||||
EVP_BytesToKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p]
|
||||
|
||||
|
||||
ERR_get_error = libraries['ssl'].ERR_get_error
|
||||
ERR_get_error.restype = ctypes.c_long
|
||||
ERR_get_error.argtypes = []
|
||||
|
||||
|
||||
ERR_error_string_n = libraries['ssl'].ERR_error_string_n
|
||||
ERR_error_string_n.restype = ctypes.c_void_p
|
||||
ERR_error_string_n.argtypes = [ctypes.c_long, ctypes.c_char_p, ctypes.c_int]
|
||||
|
||||
RSA_size = libraries['ssl'].RSA_size
|
||||
RSA_size.restype = ctypes.c_int
|
||||
RSA_size.argtypes = [ctypes.c_void_p]
|
||||
|
||||
RSA_new = libraries['ssl'].RSA_new
|
||||
RSA_new.restype = ctypes.POINTER(RSA)
|
||||
|
||||
RSA_generate_key = libraries['ssl'].RSA_generate_key
|
||||
RSA_generate_key.restype = ctypes.POINTER(RSA)
|
||||
RSA_generate_key.argtypes = [ctypes.c_int, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
RSA_print = libraries['ssl'].RSA_print
|
||||
RSA_print.restype = ctypes.c_int
|
||||
RSA_print.argtypes = [ctypes.c_void_p, ctypes.POINTER(RSA), ctypes.c_int]
|
||||
|
||||
PEM_write_bio_RSA_PUBKEY = libraries['ssl'].PEM_write_bio_RSA_PUBKEY
|
||||
|
||||
PEM_write_bio_RSAPrivateKey = libraries['ssl'].PEM_write_bio_RSAPrivateKey
|
||||
PEM_write_bio_RSAPrivateKey.restype = ctypes.c_int
|
||||
PEM_write_bio_RSAPrivateKey.argtypes = [ctypes.c_void_p, ctypes.POINTER(RSA), ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
PEM_write_RSAPrivateKey = libraries['ssl'].PEM_write_RSAPrivateKey
|
||||
PEM_write_RSAPrivateKey.restype = ctypes.c_int
|
||||
PEM_write_RSAPrivateKey.argtypes = [ctypes.c_void_p, ctypes.POINTER(RSA), ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
i2d_RSAPrivateKey = libraries['ssl'].i2d_RSAPrivateKey
|
||||
|
||||
BIO_read = libraries['ssl'].BIO_read
|
||||
|
||||
BIO_s_mem = libraries['ssl'].BIO_s_mem
|
||||
BIO_s_mem.restype = ctypes.c_void_p
|
||||
|
||||
BIO_new = libraries['ssl'].BIO_new
|
||||
BIO_new.restype = ctypes.c_void_p
|
||||
BIO_new.argtypes = [ctypes.c_void_p]
|
||||
|
||||
RAND_seed = libraries['ssl'].RAND_seed
|
||||
RAND_seed.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
||||
215
src/evpy/signature.py
Executable file
215
src/evpy/signature.py
Executable file
|
|
@ -0,0 +1,215 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""
|
||||
signature.py
|
||||
|
||||
Written by Geremy Condra
|
||||
Released on 18 March 2010
|
||||
Licensed under MIT License
|
||||
|
||||
This module provides a basic interface to OpenSSL's EVP
|
||||
signature functions.
|
||||
|
||||
All functions in this module will raise a SignatureError
|
||||
in the event of a malfunction.
|
||||
|
||||
The goal of cryptographic signatures is to provide some
|
||||
degree of assurance that the data you are processing is
|
||||
both coming from the person you think is sending it and
|
||||
is what they sent.
|
||||
|
||||
Note that this does not encrypt data in the sense that
|
||||
it does not provide secrecy for it, while evpy.cipher
|
||||
and evpy.envelope provide secrecy but no other security
|
||||
properties.
|
||||
|
||||
Usage:
|
||||
>>> from evpy import signature
|
||||
>>> data = b"abcdefg"
|
||||
>>> public_key = "test/keys/public1.pem"
|
||||
>>> private_key = "test/keys/private1.pem"
|
||||
>>> s = signature.sign(data, private_key)
|
||||
>>> signature.verify(data, s, public_key)
|
||||
True
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
|
||||
import evp
|
||||
|
||||
|
||||
class SignatureError(evp.SSLError):
|
||||
pass
|
||||
|
||||
|
||||
def sign(data, keyfile=None, key=None):
|
||||
"""Signs the given data, raising SignatureError on failure.
|
||||
|
||||
Exactly one of keyfile, key should be given; if key is not
|
||||
defined, then the key will be read from the given file.
|
||||
|
||||
Usage:
|
||||
>>> from evpy import signature
|
||||
>>> f = open("test/short.txt", "rb")
|
||||
>>> data = f.read()
|
||||
>>> public_key = "test/keys/public1.pem"
|
||||
>>> private_key = "test/keys/private1.pem"
|
||||
>>> s = signature.sign(data, private_key)
|
||||
>>> signature.verify(data, s, public_key)
|
||||
True
|
||||
"""
|
||||
# add the digests
|
||||
evp.OpenSSL_add_all_digests()
|
||||
|
||||
# build the context
|
||||
ctx = evp.EVP_MD_CTX_create()
|
||||
if not ctx:
|
||||
raise SignatureError("Could not create context")
|
||||
|
||||
# get the signing key
|
||||
if key and not keyfile:
|
||||
skey = _build_skey_from_string(key)
|
||||
elif keyfile and not key:
|
||||
skey = _build_skey_from_file(keyfile)
|
||||
else:
|
||||
raise SignatureError("Exactly one of key, keyfile must be specified")
|
||||
|
||||
# build the hash object
|
||||
evp_hash = _build_hash()
|
||||
if not evp.EVP_DigestInit(ctx, evp_hash):
|
||||
_cleanup(skey, ctx)
|
||||
raise SignatureError("Could not initialize signature")
|
||||
|
||||
# update
|
||||
if not evp.EVP_DigestUpdate(ctx, data, len(data)):
|
||||
_cleanup(skey, ctx)
|
||||
raise SignatureError("Could not update signature")
|
||||
|
||||
# finalize
|
||||
output_buflen = ctypes.c_int(evp.EVP_PKEY_size(skey))
|
||||
output = ctypes.create_string_buffer(output_buflen.value)
|
||||
if not evp.EVP_SignFinal(ctx, output, ctypes.byref(output_buflen), skey):
|
||||
_cleanup(skey, ctx)
|
||||
raise SignatureError("Could not finalize signature")
|
||||
|
||||
# cleanup
|
||||
_cleanup(skey, ctx)
|
||||
|
||||
# and go home
|
||||
return ctypes.string_at(output, output_buflen)
|
||||
|
||||
|
||||
def verify(data, sig, keyfile=None, key=None):
|
||||
"""Verifies the given signature, returning a boolean.
|
||||
|
||||
Exactly one of keyfile, key should be specified.
|
||||
|
||||
This function raises SignatureError on error.
|
||||
|
||||
Usage:
|
||||
>>> from evpy import signature
|
||||
>>> f = open("test/short.txt", "rb")
|
||||
>>> data = f.read()
|
||||
>>> public_key = "test/keys/public1.pem"
|
||||
>>> private_key = "test/keys/private1.pem"
|
||||
>>> s = signature.sign(data, private_key)
|
||||
>>> signature.verify(data, s, public_key)
|
||||
True
|
||||
"""
|
||||
# add the digests
|
||||
evp.OpenSSL_add_all_digests()
|
||||
|
||||
# build the context
|
||||
ctx = evp.EVP_MD_CTX_create()
|
||||
if not ctx:
|
||||
raise SignatureError("Could not create context")
|
||||
|
||||
# get the vkey
|
||||
if key and not keyfile:
|
||||
vkey = _build_vkey_from_string(key)
|
||||
elif keyfile and not key:
|
||||
vkey = _build_vkey_from_file(keyfile)
|
||||
else:
|
||||
raise SignatureError("Exactly one of key, keyfile must be specified")
|
||||
|
||||
# build the hash object
|
||||
evp_hash = _build_hash()
|
||||
if not evp.EVP_DigestInit(ctx, evp_hash):
|
||||
_cleanup(vkey, ctx)
|
||||
raise SignatureError("Could not initialize verifier")
|
||||
|
||||
# update
|
||||
if not evp.EVP_DigestUpdate(ctx, data, len(data)):
|
||||
_cleanup(vkey, ctx)
|
||||
raise SignatureError("Could not update verifier")
|
||||
|
||||
# finalize
|
||||
retcode = evp.EVP_VerifyFinal(ctx, sig, len(sig), vkey)
|
||||
|
||||
# cleanup
|
||||
_cleanup(vkey, ctx)
|
||||
|
||||
# and go home
|
||||
if retcode == 1:
|
||||
return True
|
||||
elif retcode == 0:
|
||||
return False
|
||||
else:
|
||||
raise SignatureError("Error verifying signature")
|
||||
|
||||
def _cleanup(key, ctx):
|
||||
evp.EVP_PKEY_free(key)
|
||||
evp.EVP_MD_CTX_cleanup(ctx)
|
||||
evp.EVP_MD_CTX_destroy(ctx)
|
||||
|
||||
def _string_to_bio(s):
|
||||
return evp.BIO_new_mem_buf(s, len(s))
|
||||
|
||||
def _build_skey_from_file(keyfile):
|
||||
fp = evp.fopen(keyfile, "r")
|
||||
if not fp:
|
||||
raise SignatureError("Could not open keyfile")
|
||||
# get the signing key
|
||||
skey = evp.PEM_read_PrivateKey(fp, None, None, None)
|
||||
if not skey:
|
||||
evp.fclose(fp)
|
||||
raise SignatureError("Could not read signing key")
|
||||
# close the file
|
||||
evp.fclose(fp)
|
||||
return skey
|
||||
|
||||
def _build_skey_from_string(key):
|
||||
buf = ctypes.create_string_buffer(key)
|
||||
bio = evp.BIO_new_mem_buf(buf, len(buf.value))
|
||||
skey = evp.PEM_read_bio_PrivateKey(bio, None, None, None, None)
|
||||
if not skey:
|
||||
raise SignatureError("Could not construct signing key from the given string")
|
||||
evp.BIO_free(bio)
|
||||
return skey
|
||||
|
||||
def _build_vkey_from_file(keyfile):
|
||||
fp = evp.fopen(keyfile, "r")
|
||||
if not fp:
|
||||
raise SignatureError("Could not open keyfile")
|
||||
# get the verification key
|
||||
vkey = evp.PEM_read_PUBKEY(fp, None, None, None)
|
||||
if not vkey:
|
||||
evp.fclose(fp)
|
||||
raise SignatureError("Could not read verification key")
|
||||
# close the file
|
||||
evp.fclose(fp)
|
||||
return vkey
|
||||
|
||||
def _build_vkey_from_string(key):
|
||||
buf = ctypes.create_string_buffer(key)
|
||||
bio = evp.BIO_new_mem_buf(buf, len(buf.value))
|
||||
vkey = evp.PEM_read_bio_PUBKEY(bio, None, None, None)
|
||||
if not vkey:
|
||||
raise SignatureError("Could not construct verification key from the given string")
|
||||
return vkey
|
||||
|
||||
def _build_hash():
|
||||
evp_hash = evp.EVP_get_digestbyname("sha512")
|
||||
if not evp_hash:
|
||||
raise SignatureError("Could not create hash object")
|
||||
return evp_hash
|
||||
668
src/evpy/test.py
Executable file
668
src/evpy/test.py
Executable file
|
|
@ -0,0 +1,668 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""
|
||||
test.py
|
||||
|
||||
Written by Geremy Condra
|
||||
Licensed under MIT License
|
||||
Released 21 March 2010
|
||||
|
||||
Simple unit tests for evpy
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from evpy import evp
|
||||
from evpy import cipher
|
||||
from evpy import signature
|
||||
from evpy import envelope
|
||||
|
||||
# locations for test data
|
||||
TEST_DATA_DIR = "test/"
|
||||
TEST_KEYS = TEST_DATA_DIR + "keys/"
|
||||
|
||||
# test text
|
||||
STRING = open(TEST_DATA_DIR + "long.txt").read()
|
||||
LONG = open(TEST_DATA_DIR + "long.txt", 'rb').read()
|
||||
SHORT = open(TEST_DATA_DIR + "short.txt", 'rb').read()
|
||||
UNICODE = open(TEST_DATA_DIR + "unicode.txt", 'rb').read()
|
||||
NULL = open(TEST_DATA_DIR + "null.txt", 'rb').read()
|
||||
TEST_TEXTS = { "long": LONG,
|
||||
"short": SHORT,
|
||||
"unicode": UNICODE,
|
||||
"null": NULL}
|
||||
|
||||
# test keys
|
||||
SHORT_SYMMETRIC = open(TEST_KEYS + "short_symmetric.txt", 'rb').read()
|
||||
LONG_SYMMETRIC = open(TEST_KEYS + "long_symmetric.txt", 'rb').read()
|
||||
SYMMETRIC_KEYS = [LONG_SYMMETRIC, SHORT_SYMMETRIC]
|
||||
SYMMETRIC_STRING = open(TEST_KEYS + "short_symmetric.txt").read()
|
||||
|
||||
KEY_1 = TEST_KEYS + "private1.pem", TEST_KEYS + "public1.pem"
|
||||
KEY_2 = TEST_KEYS + "private2.pem", TEST_KEYS + "public2.pem"
|
||||
MISMATCH_1 = KEY_1[0], KEY_2[1]
|
||||
MISMATCH_2 = KEY_2[0], KEY_1[1]
|
||||
MISSING_PRIVATE_KEY = "notakey.pem", KEY_1[1]
|
||||
MISSING_PUBLIC_KEY = KEY_1[0], "notakey.pem"
|
||||
BLANK_PRIVATE_KEY = KEY_1[0], TEST_KEYS + "blank.pem"
|
||||
BLANK_PUBLIC_KEY = TEST_KEYS + "blank.pem", KEY_1[1]
|
||||
|
||||
def run_n_times(f, g, n):
|
||||
def err(*args, **kwargs):
|
||||
if err.n > 0:
|
||||
err.n -= 1
|
||||
return f(*args, **kwargs)
|
||||
else:
|
||||
return g(*args, **kwargs)
|
||||
err.n = n
|
||||
return err
|
||||
|
||||
class TestCipher(unittest.TestCase):
|
||||
|
||||
def round_trip(self, key, text):
|
||||
salt, iv, enc = cipher.encrypt(text, key)
|
||||
output = cipher.decrypt(salt, iv, enc, key)
|
||||
self.assertEqual(output, text, "Failed to round trip")
|
||||
|
||||
def test_round_trip_short_zero(self):
|
||||
self.assertRaises(cipher.CipherError, self.round_trip, SHORT_SYMMETRIC, '')
|
||||
|
||||
def test_round_trip_short_long(self):
|
||||
self.round_trip(SHORT_SYMMETRIC, LONG)
|
||||
|
||||
def test_round_trip_short_short(self):
|
||||
self.round_trip(SHORT_SYMMETRIC, SHORT)
|
||||
|
||||
def test_round_trip_short_unicode(self):
|
||||
self.round_trip(SHORT_SYMMETRIC, UNICODE)
|
||||
|
||||
def test_round_trip_short_null(self):
|
||||
self.round_trip(SHORT_SYMMETRIC, NULL)
|
||||
|
||||
def test_round_trip_long_zero(self):
|
||||
self.assertRaises(cipher.CipherError, self.round_trip, LONG_SYMMETRIC, '')
|
||||
|
||||
def test_round_trip_long_long(self):
|
||||
self.round_trip(LONG_SYMMETRIC, LONG)
|
||||
|
||||
def test_round_trip_long_short(self):
|
||||
self.round_trip(LONG_SYMMETRIC, SHORT)
|
||||
|
||||
def test_round_trip_long_unicode(self):
|
||||
self.round_trip(LONG_SYMMETRIC, UNICODE)
|
||||
|
||||
def test_round_trip_long_null(self):
|
||||
self.round_trip(LONG_SYMMETRIC, NULL)
|
||||
|
||||
def test_no_data(self):
|
||||
iv, salt, enc = cipher.encrypt(UNICODE, SHORT_SYMMETRIC)
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, iv, salt, '', SHORT_SYMMETRIC)
|
||||
|
||||
def test_short_salt(self):
|
||||
salt, iv, enc = cipher.encrypt(UNICODE, SHORT_SYMMETRIC)
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, salt[:-1], iv, enc, SHORT_SYMMETRIC)
|
||||
|
||||
def test_long_salt(self):
|
||||
salt, iv, enc = cipher.encrypt(UNICODE, SHORT_SYMMETRIC)
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, salt+salt[:-1], iv, enc, SHORT_SYMMETRIC)
|
||||
|
||||
def test_short_iv(self):
|
||||
salt, iv, enc = cipher.encrypt(UNICODE, SHORT_SYMMETRIC)
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, salt, iv[:-1], enc, SHORT_SYMMETRIC)
|
||||
|
||||
def test_long_iv(self):
|
||||
salt, iv, enc = cipher.encrypt(UNICODE, SHORT_SYMMETRIC)
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, salt, iv+iv[:-1], enc, SHORT_SYMMETRIC)
|
||||
|
||||
def test_round_trip_no_password(self):
|
||||
iv, salt, enc = cipher.encrypt(UNICODE, SHORT_SYMMETRIC)
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, UNICODE, '')
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, iv, salt, enc, '')
|
||||
|
||||
def test_round_trip_failure(self):
|
||||
salt, iv, enc = cipher.encrypt(SHORT, SHORT_SYMMETRIC)
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, salt, iv, enc, LONG_SYMMETRIC)
|
||||
salt, iv, enc = cipher.encrypt(NULL, SHORT_SYMMETRIC)
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, salt, iv, enc, LONG_SYMMETRIC)
|
||||
|
||||
def test_bad_rand_bytes(self):
|
||||
iv, salt, enc = cipher.encrypt(SHORT, SHORT_SYMMETRIC)
|
||||
rand_bytes = cipher.evp.RAND_bytes
|
||||
cipher.evp.RAND_bytes = lambda a,b: 0
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, SHORT, SHORT_SYMMETRIC)
|
||||
cipher.evp.RAND_bytes = rand_bytes
|
||||
|
||||
def test_bad_rand_bytes_1(self):
|
||||
iv, salt, enc = cipher.encrypt(SHORT, SHORT_SYMMETRIC)
|
||||
rand_bytes = cipher.evp.RAND_bytes
|
||||
cipher.evp.RAND_bytes = lambda a,b: 0
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, SHORT, SHORT_SYMMETRIC)
|
||||
cipher.evp.RAND_bytes = rand_bytes
|
||||
|
||||
def test_bad_rand_bytes_2(self):
|
||||
iv, salt, enc = cipher.encrypt(SHORT, SHORT_SYMMETRIC)
|
||||
rand_bytes = cipher.evp.RAND_bytes
|
||||
cipher.evp.RAND_bytes = run_n_times(cipher.evp.RAND_bytes, lambda a,b:0, 1)
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, SHORT, SHORT_SYMMETRIC)
|
||||
cipher.evp.RAND_bytes = rand_bytes
|
||||
|
||||
def test_bad_hash_by_name(self):
|
||||
iv, salt, enc = cipher.encrypt(SHORT, SHORT_SYMMETRIC)
|
||||
hash_by_name = cipher.evp.EVP_get_digestbyname
|
||||
cipher.evp.EVP_get_digestbyname = lambda a: None
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, SHORT, SHORT_SYMMETRIC)
|
||||
cipher.evp.EVP_get_digestbyname = hash_by_name
|
||||
|
||||
def test_bad_bytes_to_key(self):
|
||||
iv, salt, enc = cipher.encrypt(SHORT, SHORT_SYMMETRIC)
|
||||
bytes_to_key = cipher.evp.EVP_BytesToKey
|
||||
cipher.evp.EVP_BytesToKey = lambda a,b,c,d,e,f,g,h: 0
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, SHORT, SHORT_SYMMETRIC)
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, iv, salt, enc, SHORT_SYMMETRIC)
|
||||
cipher.evp.EVP_BytesToKey = bytes_to_key
|
||||
|
||||
def test_bad_ctx_new(self):
|
||||
iv, salt, enc = cipher.encrypt(SHORT, SHORT_SYMMETRIC)
|
||||
new_ctx = cipher.evp.EVP_CIPHER_CTX_new
|
||||
cipher.evp.EVP_CIPHER_CTX_new = lambda: None
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, SHORT, SHORT_SYMMETRIC)
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, iv, salt, enc, SHORT_SYMMETRIC)
|
||||
cipher.evp.EVP_CIPHER_CTX_new = new_ctx
|
||||
|
||||
def test_bad_cipher_object(self):
|
||||
iv, salt, enc = cipher.encrypt(SHORT, SHORT_SYMMETRIC)
|
||||
cipher_getter = cipher.evp.EVP_aes_192_cbc
|
||||
cipher.evp.EVP_aes_192_cbc= lambda: None
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, SHORT, SHORT_SYMMETRIC)
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, iv, salt, enc, SHORT_SYMMETRIC)
|
||||
cipher.evp.EVP_aes_192_cbc = cipher_getter
|
||||
|
||||
def test_bad_encrypt_init_1(self):
|
||||
encrypt_init = cipher.evp.EVP_EncryptInit_ex
|
||||
cipher.evp.EVP_EncryptInit_ex = lambda a,b,c,d,e: None
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, SHORT, SHORT_SYMMETRIC)
|
||||
cipher.evp.EVP_EncryptInit_ex = encrypt_init
|
||||
|
||||
def test_bad_encrypt_init_2(self):
|
||||
encrypt_init = cipher.evp.EVP_EncryptInit_ex
|
||||
cipher.evp.EVP_EncryptInit_ex = run_n_times(cipher.evp.EVP_EncryptInit_ex, lambda a,b,c,d,e: None, 1)
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, SHORT, SHORT_SYMMETRIC)
|
||||
cipher.evp.EVP_EncryptInit_ex = encrypt_init
|
||||
|
||||
def test_bad_encrypt_update(self):
|
||||
encrypt_update = cipher.evp.EVP_EncryptUpdate
|
||||
cipher.evp.EVP_EncryptUpdate = lambda a,b,c,d,e: None
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, SHORT, SHORT_SYMMETRIC)
|
||||
cipher.evp.EVP_EncryptUpdate = encrypt_update
|
||||
|
||||
def test_bad_encrypt_final(self):
|
||||
encrypt_final = cipher.evp.EVP_EncryptFinal_ex
|
||||
cipher.evp.EVP_EncryptFinal_ex = lambda a,b,c: None
|
||||
self.assertRaises(cipher.CipherError, cipher.encrypt, SHORT, SHORT_SYMMETRIC)
|
||||
cipher.evp.EVP_EncryptFinal_ex = encrypt_final
|
||||
|
||||
def test_bad_decrypt_init(self):
|
||||
iv, salt, enc = cipher.encrypt(SHORT, SHORT_SYMMETRIC)
|
||||
decrypt_init = cipher.evp.EVP_DecryptInit_ex
|
||||
cipher.evp.EVP_DecryptInit_ex = lambda a,b,c,d,e: None
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, iv, salt, enc, SHORT_SYMMETRIC)
|
||||
cipher.evp.EVP_DecryptInit_ex = decrypt_init
|
||||
|
||||
def test_bad_decrypt_update(self):
|
||||
iv, salt, enc = cipher.encrypt(SHORT, SHORT_SYMMETRIC)
|
||||
decrypt_update = cipher.evp.EVP_DecryptUpdate
|
||||
cipher.evp.EVP_DecryptUpdate = lambda a,b,c,d,e: None
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, iv, salt, enc, SHORT_SYMMETRIC)
|
||||
cipher.evp.EVP_DecryptUpdate = decrypt_update
|
||||
|
||||
def test_bad_decrypt_final(self):
|
||||
iv, salt, enc = cipher.encrypt(SHORT, SHORT_SYMMETRIC)
|
||||
decrypt_final = cipher.evp.EVP_DecryptFinal_ex
|
||||
cipher.evp.EVP_DecryptFinal_ex = lambda a,b,c: None
|
||||
self.assertRaises(cipher.CipherError, cipher.decrypt, iv, salt, enc, SHORT_SYMMETRIC)
|
||||
cipher.evp.EVP_DecryptFinal_ex = decrypt_final
|
||||
|
||||
|
||||
class TestSignature(unittest.TestCase):
|
||||
|
||||
def round_trip(self, keys, text):
|
||||
s = signature.sign(text, keys[0])
|
||||
return signature.verify(text, s, keys[1])
|
||||
|
||||
def round_trip_strings(self, keys, text):
|
||||
s = signature.sign(text, key=open(keys[0], 'rb').read())
|
||||
v = signature.verify(text, s, key=open(keys[1], 'rb').read())
|
||||
return v
|
||||
|
||||
def round_trip_all_keys(self, text):
|
||||
self.assertTrue(self.round_trip(KEY_1, text))
|
||||
self.assertTrue(self.round_trip(KEY_2, text))
|
||||
self.assertFalse(self.round_trip(MISMATCH_1, text))
|
||||
self.assertFalse(self.round_trip(MISMATCH_2, text))
|
||||
self.assertTrue(self.round_trip_strings(KEY_1, text))
|
||||
self.assertTrue(self.round_trip_strings(KEY_2, text))
|
||||
self.assertFalse(self.round_trip_strings(MISMATCH_1, text))
|
||||
self.assertFalse(self.round_trip_strings(MISMATCH_2, text))
|
||||
|
||||
def test_round_trip_long(self):
|
||||
self.round_trip_all_keys(LONG)
|
||||
|
||||
def test_round_trip_short(self):
|
||||
self.round_trip_all_keys(SHORT)
|
||||
|
||||
def test_round_trip_unicode(self):
|
||||
self.round_trip_all_keys(UNICODE)
|
||||
|
||||
def test_round_trip_null(self):
|
||||
self.round_trip_all_keys(NULL)
|
||||
|
||||
def test_round_trip_zero(self):
|
||||
self.round_trip_all_keys('')
|
||||
|
||||
def test_arguments(self):
|
||||
text = SHORT
|
||||
keys = KEY_1
|
||||
self.failUnlessRaises(signature.SignatureError, signature.sign, text, key=open(keys[0], 'rb').read(), keyfile=keys[0])
|
||||
self.failUnlessRaises(signature.SignatureError, signature.sign, text)
|
||||
s = signature.sign(text, keyfile=keys[0])
|
||||
self.failUnlessRaises(signature.SignatureError, signature.verify, text, s, key=open(keys[1], 'rb').read(), keyfile=keys[1])
|
||||
self.failUnlessRaises(signature.SignatureError, signature.verify, text, s)
|
||||
|
||||
def test_bad_ctx(self):
|
||||
s = signature.sign(SHORT, KEY_1[0])
|
||||
new_ctx = signature.evp.EVP_MD_CTX_create
|
||||
signature.evp.EVP_MD_CTX_create = lambda: None
|
||||
self.assertRaises(signature.SignatureError, signature.sign, SHORT, KEY_1[0])
|
||||
self.assertRaises(signature.SignatureError, signature.verify, SHORT, s, KEY_1[1])
|
||||
signature.evp.EVP_MD_CTX_create = new_ctx
|
||||
|
||||
def test_bad_hash(self):
|
||||
s = signature.sign(SHORT, KEY_1[0])
|
||||
new_hash = signature.evp.EVP_get_digestbyname
|
||||
signature.evp.EVP_get_digestbyname = lambda a: None
|
||||
self.assertRaises(signature.SignatureError, signature.sign, SHORT, KEY_1[0])
|
||||
self.assertRaises(signature.SignatureError, signature.verify, SHORT, s, KEY_1[1])
|
||||
signature.evp.EVP_get_digestbyname = new_hash
|
||||
|
||||
def test_bad_digest_init(self):
|
||||
s = signature.sign(SHORT, KEY_1[0])
|
||||
init = signature.evp.EVP_DigestInit
|
||||
signature.evp.EVP_DigestInit = lambda a,b: None
|
||||
self.assertRaises(signature.SignatureError, signature.sign, SHORT, KEY_1[0])
|
||||
self.assertRaises(signature.SignatureError, signature.verify, SHORT, s, KEY_1[1])
|
||||
signature.evp.EVP_DigestInit = init
|
||||
|
||||
def test_bad_digest_update(self):
|
||||
s = signature.sign(SHORT, KEY_1[0])
|
||||
update = signature.evp.EVP_DigestUpdate
|
||||
signature.evp.EVP_DigestUpdate = lambda a,b,c: None
|
||||
self.assertRaises(signature.SignatureError, signature.sign, SHORT, KEY_1[0])
|
||||
self.assertRaises(signature.SignatureError, signature.verify, SHORT, s, KEY_1[1])
|
||||
signature.evp.EVP_DigestUpdate = update
|
||||
|
||||
def test_bad_sign_final(self):
|
||||
final = signature.evp.EVP_SignFinal
|
||||
signature.evp.EVP_SignFinal = lambda a,b,c,d: None
|
||||
self.assertRaises(signature.SignatureError, signature.sign, SHORT, KEY_1[0])
|
||||
signature.evp.EVP_SignFinal = final
|
||||
|
||||
def test_bad_verify_final(self):
|
||||
s = signature.sign(SHORT, KEY_1[0])
|
||||
final = signature.evp.EVP_VerifyFinal
|
||||
signature.evp.EVP_VerifyFinal = lambda a,b,c,d: None
|
||||
self.assertRaises(signature.SignatureError, signature.verify, SHORT, s, KEY_1[1])
|
||||
signature.evp.EVP_VerifyFinal = final
|
||||
|
||||
def test_bad_read_privatekey(self):
|
||||
read_private_key = signature.evp.PEM_read_PrivateKey
|
||||
signature.evp.PEM_read_PrivateKey = lambda a,b,c,d: None
|
||||
self.assertRaises(signature.SignatureError, signature.sign, SHORT, KEY_1[0])
|
||||
signature.evp.PEM_read_PrivateKey = read_private_key
|
||||
|
||||
def test_bad_read_publickey(self):
|
||||
s = signature.sign(SHORT, KEY_1[0])
|
||||
read_public_key = signature.evp.PEM_read_PUBKEY
|
||||
signature.evp.PEM_read_PUBKEY = lambda a,b,c,d: None
|
||||
self.assertRaises(signature.SignatureError, signature.verify, SHORT, s, KEY_1[1])
|
||||
signature.evp.PEM_read_PUBKEY = read_public_key
|
||||
|
||||
def test_bad_read_bio_privatekey(self):
|
||||
read_private_key = signature.evp.PEM_read_bio_PrivateKey
|
||||
signature.evp.PEM_read_bio_PrivateKey = lambda a,b,c,d,e: None
|
||||
self.assertRaises(signature.SignatureError, signature.sign, SHORT, key=open(KEY_1[0], 'rb').read())
|
||||
signature.evp.PEM_read_bio_PrivateKey = read_private_key
|
||||
|
||||
def test_bad_read_bio_publickey(self):
|
||||
s = signature.sign(SHORT, KEY_1[0])
|
||||
read_public_key = signature.evp.PEM_read_bio_PUBKEY
|
||||
signature.evp.PEM_read_bio_PUBKEY = lambda a,b,c,d: None
|
||||
self.assertRaises(signature.SignatureError, signature.verify, SHORT, s, key=open(KEY_1[1], 'rb').read())
|
||||
signature.evp.PEM_read_bio_PUBKEY = read_public_key
|
||||
|
||||
def test_bad_fopen(self):
|
||||
s = signature.sign(SHORT, KEY_1[0])
|
||||
file_open = signature.evp.fopen
|
||||
signature.evp.fopen = lambda a,b: None
|
||||
self.assertRaises(signature.SignatureError, signature.sign, SHORT, KEY_1[1])
|
||||
self.assertRaises(signature.SignatureError, signature.verify, SHORT, s, KEY_1[0])
|
||||
signature.evp.fopen = file_open
|
||||
|
||||
|
||||
class TestEnvelope(unittest.TestCase):
|
||||
|
||||
def round_trip(self, keys, text):
|
||||
iv, sym_key, enc = envelope.encrypt(text, keys[1])
|
||||
return envelope.decrypt(iv, sym_key, enc, keys[0])
|
||||
|
||||
def round_trip_strings(self, keys, text):
|
||||
iv, sym_key, enc = envelope.encrypt(text, key=open(keys[1], 'rb').read())
|
||||
return envelope.decrypt(iv, sym_key, enc, key=open(keys[0], 'rb').read())
|
||||
|
||||
def test_round_trip_zero(self):
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, KEY_1, '')
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, KEY_2, '')
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISMATCH_1, '')
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISMATCH_2, '')
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, KEY_1, '')
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, KEY_2, '')
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, MISMATCH_1, '')
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, MISMATCH_2, '')
|
||||
|
||||
def test_round_trip_long(self):
|
||||
self.assertEqual(self.round_trip_strings(KEY_1, LONG), LONG, "Failed to round trip")
|
||||
self.assertEqual(self.round_trip_strings(KEY_2, LONG), LONG, "Failed to round trip")
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, MISMATCH_1, LONG)
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, MISMATCH_2, LONG)
|
||||
self.assertEqual(self.round_trip(KEY_1, LONG), LONG, "Failed to round trip")
|
||||
self.assertEqual(self.round_trip(KEY_2, LONG), LONG, "Failed to round trip")
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISMATCH_1, LONG)
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISMATCH_2, LONG)
|
||||
|
||||
def test_round_trip_short(self):
|
||||
self.assertEqual(self.round_trip(KEY_1, SHORT), SHORT, "Failed to round trip")
|
||||
self.assertEqual(self.round_trip(KEY_2, SHORT), SHORT, "Failed to round trip")
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISMATCH_1, SHORT)
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISMATCH_2, SHORT)
|
||||
self.assertEqual(self.round_trip_strings(KEY_1, SHORT), SHORT, "Failed to round trip")
|
||||
self.assertEqual(self.round_trip_strings(KEY_2, SHORT), SHORT, "Failed to round trip")
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, MISMATCH_1, SHORT)
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, MISMATCH_2, SHORT)
|
||||
|
||||
def test_round_trip_unicode(self):
|
||||
self.assertEqual(self.round_trip(KEY_1, UNICODE), UNICODE, "Failed to round trip")
|
||||
self.assertEqual(self.round_trip(KEY_2, UNICODE), UNICODE, "Failed to round trip")
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISMATCH_1, UNICODE)
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISMATCH_2, UNICODE)
|
||||
self.assertEqual(self.round_trip_strings(KEY_1, UNICODE), UNICODE, "Failed to round trip")
|
||||
self.assertEqual(self.round_trip_strings(KEY_2, UNICODE), UNICODE, "Failed to round trip")
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, MISMATCH_1, UNICODE)
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, MISMATCH_2, UNICODE)
|
||||
|
||||
def test_round_trip_null(self):
|
||||
self.assertEqual(self.round_trip(KEY_1, NULL), NULL, "Failed to round trip")
|
||||
self.assertEqual(self.round_trip(KEY_2, NULL), NULL, "Failed to round trip")
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISMATCH_1, NULL)
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISMATCH_2, NULL)
|
||||
self.assertEqual(self.round_trip_strings(KEY_1, NULL), NULL, "Failed to round trip")
|
||||
self.assertEqual(self.round_trip_strings(KEY_2, NULL), NULL, "Failed to round trip")
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, MISMATCH_1, NULL)
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, MISMATCH_2, NULL)
|
||||
|
||||
def test_bad_keys(self):
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISSING_PUBLIC_KEY, SHORT)
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip, MISSING_PRIVATE_KEY, SHORT)
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, BLANK_PUBLIC_KEY, SHORT)
|
||||
self.assertRaises(envelope.EnvelopeError, self.round_trip_strings, BLANK_PRIVATE_KEY, SHORT)
|
||||
|
||||
def test_bad_call(self):
|
||||
# neither key nor keyfile
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT)
|
||||
# both
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[0], open(KEY_1[0], 'rb').read())
|
||||
# string key instead of bytes
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, key=open(KEY_1[0], 'r').read())
|
||||
# string data instead of bytes
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, str(SHORT), KEY_1[0], open(KEY_1[0], 'rb').read())
|
||||
# get valid encryption data
|
||||
iv, aes_key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
# neither key nor keyfile
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, aes_key, enc)
|
||||
# both
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, aes_key, enc, KEY_1[1], key=open(KEY_1[1], 'rb').read())
|
||||
# string key instead of bytes
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, aes_key, enc, key=open(KEY_1[1], 'r').read())
|
||||
# string data instead of bytes
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, aes_key, str(enc), KEY_1[1], key=open(KEY_1[1], 'rb').read())
|
||||
# string iv instead of bytes
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, str(iv), aes_key, enc, KEY_1[1], key=open(KEY_1[1], 'rb').read())
|
||||
# string key instead of bytes
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, str(aes_key), enc, KEY_1[1], key=open(KEY_1[1], 'rb').read())
|
||||
|
||||
def test_bad_rand_bytes_1(self):
|
||||
rand_bytes = envelope.evp.RAND_bytes
|
||||
envelope.evp.RAND_bytes = lambda a,b:0
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.RAND_bytes = rand_bytes
|
||||
|
||||
def test_bad_rand_bytes_2(self):
|
||||
rand_bytes = envelope.evp.RAND_bytes
|
||||
envelope.evp.RAND_bytes = run_n_times(evp.RAND_bytes, lambda a,b:0, 1)
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.RAND_bytes = rand_bytes
|
||||
|
||||
def test_bad_rsa_size(self):
|
||||
rsa_size = envelope.evp.RSA_size
|
||||
envelope.evp.RSA_size = lambda a:0
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.RSA_size = rsa_size
|
||||
|
||||
def test_bad_read_privatekey(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
read_private_key = envelope.evp.PEM_read_PrivateKey
|
||||
envelope.evp.PEM_read_PrivateKey = lambda a,b,c,d: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, KEY_1[0])
|
||||
envelope.evp.PEM_read_PrivateKey = read_private_key
|
||||
|
||||
def test_bad_read_publickey(self):
|
||||
read_public_key = envelope.evp.PEM_read_PUBKEY
|
||||
envelope.evp.PEM_read_PUBKEY = lambda a,b,c,d: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.PEM_read_PUBKEY = read_public_key
|
||||
|
||||
def test_bad_read_bio_privatekey(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
read_private_key = envelope.evp.PEM_read_bio_PrivateKey
|
||||
envelope.evp.PEM_read_bio_PrivateKey = lambda a,b,c,d: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, key=open(KEY_1[0], 'rb').read())
|
||||
envelope.evp.PEM_read_bio_PrivateKey = read_private_key
|
||||
|
||||
def test_bad_read_bio_publickey(self):
|
||||
read_public_key = envelope.evp.PEM_read_bio_PUBKEY
|
||||
envelope.evp.PEM_read_bio_PUBKEY = lambda a,b,c,d: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, key=open(KEY_1[0], 'rb').read())
|
||||
envelope.evp.PEM_read_bio_PUBKEY = read_public_key
|
||||
|
||||
def test_bad_fopen(self):
|
||||
iv, aes_key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
file_open = envelope.evp.fopen
|
||||
envelope.evp.fopen = lambda a,b: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, aes_key, enc, KEY_1[0])
|
||||
envelope.evp.fopen = file_open
|
||||
|
||||
def test_bad_ctx_new(self):
|
||||
iv, aes_key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
new_ctx = envelope.evp.EVP_CIPHER_CTX_new
|
||||
envelope.evp.EVP_CIPHER_CTX_new = lambda: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, aes_key, enc, KEY_1[0])
|
||||
envelope.evp.EVP_CIPHER_CTX_new = new_ctx
|
||||
|
||||
def test_bad_cipher_object(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
cipher_getter = envelope.evp.EVP_aes_192_cbc
|
||||
envelope.evp.EVP_aes_192_cbc= lambda: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, KEY_1[0])
|
||||
envelope.evp.EVP_aes_192_cbc = cipher_getter
|
||||
|
||||
def test_bad_encrypt_init_1(self):
|
||||
encrypt_init = envelope.evp.EVP_EncryptInit_ex
|
||||
envelope.evp.EVP_EncryptInit_ex = lambda a,b,c,d,e: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.EVP_EncryptInit_ex = encrypt_init
|
||||
|
||||
def test_bad_encrypt_init_2(self):
|
||||
encrypt_init = envelope.evp.EVP_EncryptInit_ex
|
||||
envelope.evp.EVP_EncryptInit_ex = run_n_times(envelope.evp.EVP_EncryptInit_ex, lambda a,b,c,d,e: None, 1)
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.EVP_EncryptInit_ex = encrypt_init
|
||||
|
||||
def test_bad_encrypt_update(self):
|
||||
encrypt_update = envelope.evp.EVP_EncryptUpdate
|
||||
envelope.evp.EVP_EncryptUpdate = lambda a,b,c,d,e: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.EVP_EncryptUpdate = encrypt_update
|
||||
|
||||
def test_bad_encrypt_final(self):
|
||||
encrypt_final = envelope.evp.EVP_EncryptFinal_ex
|
||||
envelope.evp.EVP_EncryptFinal_ex = lambda a,b,c: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.EVP_EncryptFinal_ex = encrypt_final
|
||||
|
||||
def test_bad_open_init(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
decrypt_init = envelope.evp.EVP_OpenInit
|
||||
envelope.evp.EVP_OpenInit = lambda a,b,c,d,e,f: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, KEY_1[0])
|
||||
envelope.evp.EVP_OpenInit = decrypt_init
|
||||
|
||||
def test_bad_decrypt_update(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
decrypt_update = envelope.evp.EVP_DecryptUpdate
|
||||
envelope.evp.EVP_DecryptUpdate = lambda a,b,c,d,e: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, KEY_1[0])
|
||||
envelope.evp.EVP_DecryptUpdate = decrypt_update
|
||||
|
||||
def test_bad_decrypt_final(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
decrypt_final = envelope.evp.EVP_DecryptFinal_ex
|
||||
envelope.evp.EVP_DecryptFinal_ex = lambda a,b,c: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, KEY_1[0])
|
||||
cipher.evp.EVP_DecryptFinal_ex = decrypt_final
|
||||
|
||||
def test_bad_rsa_get(self):
|
||||
get_rsa = envelope.evp.EVP_PKEY_get1_RSA
|
||||
envelope.evp.EVP_PKEY_get1_RSA = lambda a: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.EVP_PKEY_get1_RSA = get_rsa
|
||||
|
||||
def test_bad_rsa_encrypt(self):
|
||||
encrypt_rsa = envelope.evp.RSA_public_encrypt
|
||||
envelope.evp.RSA_public_encrypt = lambda a,b,c,d,e: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.RSA_public_encrypt = encrypt_rsa
|
||||
|
||||
def test_bad_read_privatekey(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
read_private_key = envelope.evp.PEM_read_PrivateKey
|
||||
envelope.evp.PEM_read_PrivateKey = lambda a,b,c,d: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, KEY_1[0])
|
||||
envelope.evp.PEM_read_PrivateKey = read_private_key
|
||||
|
||||
def test_bad_read_publickey(self):
|
||||
read_public_key = envelope.evp.PEM_read_PUBKEY
|
||||
envelope.evp.PEM_read_PUBKEY = lambda a,b,c,d: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.PEM_read_PUBKEY = read_public_key
|
||||
|
||||
def test_bad_read_bio_privatekey(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
read_private_key = envelope.evp.PEM_read_bio_PrivateKey
|
||||
envelope.evp.PEM_read_bio_PrivateKey = lambda a,b,c,d: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, key=open(KEY_1[0], 'rb').read())
|
||||
envelope.evp.PEM_read_bio_PrivateKey = read_private_key
|
||||
|
||||
def test_bad_read_bio_publickey(self):
|
||||
read_public_key = envelope.evp.PEM_read_bio_PUBKEY
|
||||
envelope.evp.PEM_read_bio_PUBKEY = lambda a,b,c,d: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, key=open(KEY_1[0], 'rb').read())
|
||||
envelope.evp.PEM_read_bio_PUBKEY = read_public_key
|
||||
|
||||
def test_bad_fopen(self):
|
||||
iv, aes_key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
file_open = envelope.evp.fopen
|
||||
envelope.evp.fopen = lambda a,b: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, aes_key, enc, KEY_1[0])
|
||||
envelope.evp.fopen = file_open
|
||||
|
||||
def test_bad_ctx_new(self):
|
||||
iv, aes_key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
new_ctx = envelope.evp.EVP_CIPHER_CTX_new
|
||||
envelope.evp.EVP_CIPHER_CTX_new = lambda: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, aes_key, enc, KEY_1[0])
|
||||
envelope.evp.EVP_CIPHER_CTX_new = new_ctx
|
||||
|
||||
def test_bad_cipher_object(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
cipher_getter = envelope.evp.EVP_aes_192_cbc
|
||||
envelope.evp.EVP_aes_192_cbc= lambda: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, KEY_1[0])
|
||||
envelope.evp.EVP_aes_192_cbc = cipher_getter
|
||||
|
||||
def test_bad_encrypt_init(self):
|
||||
encrypt_init = envelope.evp.EVP_EncryptInit_ex
|
||||
envelope.evp.EVP_EncryptInit_ex = lambda a,b,c,d,e: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.EVP_EncryptInit_ex = encrypt_init
|
||||
|
||||
def test_bad_encrypt_update(self):
|
||||
encrypt_update = envelope.evp.EVP_EncryptUpdate
|
||||
envelope.evp.EVP_EncryptUpdate = lambda a,b,c,d,e: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.EVP_EncryptUpdate = encrypt_update
|
||||
|
||||
def test_bad_encrypt_final(self):
|
||||
encrypt_final = envelope.evp.EVP_EncryptFinal_ex
|
||||
envelope.evp.EVP_EncryptFinal_ex = lambda a,b,c: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.EVP_EncryptFinal_ex = encrypt_final
|
||||
|
||||
def test_bad_open_init(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
decrypt_init = envelope.evp.EVP_OpenInit
|
||||
envelope.evp.EVP_OpenInit = lambda a,b,c,d,e,f: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, KEY_1[0])
|
||||
envelope.evp.EVP_OpenInit = decrypt_init
|
||||
|
||||
def test_bad_decrypt_update(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
decrypt_update = envelope.evp.EVP_DecryptUpdate
|
||||
envelope.evp.EVP_DecryptUpdate = lambda a,b,c,d,e: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, KEY_1[0])
|
||||
envelope.evp.EVP_DecryptUpdate = decrypt_update
|
||||
|
||||
def test_bad_decrypt_final(self):
|
||||
iv, key, enc = envelope.encrypt(SHORT, KEY_1[1])
|
||||
decrypt_final = envelope.evp.EVP_DecryptFinal_ex
|
||||
envelope.evp.EVP_DecryptFinal_ex = lambda a,b,c: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.decrypt, iv, key, enc, KEY_1[0])
|
||||
cipher.evp.EVP_DecryptFinal_ex = decrypt_final
|
||||
|
||||
def test_bad_rsa_get(self):
|
||||
get_rsa = envelope.evp.EVP_PKEY_get1_RSA
|
||||
envelope.evp.EVP_PKEY_get1_RSA = lambda a: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.EVP_PKEY_get1_RSA = get_rsa
|
||||
|
||||
def test_bad_rsa_encrypt(self):
|
||||
encrypt_rsa = envelope.evp.RSA_public_encrypt
|
||||
envelope.evp.RSA_public_encrypt = lambda a,b,c,d,e: None
|
||||
self.assertRaises(envelope.EnvelopeError, envelope.encrypt, SHORT, KEY_1[1])
|
||||
envelope.evp.RSA_public_encrypt = encrypt_rsa
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
399
src/quickstart.py
Executable file
399
src/quickstart.py
Executable file
|
|
@ -0,0 +1,399 @@
|
|||
"""
|
||||
<Program Name>
|
||||
quickstart.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
June 2012. Based on a previous version by Geremy Condra.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
This script acts as a handy quickstart for TUF, helping project and
|
||||
repository maintainers get into the game as quickly and painlessly as
|
||||
possible. 'quickstart.py' creates the metadata files for all the top-level
|
||||
roles (along with their respective cryptographic keys), all of the
|
||||
target files specified by the user, and a configuration file named
|
||||
'config.cfg'. The user may then use the 'signercli' script to modify,
|
||||
if they wish, the basic repository created by 'quickstart.py'.
|
||||
|
||||
If executed successfully, 'quickstart.py' saves the 'repository', 'keystore',
|
||||
and 'client' directories to the current directory. The 'repository' directory
|
||||
should be transferred to the server responding to TUF repository requests.
|
||||
'keystore' and the individual encrypted key files should be securely stored
|
||||
and managed by the repository maintainer; these files will be needed again
|
||||
when modifying the metadata files. 'client' should be initially distributed
|
||||
to users by the software updater utilizing TUF.
|
||||
|
||||
<Usage>
|
||||
$ python quickstart.py --<option> argument
|
||||
|
||||
Examples:
|
||||
$ python quickstart.py --project ./project-files/
|
||||
$ python quickstart.py --project ./project-files/ --verbose 1
|
||||
|
||||
<Options>
|
||||
--verbose:
|
||||
Set the verbosity level of logging messages. Accepts values 1-5.
|
||||
The lower the setting, the greater the verbosity.
|
||||
|
||||
--project:
|
||||
Specify the project directory containing the target files to be
|
||||
served by the TUF repository.
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import getpass
|
||||
import sys
|
||||
import os
|
||||
import optparse
|
||||
import ConfigParser
|
||||
import shutil
|
||||
import tempfile
|
||||
import logging
|
||||
|
||||
import tuf
|
||||
import tuf.repo.signerlib
|
||||
import tuf.repo.keystore
|
||||
import tuf.formats
|
||||
import tuf.util
|
||||
import tuf.log
|
||||
|
||||
# See 'log.py' to learn how logging is handled in TUF.
|
||||
logger = logging.getLogger('tuf')
|
||||
|
||||
# Set the default file names for the top-level roles.
|
||||
# For instance: in 'signerlib.py', ROOT_FILENAME = 'root.txt'.
|
||||
ROOT_FILENAME = tuf.repo.signerlib.ROOT_FILENAME
|
||||
TARGETS_FILENAME = tuf.repo.signerlib.TARGETS_FILENAME
|
||||
RELEASE_FILENAME = tuf.repo.signerlib.RELEASE_FILENAME
|
||||
TIMESTAMP_FILENAME = tuf.repo.signerlib.TIMESTAMP_FILENAME
|
||||
|
||||
# The maximum number of attempts the user has to enter
|
||||
# valid input.
|
||||
MAX_INPUT_ATTEMPTS = 3
|
||||
|
||||
|
||||
def _prompt(message, result_type=str):
|
||||
"""
|
||||
Prompt the user for input by printing 'message', converting
|
||||
the input to 'result_type', and returning the value to the
|
||||
caller.
|
||||
|
||||
"""
|
||||
|
||||
return result_type(raw_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.
|
||||
|
||||
"""
|
||||
|
||||
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 build_repository(project_directory):
|
||||
"""
|
||||
<Purpose>
|
||||
Build a basic TUF repository. All of the required files needed by a
|
||||
repository mirror are created, such as the metadata files of the top-level
|
||||
roles, cryptographic keys, and the directories containing all of the target
|
||||
files.
|
||||
|
||||
<Arguments>
|
||||
project_directory:
|
||||
The directory containing the target files to be copied over to the
|
||||
targets directory of the repository.
|
||||
|
||||
<Exceptions>
|
||||
tuf.RepositoryError, if there was an error building the repository.
|
||||
|
||||
<Side Effects>
|
||||
The repository files created are written to disk to the current
|
||||
working directory.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
# Do the arguments have the correct format?
|
||||
# Raise 'tuf.RepositoryError' if there is a mismatch.
|
||||
try:
|
||||
tuf.formats.PATH_SCHEMA.check_match(project_directory)
|
||||
except tuf.FormatError, e:
|
||||
message = str(e)
|
||||
raise tuf.RepositoryError(message)
|
||||
|
||||
# Verify the 'project_directory' argument.
|
||||
project_directory = os.path.abspath(project_directory)
|
||||
try:
|
||||
tuf.repo.signerlib.check_directory(project_directory)
|
||||
except (tuf.FormatError, tuf.Error), e:
|
||||
message = str(e)
|
||||
raise tuf.RepositoryError(message)
|
||||
|
||||
# Handle the expiration time. The expiration date determines when
|
||||
# the top-level roles expire.
|
||||
message = '\nWhen would you like your certificates to expire? (mm/dd/yyyy): '
|
||||
timeout = None
|
||||
for attempt in range(MAX_INPUT_ATTEMPTS):
|
||||
# Get the difference between the user's entered expiration date and today's
|
||||
# date. Convert and store the difference to total days till expiration.
|
||||
try:
|
||||
input_date = _prompt(message)
|
||||
expiration_date = datetime.datetime.strptime(input_date, '%m/%d/%Y')
|
||||
time_difference = expiration_date - datetime.datetime.now()
|
||||
timeout = time_difference.days
|
||||
if timeout < 1:
|
||||
raise ValueError
|
||||
break
|
||||
except ValueError, e:
|
||||
logger.error('Invalid expiration date entered')
|
||||
timeout = None
|
||||
continue
|
||||
|
||||
# Was a valid value for 'timeout' set?
|
||||
if timeout is None:
|
||||
raise tuf.RepositoryError('Could not get a valid expiration date\n')
|
||||
|
||||
# Build the repository directories.
|
||||
metadata_directory = None
|
||||
targets_directory = None
|
||||
|
||||
# Save the repository directory to the current directory, with
|
||||
# an initial name of 'repository'. The repository maintainer
|
||||
# may opt to rename this directory and should transfer it elsewhere,
|
||||
# such as the webserver that will respond to TUF requests.
|
||||
repository_directory = os.path.join(os.getcwd(), 'repository')
|
||||
|
||||
# Copy the files from the project directory to the repository's targets
|
||||
# directory. The targets directory will hold all the individual
|
||||
# target files.
|
||||
targets_directory = os.path.join(repository_directory, 'targets')
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
temporary_targets = os.path.join(temporary_directory, 'targets')
|
||||
shutil.copytree(project_directory, temporary_targets)
|
||||
|
||||
# Remove the log file created by the tuf logger, if it exists.
|
||||
# It might exist if the current directory was specified as the
|
||||
# project directory on the command-line.
|
||||
log_filename = tuf.log._DEFAULT_LOG_FILENAME
|
||||
if log_filename in os.listdir(temporary_targets):
|
||||
log_file = os.path.join(temporary_targets, log_filename)
|
||||
os.remove(log_file)
|
||||
|
||||
# Try to create the repository directory.
|
||||
try:
|
||||
os.mkdir(repository_directory)
|
||||
# 'OSError' raised if the directory exists.
|
||||
except OSError, e:
|
||||
message = 'Trying to create a new repository over an old repository '+\
|
||||
'installation. Remove '+repr(repository_directory)+' before '+\
|
||||
'trying again.'
|
||||
raise tuf.RepositoryError(message)
|
||||
|
||||
# Move the temporary targets directory into place now that repository
|
||||
# directory has been created.
|
||||
shutil.move(temporary_targets, targets_directory)
|
||||
|
||||
# Try to create the metadata directory that will hold all of the
|
||||
# metadata files, such as 'root.txt' and 'release.txt'.
|
||||
try:
|
||||
metadata_directory = os.path.join(repository_directory, 'metadata')
|
||||
logger.info('Creating '+repr(metadata_directory))
|
||||
os.mkdir(metadata_directory)
|
||||
except OSError, e:
|
||||
pass
|
||||
|
||||
# Set the keystore directory.
|
||||
keystore_directory = os.path.join(os.getcwd(), 'keystore')
|
||||
|
||||
# Try to create the keystore directory.
|
||||
try:
|
||||
os.mkdir(keystore_directory)
|
||||
# 'OSError' raised if the directory exists.
|
||||
except OSError, e:
|
||||
pass
|
||||
|
||||
# Build the keystore and save the generated keys.
|
||||
role_info = {}
|
||||
for role in ['root', 'targets', 'release', 'timestamp']:
|
||||
# Ensure the user inputs a valid threshold value.
|
||||
role_threshold = None
|
||||
for attempt in range(MAX_INPUT_ATTEMPTS):
|
||||
message = '\nEnter the desired threshold for the role '+repr(role)+': '
|
||||
|
||||
# Check for non-integers and values less than one.
|
||||
try:
|
||||
role_threshold = _prompt(message, int)
|
||||
if not tuf.formats.THRESHOLD_SCHEMA.matches(role_threshold):
|
||||
raise ValueError
|
||||
break
|
||||
except ValueError, e:
|
||||
logger.warning('Invalid role threshold entered')
|
||||
role_threshold = None
|
||||
continue
|
||||
|
||||
# Did the user input a valid threshold value?
|
||||
if role_threshold is None:
|
||||
raise tuf.RepositoryError('Could not build the keystore\n')
|
||||
|
||||
# Retrieve the password(s) for 'role', generate the key(s),
|
||||
# and save them to the keystore.
|
||||
for threshold in range(role_threshold):
|
||||
message = 'Enter a password for '+repr(role)+' ('+str(threshold+1)+'): '
|
||||
password = _get_password(message)
|
||||
key = tuf.repo.signerlib.generate_and_save_rsa_key(keystore_directory,
|
||||
password)
|
||||
try:
|
||||
role_info[role]['keyids'].append(key['keyid'])
|
||||
except KeyError:
|
||||
info = {'keyids': [key['keyid']], 'threshold': role_threshold}
|
||||
role_info[role] = info
|
||||
|
||||
# At this point the keystore is built and the 'role_info' dictionary
|
||||
# looks something like this:
|
||||
# {'keyids : [keyid1, keyid2] , 'threshold' : 2}
|
||||
|
||||
# Build the configuration file.
|
||||
config_filepath = tuf.repo.signerlib.build_config_file(repository_directory,
|
||||
timeout, role_info)
|
||||
|
||||
# Generate the 'root.txt' metadata file.
|
||||
root_keyids = role_info['root']['keyids']
|
||||
tuf.repo.signerlib.build_root_file(config_filepath, root_keyids,
|
||||
metadata_directory)
|
||||
|
||||
# Generate the 'targets.txt' metadata file.
|
||||
targets_keyids = role_info['targets']['keyids']
|
||||
tuf.repo.signerlib.build_targets_file(targets_directory, targets_keyids,
|
||||
metadata_directory)
|
||||
|
||||
# Generate the 'release.txt' metadata file.
|
||||
release_keyids = role_info['release']['keyids']
|
||||
tuf.repo.signerlib.build_release_file(release_keyids, metadata_directory)
|
||||
|
||||
# Generate the 'timestamp.txt' metadata file.
|
||||
timestamp_keyids = role_info['timestamp']['keyids']
|
||||
tuf.repo.signerlib.build_timestamp_file(timestamp_keyids, metadata_directory)
|
||||
|
||||
# Generate the 'client' directory containing the metadata of the created
|
||||
# repository. 'tuf.client.updater.py' expects the 'current' and 'previous'
|
||||
# directories to exist under 'metadata'.
|
||||
client_metadata_directory = os.path.join(os.getcwd(), 'client', 'metadata')
|
||||
try:
|
||||
os.makedirs(client_metadata_directory)
|
||||
except OSError, e:
|
||||
message = 'Cannot create a fresh client metadata directory: '+\
|
||||
repr(client_metadata_directory)+'. The client metadata '+\
|
||||
'will need to be manually created. See the README file.'
|
||||
logger.warn(message)
|
||||
|
||||
# Move the metadata to the client's 'current' and 'previous' directories.
|
||||
client_current = os.path.join(client_metadata_directory, 'current')
|
||||
client_previous = os.path.join(client_metadata_directory, 'previous')
|
||||
shutil.copytree(metadata_directory, client_current)
|
||||
shutil.copytree(metadata_directory, client_previous)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def parse_options():
|
||||
"""
|
||||
<Purpose>
|
||||
Parse the command-line options and set the logging level,
|
||||
as specified by the user using the '--verbose' option.
|
||||
The user must also set the '--project' option. If unset,
|
||||
the current directory is used as the location of the project
|
||||
files. The project files are copied by 'quickstart.py' and
|
||||
saved to the repository's targets directory.
|
||||
|
||||
<Arguments>
|
||||
None.
|
||||
|
||||
<Exceptions>
|
||||
None.
|
||||
|
||||
<Side Effects>
|
||||
Sets the logging level for TUF logging.
|
||||
|
||||
<Returns>
|
||||
The 'options.PROJECT_DIRECTORY' string.
|
||||
|
||||
"""
|
||||
|
||||
parser = optparse.OptionParser()
|
||||
|
||||
# Add the options supported by 'quickstart' to the option parser.
|
||||
parser.add_option('--verbose', dest='VERBOSE', type=int, default=3,
|
||||
help='Set the verbosity level of logging messages.'
|
||||
'The lower the setting, the greater the verbosity.')
|
||||
|
||||
parser.add_option('--project', dest='PROJECT_DIRECTORY', type='string',
|
||||
default='.', help='Specify the directory containing the '
|
||||
'project files to host on the TUF repository.')
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
# Set the logging level.
|
||||
if options.VERBOSE == 5:
|
||||
tuf.log.set_log_level(logging.CRITICAL)
|
||||
elif options.VERBOSE == 4:
|
||||
tuf.log.set_log_level(logging.ERROR)
|
||||
elif options.VERBOSE == 3:
|
||||
tuf.log.set_log_level(logging.WARNING)
|
||||
elif options.VERBOSE == 2:
|
||||
tuf.log.set_log_level(logging.INFO)
|
||||
elif options.VERBOSE == 1:
|
||||
tuf.log.set_log_level(logging.DEBUG)
|
||||
else:
|
||||
tuf.log.set_log_level(logging.NOTSET)
|
||||
|
||||
# Return the directory containing the project files. These files
|
||||
# are copied over to the targets directory of the repository.
|
||||
return options.PROJECT_DIRECTORY
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse the options and set the logging level.
|
||||
project_directory = parse_options()
|
||||
|
||||
# Build the repository. The top-level metadata files, cryptographic keys,
|
||||
# target files, and the configuration file are created.
|
||||
try:
|
||||
build_repository(project_directory)
|
||||
except tuf.RepositoryError, e:
|
||||
sys.stderr.write(str(e)+'\n')
|
||||
sys.exit(1)
|
||||
|
||||
print '\nSuccessfully created the repository.'
|
||||
sys.exit(0)
|
||||
17
src/setup.py
Executable file
17
src/setup.py
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
setup(name='TUF',
|
||||
version='0.0',
|
||||
description='A secure updater framework for Python',
|
||||
author='lots of people',
|
||||
url='https://updateframework.com',
|
||||
packages=['tuf',
|
||||
'tuf.repo',
|
||||
'tuf.client',
|
||||
'tuf.pushtools',
|
||||
'tuf.pushtools.transfer',
|
||||
'simplejson'],
|
||||
scripts=['quickstart.py', 'tuf/pushtools/push.py', 'tuf/pushtools/receivetools/receive.py', 'tuf/repo/signercli.py']
|
||||
)
|
||||
437
src/simplejson/__init__.py
Executable file
437
src/simplejson/__init__.py
Executable file
|
|
@ -0,0 +1,437 @@
|
|||
r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
|
||||
JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
|
||||
interchange format.
|
||||
|
||||
:mod:`simplejson` exposes an API familiar to users of the standard library
|
||||
:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
|
||||
version of the :mod:`json` library contained in Python 2.6, but maintains
|
||||
compatibility with Python 2.4 and Python 2.5 and (currently) has
|
||||
significant performance advantages, even without using the optional C
|
||||
extension for speedups.
|
||||
|
||||
Encoding basic Python object hierarchies::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
|
||||
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
|
||||
>>> print json.dumps("\"foo\bar")
|
||||
"\"foo\bar"
|
||||
>>> print json.dumps(u'\u1234')
|
||||
"\u1234"
|
||||
>>> print json.dumps('\\')
|
||||
"\\"
|
||||
>>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
|
||||
{"a": 0, "b": 0, "c": 0}
|
||||
>>> from StringIO import StringIO
|
||||
>>> io = StringIO()
|
||||
>>> json.dump(['streaming API'], io)
|
||||
>>> io.getvalue()
|
||||
'["streaming API"]'
|
||||
|
||||
Compact encoding::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
|
||||
'[1,2,3,{"4":5,"6":7}]'
|
||||
|
||||
Pretty printing::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=' ')
|
||||
>>> print '\n'.join([l.rstrip() for l in s.splitlines()])
|
||||
{
|
||||
"4": 5,
|
||||
"6": 7
|
||||
}
|
||||
|
||||
Decoding JSON::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
|
||||
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
|
||||
True
|
||||
>>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
|
||||
True
|
||||
>>> from StringIO import StringIO
|
||||
>>> io = StringIO('["streaming API"]')
|
||||
>>> json.load(io)[0] == 'streaming API'
|
||||
True
|
||||
|
||||
Specializing JSON object decoding::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> def as_complex(dct):
|
||||
... if '__complex__' in dct:
|
||||
... return complex(dct['real'], dct['imag'])
|
||||
... return dct
|
||||
...
|
||||
>>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
|
||||
... object_hook=as_complex)
|
||||
(1+2j)
|
||||
>>> from decimal import Decimal
|
||||
>>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1')
|
||||
True
|
||||
|
||||
Specializing JSON object encoding::
|
||||
|
||||
>>> import simplejson as json
|
||||
>>> def encode_complex(obj):
|
||||
... if isinstance(obj, complex):
|
||||
... return [obj.real, obj.imag]
|
||||
... raise TypeError(repr(o) + " is not JSON serializable")
|
||||
...
|
||||
>>> json.dumps(2 + 1j, default=encode_complex)
|
||||
'[2.0, 1.0]'
|
||||
>>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
|
||||
'[2.0, 1.0]'
|
||||
>>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
|
||||
'[2.0, 1.0]'
|
||||
|
||||
|
||||
Using simplejson.tool from the shell to validate and pretty-print::
|
||||
|
||||
$ echo '{"json":"obj"}' | python -m simplejson.tool
|
||||
{
|
||||
"json": "obj"
|
||||
}
|
||||
$ echo '{ 1.2:3.4}' | python -m simplejson.tool
|
||||
Expecting property name: line 1 column 2 (char 2)
|
||||
"""
|
||||
__version__ = '2.1.1'
|
||||
__all__ = [
|
||||
'dump', 'dumps', 'load', 'loads',
|
||||
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
|
||||
'OrderedDict',
|
||||
]
|
||||
|
||||
__author__ = 'Bob Ippolito <bob@redivi.com>'
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from decoder import JSONDecoder, JSONDecodeError
|
||||
from encoder import JSONEncoder
|
||||
def _import_OrderedDict():
|
||||
import collections
|
||||
try:
|
||||
return collections.OrderedDict
|
||||
except AttributeError:
|
||||
import ordered_dict
|
||||
return ordered_dict.OrderedDict
|
||||
OrderedDict = _import_OrderedDict()
|
||||
|
||||
def _import_c_make_encoder():
|
||||
try:
|
||||
from simplejson._speedups import make_encoder
|
||||
return make_encoder
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
_default_encoder = JSONEncoder(
|
||||
skipkeys=False,
|
||||
ensure_ascii=True,
|
||||
check_circular=True,
|
||||
allow_nan=True,
|
||||
indent=None,
|
||||
separators=None,
|
||||
encoding='utf-8',
|
||||
default=None,
|
||||
use_decimal=False,
|
||||
)
|
||||
|
||||
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
|
||||
allow_nan=True, cls=None, indent=None, separators=None,
|
||||
encoding='utf-8', default=None, use_decimal=False, **kw):
|
||||
"""Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
|
||||
``.write()``-supporting file-like object).
|
||||
|
||||
If ``skipkeys`` is true then ``dict`` keys that are not basic types
|
||||
(``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
|
||||
will be skipped instead of raising a ``TypeError``.
|
||||
|
||||
If ``ensure_ascii`` is false, then the some chunks written to ``fp``
|
||||
may be ``unicode`` instances, subject to normal Python ``str`` to
|
||||
``unicode`` coercion rules. Unless ``fp.write()`` explicitly
|
||||
understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
|
||||
to cause an error.
|
||||
|
||||
If ``check_circular`` is false, then the circular reference check
|
||||
for container types will be skipped and a circular reference will
|
||||
result in an ``OverflowError`` (or worse).
|
||||
|
||||
If ``allow_nan`` is false, then it will be a ``ValueError`` to
|
||||
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
|
||||
in strict compliance of the JSON specification, instead of using the
|
||||
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
|
||||
|
||||
If *indent* is a string, then JSON array elements and object members
|
||||
will be pretty-printed with a newline followed by that string repeated
|
||||
for each level of nesting. ``None`` (the default) selects the most compact
|
||||
representation without any newlines. For backwards compatibility with
|
||||
versions of simplejson earlier than 2.1.0, an integer is also accepted
|
||||
and is converted to a string with that many spaces.
|
||||
|
||||
If ``separators`` is an ``(item_separator, dict_separator)`` tuple
|
||||
then it will be used instead of the default ``(', ', ': ')`` separators.
|
||||
``(',', ':')`` is the most compact JSON representation.
|
||||
|
||||
``encoding`` is the character encoding for str instances, default is UTF-8.
|
||||
|
||||
``default(obj)`` is a function that should return a serializable version
|
||||
of obj or raise TypeError. The default simply raises TypeError.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then decimal.Decimal
|
||||
will be natively serialized to JSON with full precision.
|
||||
|
||||
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
|
||||
``.default()`` method to serialize additional types), specify it with
|
||||
the ``cls`` kwarg.
|
||||
|
||||
"""
|
||||
# cached encoder
|
||||
if (not skipkeys and ensure_ascii and
|
||||
check_circular and allow_nan and
|
||||
cls is None and indent is None and separators is None and
|
||||
encoding == 'utf-8' and default is None and not kw):
|
||||
iterable = _default_encoder.iterencode(obj)
|
||||
else:
|
||||
if cls is None:
|
||||
cls = JSONEncoder
|
||||
iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
|
||||
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
|
||||
separators=separators, encoding=encoding,
|
||||
default=default, use_decimal=use_decimal, **kw).iterencode(obj)
|
||||
# could accelerate with writelines in some versions of Python, at
|
||||
# a debuggability cost
|
||||
for chunk in iterable:
|
||||
fp.write(chunk)
|
||||
|
||||
|
||||
def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
|
||||
allow_nan=True, cls=None, indent=None, separators=None,
|
||||
encoding='utf-8', default=None, use_decimal=False, **kw):
|
||||
"""Serialize ``obj`` to a JSON formatted ``str``.
|
||||
|
||||
If ``skipkeys`` is false then ``dict`` keys that are not basic types
|
||||
(``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
|
||||
will be skipped instead of raising a ``TypeError``.
|
||||
|
||||
If ``ensure_ascii`` is false, then the return value will be a
|
||||
``unicode`` instance subject to normal Python ``str`` to ``unicode``
|
||||
coercion rules instead of being escaped to an ASCII ``str``.
|
||||
|
||||
If ``check_circular`` is false, then the circular reference check
|
||||
for container types will be skipped and a circular reference will
|
||||
result in an ``OverflowError`` (or worse).
|
||||
|
||||
If ``allow_nan`` is false, then it will be a ``ValueError`` to
|
||||
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
|
||||
strict compliance of the JSON specification, instead of using the
|
||||
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
|
||||
|
||||
If ``indent`` is a string, then JSON array elements and object members
|
||||
will be pretty-printed with a newline followed by that string repeated
|
||||
for each level of nesting. ``None`` (the default) selects the most compact
|
||||
representation without any newlines. For backwards compatibility with
|
||||
versions of simplejson earlier than 2.1.0, an integer is also accepted
|
||||
and is converted to a string with that many spaces.
|
||||
|
||||
If ``separators`` is an ``(item_separator, dict_separator)`` tuple
|
||||
then it will be used instead of the default ``(', ', ': ')`` separators.
|
||||
``(',', ':')`` is the most compact JSON representation.
|
||||
|
||||
``encoding`` is the character encoding for str instances, default is UTF-8.
|
||||
|
||||
``default(obj)`` is a function that should return a serializable version
|
||||
of obj or raise TypeError. The default simply raises TypeError.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then decimal.Decimal
|
||||
will be natively serialized to JSON with full precision.
|
||||
|
||||
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
|
||||
``.default()`` method to serialize additional types), specify it with
|
||||
the ``cls`` kwarg.
|
||||
|
||||
"""
|
||||
# cached encoder
|
||||
if (not skipkeys and ensure_ascii and
|
||||
check_circular and allow_nan and
|
||||
cls is None and indent is None and separators is None and
|
||||
encoding == 'utf-8' and default is None and not use_decimal
|
||||
and not kw):
|
||||
return _default_encoder.encode(obj)
|
||||
if cls is None:
|
||||
cls = JSONEncoder
|
||||
return cls(
|
||||
skipkeys=skipkeys, ensure_ascii=ensure_ascii,
|
||||
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
|
||||
separators=separators, encoding=encoding, default=default,
|
||||
use_decimal=use_decimal, **kw).encode(obj)
|
||||
|
||||
|
||||
_default_decoder = JSONDecoder(encoding=None, object_hook=None,
|
||||
object_pairs_hook=None)
|
||||
|
||||
|
||||
def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
|
||||
parse_int=None, parse_constant=None, object_pairs_hook=None,
|
||||
use_decimal=False, **kw):
|
||||
"""Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
|
||||
a JSON document) to a Python object.
|
||||
|
||||
*encoding* determines the encoding used to interpret any
|
||||
:class:`str` objects decoded by this instance (``'utf-8'`` by
|
||||
default). It has no effect when decoding :class:`unicode` objects.
|
||||
|
||||
Note that currently only encodings that are a superset of ASCII work,
|
||||
strings of other encodings should be passed in as :class:`unicode`.
|
||||
|
||||
*object_hook*, if specified, will be called with the result of every
|
||||
JSON object decoded and its return value will be used in place of the
|
||||
given :class:`dict`. This can be used to provide custom
|
||||
deserializations (e.g. to support JSON-RPC class hinting).
|
||||
|
||||
*object_pairs_hook* is an optional function that will be called with
|
||||
the result of any object literal decode with an ordered list of pairs.
|
||||
The return value of *object_pairs_hook* will be used instead of the
|
||||
:class:`dict`. This feature can be used to implement custom decoders
|
||||
that rely on the order that the key and value pairs are decoded (for
|
||||
example, :func:`collections.OrderedDict` will remember the order of
|
||||
insertion). If *object_hook* is also defined, the *object_pairs_hook*
|
||||
takes priority.
|
||||
|
||||
*parse_float*, if specified, will be called with the string of every
|
||||
JSON float to be decoded. By default, this is equivalent to
|
||||
``float(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON floats (e.g. :class:`decimal.Decimal`).
|
||||
|
||||
*parse_int*, if specified, will be called with the string of every
|
||||
JSON int to be decoded. By default, this is equivalent to
|
||||
``int(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON integers (e.g. :class:`float`).
|
||||
|
||||
*parse_constant*, if specified, will be called with one of the
|
||||
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
|
||||
can be used to raise an exception if invalid JSON numbers are
|
||||
encountered.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then it implies
|
||||
parse_float=decimal.Decimal for parity with ``dump``.
|
||||
|
||||
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
|
||||
kwarg.
|
||||
|
||||
"""
|
||||
return loads(fp.read(),
|
||||
encoding=encoding, cls=cls, object_hook=object_hook,
|
||||
parse_float=parse_float, parse_int=parse_int,
|
||||
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook,
|
||||
use_decimal=use_decimal, **kw)
|
||||
|
||||
|
||||
def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
|
||||
parse_int=None, parse_constant=None, object_pairs_hook=None,
|
||||
use_decimal=False, **kw):
|
||||
"""Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
|
||||
document) to a Python object.
|
||||
|
||||
*encoding* determines the encoding used to interpret any
|
||||
:class:`str` objects decoded by this instance (``'utf-8'`` by
|
||||
default). It has no effect when decoding :class:`unicode` objects.
|
||||
|
||||
Note that currently only encodings that are a superset of ASCII work,
|
||||
strings of other encodings should be passed in as :class:`unicode`.
|
||||
|
||||
*object_hook*, if specified, will be called with the result of every
|
||||
JSON object decoded and its return value will be used in place of the
|
||||
given :class:`dict`. This can be used to provide custom
|
||||
deserializations (e.g. to support JSON-RPC class hinting).
|
||||
|
||||
*object_pairs_hook* is an optional function that will be called with
|
||||
the result of any object literal decode with an ordered list of pairs.
|
||||
The return value of *object_pairs_hook* will be used instead of the
|
||||
:class:`dict`. This feature can be used to implement custom decoders
|
||||
that rely on the order that the key and value pairs are decoded (for
|
||||
example, :func:`collections.OrderedDict` will remember the order of
|
||||
insertion). If *object_hook* is also defined, the *object_pairs_hook*
|
||||
takes priority.
|
||||
|
||||
*parse_float*, if specified, will be called with the string of every
|
||||
JSON float to be decoded. By default, this is equivalent to
|
||||
``float(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON floats (e.g. :class:`decimal.Decimal`).
|
||||
|
||||
*parse_int*, if specified, will be called with the string of every
|
||||
JSON int to be decoded. By default, this is equivalent to
|
||||
``int(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON integers (e.g. :class:`float`).
|
||||
|
||||
*parse_constant*, if specified, will be called with one of the
|
||||
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
|
||||
can be used to raise an exception if invalid JSON numbers are
|
||||
encountered.
|
||||
|
||||
If *use_decimal* is true (default: ``False``) then it implies
|
||||
parse_float=decimal.Decimal for parity with ``dump``.
|
||||
|
||||
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
|
||||
kwarg.
|
||||
|
||||
"""
|
||||
if (cls is None and encoding is None and object_hook is None and
|
||||
parse_int is None and parse_float is None and
|
||||
parse_constant is None and object_pairs_hook is None
|
||||
and not use_decimal and not kw):
|
||||
return _default_decoder.decode(s)
|
||||
if cls is None:
|
||||
cls = JSONDecoder
|
||||
if object_hook is not None:
|
||||
kw['object_hook'] = object_hook
|
||||
if object_pairs_hook is not None:
|
||||
kw['object_pairs_hook'] = object_pairs_hook
|
||||
if parse_float is not None:
|
||||
kw['parse_float'] = parse_float
|
||||
if parse_int is not None:
|
||||
kw['parse_int'] = parse_int
|
||||
if parse_constant is not None:
|
||||
kw['parse_constant'] = parse_constant
|
||||
if use_decimal:
|
||||
if parse_float is not None:
|
||||
raise TypeError("use_decimal=True implies parse_float=Decimal")
|
||||
kw['parse_float'] = Decimal
|
||||
return cls(encoding=encoding, **kw).decode(s)
|
||||
|
||||
|
||||
def _toggle_speedups(enabled):
|
||||
import simplejson.decoder as dec
|
||||
import simplejson.encoder as enc
|
||||
import simplejson.scanner as scan
|
||||
c_make_encoder = _import_c_make_encoder()
|
||||
if enabled:
|
||||
dec.scanstring = dec.c_scanstring or dec.py_scanstring
|
||||
enc.c_make_encoder = c_make_encoder
|
||||
enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or
|
||||
enc.py_encode_basestring_ascii)
|
||||
scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner
|
||||
else:
|
||||
dec.scanstring = dec.py_scanstring
|
||||
enc.c_make_encoder = None
|
||||
enc.encode_basestring_ascii = enc.py_encode_basestring_ascii
|
||||
scan.make_scanner = scan.py_make_scanner
|
||||
dec.make_scanner = scan.make_scanner
|
||||
global _default_decoder
|
||||
_default_decoder = JSONDecoder(
|
||||
encoding=None,
|
||||
object_hook=None,
|
||||
object_pairs_hook=None,
|
||||
)
|
||||
global _default_encoder
|
||||
_default_encoder = JSONEncoder(
|
||||
skipkeys=False,
|
||||
ensure_ascii=True,
|
||||
check_circular=True,
|
||||
allow_nan=True,
|
||||
indent=None,
|
||||
separators=None,
|
||||
encoding='utf-8',
|
||||
default=None,
|
||||
)
|
||||
BIN
src/simplejson/__init__.pyc
Executable file
BIN
src/simplejson/__init__.pyc
Executable file
Binary file not shown.
2561
src/simplejson/_speedups.c
Executable file
2561
src/simplejson/_speedups.c
Executable file
File diff suppressed because it is too large
Load diff
421
src/simplejson/decoder.py
Executable file
421
src/simplejson/decoder.py
Executable file
|
|
@ -0,0 +1,421 @@
|
|||
"""Implementation of JSONDecoder
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import struct
|
||||
|
||||
from simplejson.scanner import make_scanner
|
||||
def _import_c_scanstring():
|
||||
try:
|
||||
from simplejson._speedups import scanstring
|
||||
return scanstring
|
||||
except ImportError:
|
||||
return None
|
||||
c_scanstring = _import_c_scanstring()
|
||||
|
||||
__all__ = ['JSONDecoder']
|
||||
|
||||
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
|
||||
|
||||
def _floatconstants():
|
||||
_BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
|
||||
# The struct module in Python 2.4 would get frexp() out of range here
|
||||
# when an endian is specified in the format string. Fixed in Python 2.5+
|
||||
if sys.byteorder != 'big':
|
||||
_BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
|
||||
nan, inf = struct.unpack('dd', _BYTES)
|
||||
return nan, inf, -inf
|
||||
|
||||
NaN, PosInf, NegInf = _floatconstants()
|
||||
|
||||
|
||||
class JSONDecodeError(ValueError):
|
||||
"""Subclass of ValueError with the following additional properties:
|
||||
|
||||
msg: The unformatted error message
|
||||
doc: The JSON document being parsed
|
||||
pos: The start index of doc where parsing failed
|
||||
end: The end index of doc where parsing failed (may be None)
|
||||
lineno: The line corresponding to pos
|
||||
colno: The column corresponding to pos
|
||||
endlineno: The line corresponding to end (may be None)
|
||||
endcolno: The column corresponding to end (may be None)
|
||||
|
||||
"""
|
||||
def __init__(self, msg, doc, pos, end=None):
|
||||
ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
|
||||
self.msg = msg
|
||||
self.doc = doc
|
||||
self.pos = pos
|
||||
self.end = end
|
||||
self.lineno, self.colno = linecol(doc, pos)
|
||||
if end is not None:
|
||||
self.endlineno, self.endcolno = linecol(doc, pos)
|
||||
else:
|
||||
self.endlineno, self.endcolno = None, None
|
||||
|
||||
|
||||
def linecol(doc, pos):
|
||||
lineno = doc.count('\n', 0, pos) + 1
|
||||
if lineno == 1:
|
||||
colno = pos
|
||||
else:
|
||||
colno = pos - doc.rindex('\n', 0, pos)
|
||||
return lineno, colno
|
||||
|
||||
|
||||
def errmsg(msg, doc, pos, end=None):
|
||||
# Note that this function is called from _speedups
|
||||
lineno, colno = linecol(doc, pos)
|
||||
if end is None:
|
||||
#fmt = '{0}: line {1} column {2} (char {3})'
|
||||
#return fmt.format(msg, lineno, colno, pos)
|
||||
fmt = '%s: line %d column %d (char %d)'
|
||||
return fmt % (msg, lineno, colno, pos)
|
||||
endlineno, endcolno = linecol(doc, end)
|
||||
#fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
|
||||
#return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
|
||||
fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
|
||||
return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
|
||||
|
||||
|
||||
_CONSTANTS = {
|
||||
'-Infinity': NegInf,
|
||||
'Infinity': PosInf,
|
||||
'NaN': NaN,
|
||||
}
|
||||
|
||||
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
|
||||
BACKSLASH = {
|
||||
'"': u'"', '\\': u'\\', '/': u'/',
|
||||
'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
|
||||
}
|
||||
|
||||
DEFAULT_ENCODING = "utf-8"
|
||||
|
||||
def py_scanstring(s, end, encoding=None, strict=True,
|
||||
_b=BACKSLASH, _m=STRINGCHUNK.match):
|
||||
"""Scan the string s for a JSON string. End is the index of the
|
||||
character in s after the quote that started the JSON string.
|
||||
Unescapes all valid JSON string escape sequences and raises ValueError
|
||||
on attempt to decode an invalid string. If strict is False then literal
|
||||
control characters are allowed in the string.
|
||||
|
||||
Returns a tuple of the decoded string and the index of the character in s
|
||||
after the end quote."""
|
||||
if encoding is None:
|
||||
encoding = DEFAULT_ENCODING
|
||||
chunks = []
|
||||
_append = chunks.append
|
||||
begin = end - 1
|
||||
while 1:
|
||||
chunk = _m(s, end)
|
||||
if chunk is None:
|
||||
raise JSONDecodeError(
|
||||
"Unterminated string starting at", s, begin)
|
||||
end = chunk.end()
|
||||
content, terminator = chunk.groups()
|
||||
# Content is contains zero or more unescaped string characters
|
||||
if content:
|
||||
if not isinstance(content, unicode):
|
||||
content = unicode(content, encoding)
|
||||
_append(content)
|
||||
# Terminator is the end of string, a literal control character,
|
||||
# or a backslash denoting that an escape sequence follows
|
||||
if terminator == '"':
|
||||
break
|
||||
elif terminator != '\\':
|
||||
if strict:
|
||||
msg = "Invalid control character %r at" % (terminator,)
|
||||
#msg = "Invalid control character {0!r} at".format(terminator)
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
else:
|
||||
_append(terminator)
|
||||
continue
|
||||
try:
|
||||
esc = s[end]
|
||||
except IndexError:
|
||||
raise JSONDecodeError(
|
||||
"Unterminated string starting at", s, begin)
|
||||
# If not a unicode escape sequence, must be in the lookup table
|
||||
if esc != 'u':
|
||||
try:
|
||||
char = _b[esc]
|
||||
except KeyError:
|
||||
msg = "Invalid \\escape: " + repr(esc)
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
end += 1
|
||||
else:
|
||||
# Unicode escape sequence
|
||||
esc = s[end + 1:end + 5]
|
||||
next_end = end + 5
|
||||
if len(esc) != 4:
|
||||
msg = "Invalid \\uXXXX escape"
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
uni = int(esc, 16)
|
||||
# Check for surrogate pair on UCS-4 systems
|
||||
if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
|
||||
msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
|
||||
if not s[end + 5:end + 7] == '\\u':
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
esc2 = s[end + 7:end + 11]
|
||||
if len(esc2) != 4:
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
uni2 = int(esc2, 16)
|
||||
uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
|
||||
next_end += 6
|
||||
char = unichr(uni)
|
||||
end = next_end
|
||||
# Append the unescaped character
|
||||
_append(char)
|
||||
return u''.join(chunks), end
|
||||
|
||||
|
||||
# Use speedup if available
|
||||
scanstring = c_scanstring or py_scanstring
|
||||
|
||||
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
|
||||
WHITESPACE_STR = ' \t\n\r'
|
||||
|
||||
def JSONObject((s, end), encoding, strict, scan_once, object_hook,
|
||||
object_pairs_hook, memo=None,
|
||||
_w=WHITESPACE.match, _ws=WHITESPACE_STR):
|
||||
# Backwards compatibility
|
||||
if memo is None:
|
||||
memo = {}
|
||||
memo_get = memo.setdefault
|
||||
pairs = []
|
||||
# Use a slice to prevent IndexError from being raised, the following
|
||||
# check will raise a more specific ValueError if the string is empty
|
||||
nextchar = s[end:end + 1]
|
||||
# Normally we expect nextchar == '"'
|
||||
if nextchar != '"':
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end).end()
|
||||
nextchar = s[end:end + 1]
|
||||
# Trivial empty object
|
||||
if nextchar == '}':
|
||||
if object_pairs_hook is not None:
|
||||
result = object_pairs_hook(pairs)
|
||||
return result, end
|
||||
pairs = {}
|
||||
if object_hook is not None:
|
||||
pairs = object_hook(pairs)
|
||||
return pairs, end + 1
|
||||
elif nextchar != '"':
|
||||
raise JSONDecodeError("Expecting property name", s, end)
|
||||
end += 1
|
||||
while True:
|
||||
key, end = scanstring(s, end, encoding, strict)
|
||||
key = memo_get(key, key)
|
||||
|
||||
# To skip some function call overhead we optimize the fast paths where
|
||||
# the JSON key separator is ": " or just ":".
|
||||
if s[end:end + 1] != ':':
|
||||
end = _w(s, end).end()
|
||||
if s[end:end + 1] != ':':
|
||||
raise JSONDecodeError("Expecting : delimiter", s, end)
|
||||
|
||||
end += 1
|
||||
|
||||
try:
|
||||
if s[end] in _ws:
|
||||
end += 1
|
||||
if s[end] in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
try:
|
||||
value, end = scan_once(s, end)
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("Expecting object", s, end)
|
||||
pairs.append((key, value))
|
||||
|
||||
try:
|
||||
nextchar = s[end]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end]
|
||||
except IndexError:
|
||||
nextchar = ''
|
||||
end += 1
|
||||
|
||||
if nextchar == '}':
|
||||
break
|
||||
elif nextchar != ',':
|
||||
raise JSONDecodeError("Expecting , delimiter", s, end - 1)
|
||||
|
||||
try:
|
||||
nextchar = s[end]
|
||||
if nextchar in _ws:
|
||||
end += 1
|
||||
nextchar = s[end]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end]
|
||||
except IndexError:
|
||||
nextchar = ''
|
||||
|
||||
end += 1
|
||||
if nextchar != '"':
|
||||
raise JSONDecodeError("Expecting property name", s, end - 1)
|
||||
|
||||
if object_pairs_hook is not None:
|
||||
result = object_pairs_hook(pairs)
|
||||
return result, end
|
||||
pairs = dict(pairs)
|
||||
if object_hook is not None:
|
||||
pairs = object_hook(pairs)
|
||||
return pairs, end
|
||||
|
||||
def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
|
||||
values = []
|
||||
nextchar = s[end:end + 1]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end:end + 1]
|
||||
# Look-ahead for trivial empty array
|
||||
if nextchar == ']':
|
||||
return values, end + 1
|
||||
_append = values.append
|
||||
while True:
|
||||
try:
|
||||
value, end = scan_once(s, end)
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("Expecting object", s, end)
|
||||
_append(value)
|
||||
nextchar = s[end:end + 1]
|
||||
if nextchar in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
nextchar = s[end:end + 1]
|
||||
end += 1
|
||||
if nextchar == ']':
|
||||
break
|
||||
elif nextchar != ',':
|
||||
raise JSONDecodeError("Expecting , delimiter", s, end)
|
||||
|
||||
try:
|
||||
if s[end] in _ws:
|
||||
end += 1
|
||||
if s[end] in _ws:
|
||||
end = _w(s, end + 1).end()
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
return values, end
|
||||
|
||||
class JSONDecoder(object):
|
||||
"""Simple JSON <http://json.org> decoder
|
||||
|
||||
Performs the following translations in decoding by default:
|
||||
|
||||
+---------------+-------------------+
|
||||
| JSON | Python |
|
||||
+===============+===================+
|
||||
| object | dict |
|
||||
+---------------+-------------------+
|
||||
| array | list |
|
||||
+---------------+-------------------+
|
||||
| string | unicode |
|
||||
+---------------+-------------------+
|
||||
| number (int) | int, long |
|
||||
+---------------+-------------------+
|
||||
| number (real) | float |
|
||||
+---------------+-------------------+
|
||||
| true | True |
|
||||
+---------------+-------------------+
|
||||
| false | False |
|
||||
+---------------+-------------------+
|
||||
| null | None |
|
||||
+---------------+-------------------+
|
||||
|
||||
It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
|
||||
their corresponding ``float`` values, which is outside the JSON spec.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, encoding=None, object_hook=None, parse_float=None,
|
||||
parse_int=None, parse_constant=None, strict=True,
|
||||
object_pairs_hook=None):
|
||||
"""
|
||||
*encoding* determines the encoding used to interpret any
|
||||
:class:`str` objects decoded by this instance (``'utf-8'`` by
|
||||
default). It has no effect when decoding :class:`unicode` objects.
|
||||
|
||||
Note that currently only encodings that are a superset of ASCII work,
|
||||
strings of other encodings should be passed in as :class:`unicode`.
|
||||
|
||||
*object_hook*, if specified, will be called with the result of every
|
||||
JSON object decoded and its return value will be used in place of the
|
||||
given :class:`dict`. This can be used to provide custom
|
||||
deserializations (e.g. to support JSON-RPC class hinting).
|
||||
|
||||
*object_pairs_hook* is an optional function that will be called with
|
||||
the result of any object literal decode with an ordered list of pairs.
|
||||
The return value of *object_pairs_hook* will be used instead of the
|
||||
:class:`dict`. This feature can be used to implement custom decoders
|
||||
that rely on the order that the key and value pairs are decoded (for
|
||||
example, :func:`collections.OrderedDict` will remember the order of
|
||||
insertion). If *object_hook* is also defined, the *object_pairs_hook*
|
||||
takes priority.
|
||||
|
||||
*parse_float*, if specified, will be called with the string of every
|
||||
JSON float to be decoded. By default, this is equivalent to
|
||||
``float(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON floats (e.g. :class:`decimal.Decimal`).
|
||||
|
||||
*parse_int*, if specified, will be called with the string of every
|
||||
JSON int to be decoded. By default, this is equivalent to
|
||||
``int(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON integers (e.g. :class:`float`).
|
||||
|
||||
*parse_constant*, if specified, will be called with one of the
|
||||
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
|
||||
can be used to raise an exception if invalid JSON numbers are
|
||||
encountered.
|
||||
|
||||
*strict* controls the parser's behavior when it encounters an
|
||||
invalid control character in a string. The default setting of
|
||||
``True`` means that unescaped control characters are parse errors, if
|
||||
``False`` then control characters will be allowed in strings.
|
||||
|
||||
"""
|
||||
self.encoding = encoding
|
||||
self.object_hook = object_hook
|
||||
self.object_pairs_hook = object_pairs_hook
|
||||
self.parse_float = parse_float or float
|
||||
self.parse_int = parse_int or int
|
||||
self.parse_constant = parse_constant or _CONSTANTS.__getitem__
|
||||
self.strict = strict
|
||||
self.parse_object = JSONObject
|
||||
self.parse_array = JSONArray
|
||||
self.parse_string = scanstring
|
||||
self.memo = {}
|
||||
self.scan_once = make_scanner(self)
|
||||
|
||||
def decode(self, s, _w=WHITESPACE.match):
|
||||
"""Return the Python representation of ``s`` (a ``str`` or ``unicode``
|
||||
instance containing a JSON document)
|
||||
|
||||
"""
|
||||
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
|
||||
end = _w(s, end).end()
|
||||
if end != len(s):
|
||||
raise JSONDecodeError("Extra data", s, end, len(s))
|
||||
return obj
|
||||
|
||||
def raw_decode(self, s, idx=0):
|
||||
"""Decode a JSON document from ``s`` (a ``str`` or ``unicode``
|
||||
beginning with a JSON document) and return a 2-tuple of the Python
|
||||
representation and the index in ``s`` where the document ended.
|
||||
|
||||
This can be used to decode a JSON document from a string that may
|
||||
have extraneous data at the end.
|
||||
|
||||
"""
|
||||
try:
|
||||
obj, end = self.scan_once(s, idx)
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("No JSON object could be decoded", s, idx)
|
||||
return obj, end
|
||||
BIN
src/simplejson/decoder.pyc
Executable file
BIN
src/simplejson/decoder.pyc
Executable file
Binary file not shown.
501
src/simplejson/encoder.py
Executable file
501
src/simplejson/encoder.py
Executable file
|
|
@ -0,0 +1,501 @@
|
|||
"""Implementation of JSONEncoder
|
||||
"""
|
||||
import re
|
||||
from decimal import Decimal
|
||||
|
||||
def _import_speedups():
|
||||
try:
|
||||
from simplejson import _speedups
|
||||
return _speedups.encode_basestring_ascii, _speedups.make_encoder
|
||||
except ImportError:
|
||||
return None, None
|
||||
c_encode_basestring_ascii, c_make_encoder = _import_speedups()
|
||||
|
||||
from simplejson.decoder import PosInf
|
||||
|
||||
ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
|
||||
ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
|
||||
HAS_UTF8 = re.compile(r'[\x80-\xff]')
|
||||
ESCAPE_DCT = {
|
||||
'\\': '\\\\',
|
||||
'"': '\\"',
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t',
|
||||
}
|
||||
for i in range(0x20):
|
||||
#ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
|
||||
ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
|
||||
|
||||
FLOAT_REPR = repr
|
||||
|
||||
def encode_basestring(s):
|
||||
"""Return a JSON representation of a Python string
|
||||
|
||||
"""
|
||||
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
|
||||
s = s.decode('utf-8')
|
||||
def replace(match):
|
||||
return ESCAPE_DCT[match.group(0)]
|
||||
return u'"' + ESCAPE.sub(replace, s) + u'"'
|
||||
|
||||
|
||||
def py_encode_basestring_ascii(s):
|
||||
"""Return an ASCII-only JSON representation of a Python string
|
||||
|
||||
"""
|
||||
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
|
||||
s = s.decode('utf-8')
|
||||
def replace(match):
|
||||
s = match.group(0)
|
||||
try:
|
||||
return ESCAPE_DCT[s]
|
||||
except KeyError:
|
||||
n = ord(s)
|
||||
if n < 0x10000:
|
||||
#return '\\u{0:04x}'.format(n)
|
||||
return '\\u%04x' % (n,)
|
||||
else:
|
||||
# surrogate pair
|
||||
n -= 0x10000
|
||||
s1 = 0xd800 | ((n >> 10) & 0x3ff)
|
||||
s2 = 0xdc00 | (n & 0x3ff)
|
||||
#return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
|
||||
return '\\u%04x\\u%04x' % (s1, s2)
|
||||
return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
|
||||
|
||||
|
||||
encode_basestring_ascii = (
|
||||
c_encode_basestring_ascii or py_encode_basestring_ascii)
|
||||
|
||||
class JSONEncoder(object):
|
||||
"""Extensible JSON <http://json.org> encoder for Python data structures.
|
||||
|
||||
Supports the following objects and types by default:
|
||||
|
||||
+-------------------+---------------+
|
||||
| Python | JSON |
|
||||
+===================+===============+
|
||||
| dict | object |
|
||||
+-------------------+---------------+
|
||||
| list, tuple | array |
|
||||
+-------------------+---------------+
|
||||
| str, unicode | string |
|
||||
+-------------------+---------------+
|
||||
| int, long, float | number |
|
||||
+-------------------+---------------+
|
||||
| True | true |
|
||||
+-------------------+---------------+
|
||||
| False | false |
|
||||
+-------------------+---------------+
|
||||
| None | null |
|
||||
+-------------------+---------------+
|
||||
|
||||
To extend this to recognize other objects, subclass and implement a
|
||||
``.default()`` method with another method that returns a serializable
|
||||
object for ``o`` if possible, otherwise it should call the superclass
|
||||
implementation (to raise ``TypeError``).
|
||||
|
||||
"""
|
||||
item_separator = ', '
|
||||
key_separator = ': '
|
||||
def __init__(self, skipkeys=False, ensure_ascii=True,
|
||||
check_circular=True, allow_nan=True, sort_keys=False,
|
||||
indent=None, separators=None, encoding='utf-8', default=None,
|
||||
use_decimal=False):
|
||||
"""Constructor for JSONEncoder, with sensible defaults.
|
||||
|
||||
If skipkeys is false, then it is a TypeError to attempt
|
||||
encoding of keys that are not str, int, long, float or None. If
|
||||
skipkeys is True, such items are simply skipped.
|
||||
|
||||
If ensure_ascii is true, the output is guaranteed to be str
|
||||
objects with all incoming unicode characters escaped. If
|
||||
ensure_ascii is false, the output will be unicode object.
|
||||
|
||||
If check_circular is true, then lists, dicts, and custom encoded
|
||||
objects will be checked for circular references during encoding to
|
||||
prevent an infinite recursion (which would cause an OverflowError).
|
||||
Otherwise, no such check takes place.
|
||||
|
||||
If allow_nan is true, then NaN, Infinity, and -Infinity will be
|
||||
encoded as such. This behavior is not JSON specification compliant,
|
||||
but is consistent with most JavaScript based encoders and decoders.
|
||||
Otherwise, it will be a ValueError to encode such floats.
|
||||
|
||||
If sort_keys is true, then the output of dictionaries will be
|
||||
sorted by key; this is useful for regression tests to ensure
|
||||
that JSON serializations can be compared on a day-to-day basis.
|
||||
|
||||
If indent is a string, then JSON array elements and object members
|
||||
will be pretty-printed with a newline followed by that string repeated
|
||||
for each level of nesting. ``None`` (the default) selects the most compact
|
||||
representation without any newlines. For backwards compatibility with
|
||||
versions of simplejson earlier than 2.1.0, an integer is also accepted
|
||||
and is converted to a string with that many spaces.
|
||||
|
||||
If specified, separators should be a (item_separator, key_separator)
|
||||
tuple. The default is (', ', ': '). To get the most compact JSON
|
||||
representation you should specify (',', ':') to eliminate whitespace.
|
||||
|
||||
If specified, default is a function that gets called for objects
|
||||
that can't otherwise be serialized. It should return a JSON encodable
|
||||
version of the object or raise a ``TypeError``.
|
||||
|
||||
If encoding is not None, then all input strings will be
|
||||
transformed into unicode using that encoding prior to JSON-encoding.
|
||||
The default is UTF-8.
|
||||
|
||||
If use_decimal is true (not the default), ``decimal.Decimal`` will
|
||||
be supported directly by the encoder. For the inverse, decode JSON
|
||||
with ``parse_float=decimal.Decimal``.
|
||||
|
||||
"""
|
||||
|
||||
self.skipkeys = skipkeys
|
||||
self.ensure_ascii = ensure_ascii
|
||||
self.check_circular = check_circular
|
||||
self.allow_nan = allow_nan
|
||||
self.sort_keys = sort_keys
|
||||
self.use_decimal = use_decimal
|
||||
if isinstance(indent, (int, long)):
|
||||
indent = ' ' * indent
|
||||
self.indent = indent
|
||||
if separators is not None:
|
||||
self.item_separator, self.key_separator = separators
|
||||
if default is not None:
|
||||
self.default = default
|
||||
self.encoding = encoding
|
||||
|
||||
def default(self, o):
|
||||
"""Implement this method in a subclass such that it returns
|
||||
a serializable object for ``o``, or calls the base implementation
|
||||
(to raise a ``TypeError``).
|
||||
|
||||
For example, to support arbitrary iterators, you could
|
||||
implement default like this::
|
||||
|
||||
def default(self, o):
|
||||
try:
|
||||
iterable = iter(o)
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
return list(iterable)
|
||||
return JSONEncoder.default(self, o)
|
||||
|
||||
"""
|
||||
raise TypeError(repr(o) + " is not JSON serializable")
|
||||
|
||||
def encode(self, o):
|
||||
"""Return a JSON string representation of a Python data structure.
|
||||
|
||||
>>> from simplejson import JSONEncoder
|
||||
>>> JSONEncoder().encode({"foo": ["bar", "baz"]})
|
||||
'{"foo": ["bar", "baz"]}'
|
||||
|
||||
"""
|
||||
# This is for extremely simple cases and benchmarks.
|
||||
if isinstance(o, basestring):
|
||||
if isinstance(o, str):
|
||||
_encoding = self.encoding
|
||||
if (_encoding is not None
|
||||
and not (_encoding == 'utf-8')):
|
||||
o = o.decode(_encoding)
|
||||
if self.ensure_ascii:
|
||||
return encode_basestring_ascii(o)
|
||||
else:
|
||||
return encode_basestring(o)
|
||||
# This doesn't pass the iterator directly to ''.join() because the
|
||||
# exceptions aren't as detailed. The list call should be roughly
|
||||
# equivalent to the PySequence_Fast that ''.join() would do.
|
||||
chunks = self.iterencode(o, _one_shot=True)
|
||||
if not isinstance(chunks, (list, tuple)):
|
||||
chunks = list(chunks)
|
||||
if self.ensure_ascii:
|
||||
return ''.join(chunks)
|
||||
else:
|
||||
return u''.join(chunks)
|
||||
|
||||
def iterencode(self, o, _one_shot=False):
|
||||
"""Encode the given object and yield each string
|
||||
representation as available.
|
||||
|
||||
For example::
|
||||
|
||||
for chunk in JSONEncoder().iterencode(bigobject):
|
||||
mysocket.write(chunk)
|
||||
|
||||
"""
|
||||
if self.check_circular:
|
||||
markers = {}
|
||||
else:
|
||||
markers = None
|
||||
if self.ensure_ascii:
|
||||
_encoder = encode_basestring_ascii
|
||||
else:
|
||||
_encoder = encode_basestring
|
||||
if self.encoding != 'utf-8':
|
||||
def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
|
||||
if isinstance(o, str):
|
||||
o = o.decode(_encoding)
|
||||
return _orig_encoder(o)
|
||||
|
||||
def floatstr(o, allow_nan=self.allow_nan,
|
||||
_repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf):
|
||||
# Check for specials. Note that this type of test is processor
|
||||
# and/or platform-specific, so do tests which don't depend on
|
||||
# the internals.
|
||||
|
||||
if o != o:
|
||||
text = 'NaN'
|
||||
elif o == _inf:
|
||||
text = 'Infinity'
|
||||
elif o == _neginf:
|
||||
text = '-Infinity'
|
||||
else:
|
||||
return _repr(o)
|
||||
|
||||
if not allow_nan:
|
||||
raise ValueError(
|
||||
"Out of range float values are not JSON compliant: " +
|
||||
repr(o))
|
||||
|
||||
return text
|
||||
|
||||
|
||||
key_memo = {}
|
||||
if (_one_shot and c_make_encoder is not None
|
||||
and not self.indent and not self.sort_keys):
|
||||
_iterencode = c_make_encoder(
|
||||
markers, self.default, _encoder, self.indent,
|
||||
self.key_separator, self.item_separator, self.sort_keys,
|
||||
self.skipkeys, self.allow_nan, key_memo, self.use_decimal)
|
||||
else:
|
||||
_iterencode = _make_iterencode(
|
||||
markers, self.default, _encoder, self.indent, floatstr,
|
||||
self.key_separator, self.item_separator, self.sort_keys,
|
||||
self.skipkeys, _one_shot, self.use_decimal)
|
||||
try:
|
||||
return _iterencode(o, 0)
|
||||
finally:
|
||||
key_memo.clear()
|
||||
|
||||
|
||||
class JSONEncoderForHTML(JSONEncoder):
|
||||
"""An encoder that produces JSON safe to embed in HTML.
|
||||
|
||||
To embed JSON content in, say, a script tag on a web page, the
|
||||
characters &, < and > should be escaped. They cannot be escaped
|
||||
with the usual entities (e.g. &) because they are not expanded
|
||||
within <script> tags.
|
||||
"""
|
||||
|
||||
def encode(self, o):
|
||||
# Override JSONEncoder.encode because it has hacks for
|
||||
# performance that make things more complicated.
|
||||
chunks = self.iterencode(o, True)
|
||||
if self.ensure_ascii:
|
||||
return ''.join(chunks)
|
||||
else:
|
||||
return u''.join(chunks)
|
||||
|
||||
def iterencode(self, o, _one_shot=False):
|
||||
chunks = super(JSONEncoderForHTML, self).iterencode(o, _one_shot)
|
||||
for chunk in chunks:
|
||||
chunk = chunk.replace('&', '\\u0026')
|
||||
chunk = chunk.replace('<', '\\u003c')
|
||||
chunk = chunk.replace('>', '\\u003e')
|
||||
yield chunk
|
||||
|
||||
|
||||
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
||||
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
|
||||
_use_decimal,
|
||||
## HACK: hand-optimized bytecode; turn globals into locals
|
||||
False=False,
|
||||
True=True,
|
||||
ValueError=ValueError,
|
||||
basestring=basestring,
|
||||
Decimal=Decimal,
|
||||
dict=dict,
|
||||
float=float,
|
||||
id=id,
|
||||
int=int,
|
||||
isinstance=isinstance,
|
||||
list=list,
|
||||
long=long,
|
||||
str=str,
|
||||
tuple=tuple,
|
||||
):
|
||||
|
||||
def _iterencode_list(lst, _current_indent_level):
|
||||
if not lst:
|
||||
yield '[]'
|
||||
return
|
||||
if markers is not None:
|
||||
markerid = id(lst)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = lst
|
||||
buf = '['
|
||||
if _indent is not None:
|
||||
_current_indent_level += 1
|
||||
newline_indent = '\n' + (_indent * _current_indent_level)
|
||||
separator = _item_separator + newline_indent
|
||||
buf += newline_indent
|
||||
else:
|
||||
newline_indent = None
|
||||
separator = _item_separator
|
||||
first = True
|
||||
for value in lst:
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
buf = separator
|
||||
if isinstance(value, basestring):
|
||||
yield buf + _encoder(value)
|
||||
elif value is None:
|
||||
yield buf + 'null'
|
||||
elif value is True:
|
||||
yield buf + 'true'
|
||||
elif value is False:
|
||||
yield buf + 'false'
|
||||
elif isinstance(value, (int, long)):
|
||||
yield buf + str(value)
|
||||
elif isinstance(value, float):
|
||||
yield buf + _floatstr(value)
|
||||
elif _use_decimal and isinstance(value, Decimal):
|
||||
yield buf + str(value)
|
||||
else:
|
||||
yield buf
|
||||
if isinstance(value, (list, tuple)):
|
||||
chunks = _iterencode_list(value, _current_indent_level)
|
||||
elif isinstance(value, dict):
|
||||
chunks = _iterencode_dict(value, _current_indent_level)
|
||||
else:
|
||||
chunks = _iterencode(value, _current_indent_level)
|
||||
for chunk in chunks:
|
||||
yield chunk
|
||||
if newline_indent is not None:
|
||||
_current_indent_level -= 1
|
||||
yield '\n' + (_indent * _current_indent_level)
|
||||
yield ']'
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
def _iterencode_dict(dct, _current_indent_level):
|
||||
if not dct:
|
||||
yield '{}'
|
||||
return
|
||||
if markers is not None:
|
||||
markerid = id(dct)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = dct
|
||||
yield '{'
|
||||
if _indent is not None:
|
||||
_current_indent_level += 1
|
||||
newline_indent = '\n' + (_indent * _current_indent_level)
|
||||
item_separator = _item_separator + newline_indent
|
||||
yield newline_indent
|
||||
else:
|
||||
newline_indent = None
|
||||
item_separator = _item_separator
|
||||
first = True
|
||||
if _sort_keys:
|
||||
items = dct.items()
|
||||
items.sort(key=lambda kv: kv[0])
|
||||
else:
|
||||
items = dct.iteritems()
|
||||
for key, value in items:
|
||||
if isinstance(key, basestring):
|
||||
pass
|
||||
# JavaScript is weakly typed for these, so it makes sense to
|
||||
# also allow them. Many encoders seem to do something like this.
|
||||
elif isinstance(key, float):
|
||||
key = _floatstr(key)
|
||||
elif key is True:
|
||||
key = 'true'
|
||||
elif key is False:
|
||||
key = 'false'
|
||||
elif key is None:
|
||||
key = 'null'
|
||||
elif isinstance(key, (int, long)):
|
||||
key = str(key)
|
||||
elif _skipkeys:
|
||||
continue
|
||||
else:
|
||||
raise TypeError("key " + repr(key) + " is not a string")
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
yield item_separator
|
||||
yield _encoder(key)
|
||||
yield _key_separator
|
||||
if isinstance(value, basestring):
|
||||
yield _encoder(value)
|
||||
elif value is None:
|
||||
yield 'null'
|
||||
elif value is True:
|
||||
yield 'true'
|
||||
elif value is False:
|
||||
yield 'false'
|
||||
elif isinstance(value, (int, long)):
|
||||
yield str(value)
|
||||
elif isinstance(value, float):
|
||||
yield _floatstr(value)
|
||||
elif _use_decimal and isinstance(value, Decimal):
|
||||
yield str(value)
|
||||
else:
|
||||
if isinstance(value, (list, tuple)):
|
||||
chunks = _iterencode_list(value, _current_indent_level)
|
||||
elif isinstance(value, dict):
|
||||
chunks = _iterencode_dict(value, _current_indent_level)
|
||||
else:
|
||||
chunks = _iterencode(value, _current_indent_level)
|
||||
for chunk in chunks:
|
||||
yield chunk
|
||||
if newline_indent is not None:
|
||||
_current_indent_level -= 1
|
||||
yield '\n' + (_indent * _current_indent_level)
|
||||
yield '}'
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
def _iterencode(o, _current_indent_level):
|
||||
if isinstance(o, basestring):
|
||||
yield _encoder(o)
|
||||
elif o is None:
|
||||
yield 'null'
|
||||
elif o is True:
|
||||
yield 'true'
|
||||
elif o is False:
|
||||
yield 'false'
|
||||
elif isinstance(o, (int, long)):
|
||||
yield str(o)
|
||||
elif isinstance(o, float):
|
||||
yield _floatstr(o)
|
||||
elif isinstance(o, (list, tuple)):
|
||||
for chunk in _iterencode_list(o, _current_indent_level):
|
||||
yield chunk
|
||||
elif isinstance(o, dict):
|
||||
for chunk in _iterencode_dict(o, _current_indent_level):
|
||||
yield chunk
|
||||
elif _use_decimal and isinstance(o, Decimal):
|
||||
yield str(o)
|
||||
else:
|
||||
if markers is not None:
|
||||
markerid = id(o)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = o
|
||||
o = _default(o)
|
||||
for chunk in _iterencode(o, _current_indent_level):
|
||||
yield chunk
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
return _iterencode
|
||||
BIN
src/simplejson/encoder.pyc
Executable file
BIN
src/simplejson/encoder.pyc
Executable file
Binary file not shown.
119
src/simplejson/ordered_dict.py
Executable file
119
src/simplejson/ordered_dict.py
Executable file
|
|
@ -0,0 +1,119 @@
|
|||
"""Drop-in replacement for collections.OrderedDict by Raymond Hettinger
|
||||
|
||||
http://code.activestate.com/recipes/576693/
|
||||
|
||||
"""
|
||||
from UserDict import DictMixin
|
||||
|
||||
# Modified from original to support Python 2.4, see
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=53
|
||||
try:
|
||||
all
|
||||
except NameError:
|
||||
def all(seq):
|
||||
for elem in seq:
|
||||
if not elem:
|
||||
return False
|
||||
return True
|
||||
|
||||
class OrderedDict(dict, DictMixin):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
if len(args) > 1:
|
||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||
try:
|
||||
self.__end
|
||||
except AttributeError:
|
||||
self.clear()
|
||||
self.update(*args, **kwds)
|
||||
|
||||
def clear(self):
|
||||
self.__end = end = []
|
||||
end += [None, end, end] # sentinel node for doubly linked list
|
||||
self.__map = {} # key --> [key, prev, next]
|
||||
dict.clear(self)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key not in self:
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
curr[2] = end[1] = self.__map[key] = [key, curr, end]
|
||||
dict.__setitem__(self, key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
dict.__delitem__(self, key)
|
||||
key, prev, next = self.__map.pop(key)
|
||||
prev[2] = next
|
||||
next[1] = prev
|
||||
|
||||
def __iter__(self):
|
||||
end = self.__end
|
||||
curr = end[2]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[2]
|
||||
|
||||
def __reversed__(self):
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[1]
|
||||
|
||||
def popitem(self, last=True):
|
||||
if not self:
|
||||
raise KeyError('dictionary is empty')
|
||||
# Modified from original to support Python 2.4, see
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=53
|
||||
if last:
|
||||
key = reversed(self).next()
|
||||
else:
|
||||
key = iter(self).next()
|
||||
value = self.pop(key)
|
||||
return key, value
|
||||
|
||||
def __reduce__(self):
|
||||
items = [[k, self[k]] for k in self]
|
||||
tmp = self.__map, self.__end
|
||||
del self.__map, self.__end
|
||||
inst_dict = vars(self).copy()
|
||||
self.__map, self.__end = tmp
|
||||
if inst_dict:
|
||||
return (self.__class__, (items,), inst_dict)
|
||||
return self.__class__, (items,)
|
||||
|
||||
def keys(self):
|
||||
return list(self)
|
||||
|
||||
setdefault = DictMixin.setdefault
|
||||
update = DictMixin.update
|
||||
pop = DictMixin.pop
|
||||
values = DictMixin.values
|
||||
items = DictMixin.items
|
||||
iterkeys = DictMixin.iterkeys
|
||||
itervalues = DictMixin.itervalues
|
||||
iteritems = DictMixin.iteritems
|
||||
|
||||
def __repr__(self):
|
||||
if not self:
|
||||
return '%s()' % (self.__class__.__name__,)
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self)
|
||||
|
||||
@classmethod
|
||||
def fromkeys(cls, iterable, value=None):
|
||||
d = cls()
|
||||
for key in iterable:
|
||||
d[key] = value
|
||||
return d
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, OrderedDict):
|
||||
return len(self)==len(other) and \
|
||||
all(p==q for p, q in zip(self.items(), other.items()))
|
||||
return dict.__eq__(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
77
src/simplejson/scanner.py
Executable file
77
src/simplejson/scanner.py
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
"""JSON token scanner
|
||||
"""
|
||||
import re
|
||||
def _import_c_make_scanner():
|
||||
try:
|
||||
from simplejson._speedups import make_scanner
|
||||
return make_scanner
|
||||
except ImportError:
|
||||
return None
|
||||
c_make_scanner = _import_c_make_scanner()
|
||||
|
||||
__all__ = ['make_scanner']
|
||||
|
||||
NUMBER_RE = re.compile(
|
||||
r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
|
||||
(re.VERBOSE | re.MULTILINE | re.DOTALL))
|
||||
|
||||
def py_make_scanner(context):
|
||||
parse_object = context.parse_object
|
||||
parse_array = context.parse_array
|
||||
parse_string = context.parse_string
|
||||
match_number = NUMBER_RE.match
|
||||
encoding = context.encoding
|
||||
strict = context.strict
|
||||
parse_float = context.parse_float
|
||||
parse_int = context.parse_int
|
||||
parse_constant = context.parse_constant
|
||||
object_hook = context.object_hook
|
||||
object_pairs_hook = context.object_pairs_hook
|
||||
memo = context.memo
|
||||
|
||||
def _scan_once(string, idx):
|
||||
try:
|
||||
nextchar = string[idx]
|
||||
except IndexError:
|
||||
raise StopIteration
|
||||
|
||||
if nextchar == '"':
|
||||
return parse_string(string, idx + 1, encoding, strict)
|
||||
elif nextchar == '{':
|
||||
return parse_object((string, idx + 1), encoding, strict,
|
||||
_scan_once, object_hook, object_pairs_hook, memo)
|
||||
elif nextchar == '[':
|
||||
return parse_array((string, idx + 1), _scan_once)
|
||||
elif nextchar == 'n' and string[idx:idx + 4] == 'null':
|
||||
return None, idx + 4
|
||||
elif nextchar == 't' and string[idx:idx + 4] == 'true':
|
||||
return True, idx + 4
|
||||
elif nextchar == 'f' and string[idx:idx + 5] == 'false':
|
||||
return False, idx + 5
|
||||
|
||||
m = match_number(string, idx)
|
||||
if m is not None:
|
||||
integer, frac, exp = m.groups()
|
||||
if frac or exp:
|
||||
res = parse_float(integer + (frac or '') + (exp or ''))
|
||||
else:
|
||||
res = parse_int(integer)
|
||||
return res, m.end()
|
||||
elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
|
||||
return parse_constant('NaN'), idx + 3
|
||||
elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
|
||||
return parse_constant('Infinity'), idx + 8
|
||||
elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
|
||||
return parse_constant('-Infinity'), idx + 9
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
def scan_once(string, idx):
|
||||
try:
|
||||
return _scan_once(string, idx)
|
||||
finally:
|
||||
memo.clear()
|
||||
|
||||
return scan_once
|
||||
|
||||
make_scanner = c_make_scanner or py_make_scanner
|
||||
BIN
src/simplejson/scanner.pyc
Executable file
BIN
src/simplejson/scanner.pyc
Executable file
Binary file not shown.
39
src/simplejson/tool.py
Executable file
39
src/simplejson/tool.py
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
r"""Command-line tool to validate and pretty-print JSON
|
||||
|
||||
Usage::
|
||||
|
||||
$ echo '{"json":"obj"}' | python -m simplejson.tool
|
||||
{
|
||||
"json": "obj"
|
||||
}
|
||||
$ echo '{ 1.2:3.4}' | python -m simplejson.tool
|
||||
Expecting property name: line 1 column 2 (char 2)
|
||||
|
||||
"""
|
||||
import sys
|
||||
import simplejson as json
|
||||
|
||||
def main():
|
||||
if len(sys.argv) == 1:
|
||||
infile = sys.stdin
|
||||
outfile = sys.stdout
|
||||
elif len(sys.argv) == 2:
|
||||
infile = open(sys.argv[1], 'rb')
|
||||
outfile = sys.stdout
|
||||
elif len(sys.argv) == 3:
|
||||
infile = open(sys.argv[1], 'rb')
|
||||
outfile = open(sys.argv[2], 'wb')
|
||||
else:
|
||||
raise SystemExit(sys.argv[0] + " [infile [outfile]]")
|
||||
try:
|
||||
obj = json.load(infile,
|
||||
object_pairs_hook=json.OrderedDict,
|
||||
use_decimal=True)
|
||||
except ValueError, e:
|
||||
raise SystemExit(e)
|
||||
json.dump(obj, outfile, sort_keys=True, indent=' ', use_decimal=True)
|
||||
outfile.write('\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
167
src/tuf/__init__.py
Executable file
167
src/tuf/__init__.py
Executable file
|
|
@ -0,0 +1,167 @@
|
|||
"""
|
||||
<Program Name>
|
||||
__init__.py
|
||||
|
||||
<Author>
|
||||
Geremy Condra
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
VD: April 4, 2012 Revision.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Define TUF Exceptions.
|
||||
|
||||
The names chosen for TUF Exception classes should end in
|
||||
'Error' except where there is a good reason not to, and
|
||||
provide that reason in those cases.
|
||||
|
||||
"""
|
||||
|
||||
# Import 'tuf.formats' if a module tries to import the
|
||||
# entire tuf package (i.e., from tuf import *).
|
||||
__all__ = ['formats']
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Indicate a generic error."""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Warning(Warning):
|
||||
"""TUF's warning category class. It is used by the 'warnings' module."""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class FormatError(Error):
|
||||
"""Indicate an error while validating an object's format."""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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."""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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 ExpiredMetadataError(Error):
|
||||
"""Indicate that a TUF Metadata file has expired."""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class MetadataNotAvailableError(Error):
|
||||
"""Indicate an error locating a Metadata file for a specified target/role."""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class CryptoError(Error):
|
||||
"""Indicate any cryptography-related errors."""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class UnsupportedLibraryError(Error):
|
||||
"""Indicate that a supported library could not be located or imported."""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class UnknownMethodError(CryptoError):
|
||||
"""Indicate that a user-specified cryptograpthic method is unknown."""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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 TUF role."""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class InvalidNameError(Error):
|
||||
"""Indicate an error while trying to validate any type of named object"""
|
||||
pass
|
||||
48
src/tuf/aggregate_tests.py
Executable file
48
src/tuf/aggregate_tests.py
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
"""
|
||||
<Program Name>
|
||||
aggregate_tuf.tests.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
|
||||
<Started>
|
||||
Jamuary 26, 2013
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Run all tuf.tests.
|
||||
|
||||
"""
|
||||
# Run test_updater.py.
|
||||
import tuf.tests.test_updater
|
||||
|
||||
# Run test_quickstart.
|
||||
#import tuf.tests.test_quickstart
|
||||
|
||||
# Run test_sig.py.
|
||||
#import tuf.tests.test_sig
|
||||
|
||||
# Run test_rsa_key.py.
|
||||
#import tuf.tests.test_rsa_key
|
||||
|
||||
# Run test_keystore.py.
|
||||
#import tuf.tests.test_keystore
|
||||
|
||||
# Run test_signerlib.py.
|
||||
#import tuf.tests.test_signerlib
|
||||
|
||||
# Run test_signercli.py.
|
||||
import tuf.tests.test_signercli
|
||||
|
||||
# Run test_download.py
|
||||
#import tuf.tests.test_download
|
||||
|
||||
# Run test_hash.py
|
||||
#import tuf.tests.test_hash
|
||||
|
||||
# Run test_mirrors.py.
|
||||
#import tuf.tests.test_mirrors
|
||||
|
||||
|
||||
177
src/tuf/build_updater.py
Executable file
177
src/tuf/build_updater.py
Executable file
|
|
@ -0,0 +1,177 @@
|
|||
"""
|
||||
|
||||
build_updater.py
|
||||
|
||||
Written by Geremy Condra
|
||||
Licensed under MIT
|
||||
Released 12 October 2010
|
||||
|
||||
Implements the Distutils 'build_updater' command.
|
||||
"""
|
||||
|
||||
from distutils.core import Command
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
import datetime
|
||||
|
||||
|
||||
import tuf
|
||||
import tuf.conf
|
||||
import tuf.client.updater
|
||||
|
||||
def do_update(url):
|
||||
tuf.conf.settings.repo_meta_dir = "."
|
||||
repo_data = {'repo': {'urlbase': url, 'metapath': "meta", 'targetspath': "targets", 'metacontent': ['**'], 'targetscontent': ['**']}}
|
||||
|
||||
repo = tuf.client.updater.Repository("", repo_data)
|
||||
|
||||
repo.refresh()
|
||||
targets = repo.get_all_targets()
|
||||
|
||||
# JAC: add file removal support for nmap folks...
|
||||
repo.remove_missing_files()
|
||||
|
||||
files_to_update = repo.get_files_to_update(targets)
|
||||
for target in targets:
|
||||
if target in files_to_update:
|
||||
try:os.makedirs(os.path.dirname(target.path))
|
||||
except: pass
|
||||
target.download(target.path)
|
||||
|
||||
|
||||
def retry(f, *args, **kwargs):
|
||||
# if an exception is raised, retry the operation
|
||||
f(*args, **kwargs)
|
||||
f(*args, **kwargs)
|
||||
|
||||
def copy_metadata(client, server):
|
||||
try: shutil.copytree(os.getcwd(), client)
|
||||
except: pass
|
||||
try: shutil.rmtree(client + '/' + 'cur')
|
||||
except: pass
|
||||
shutil.copytree(server + '/' + 'meta', client + '/' + 'cur')
|
||||
try: shutil.rmtree(client + '/' + 'prev')
|
||||
except: pass
|
||||
shutil.copytree(server + '/' + 'meta', client + '/' + 'prev')
|
||||
try: os.remove('build_updater.pyc')
|
||||
except: pass
|
||||
|
||||
class build_updater(Command):
|
||||
|
||||
# Brief (40-50 characters) description of the command
|
||||
description = "Builds all the necessary values for a basic TUF update system for the given project"
|
||||
|
||||
# List of option tuples: long name, short name (None if no short
|
||||
# name), and help string.
|
||||
user_options = [ ('expiration', None, "Determines when signatures expire"),
|
||||
('keystore', None, "The path to look for the keystore at"),
|
||||
('server', None, "The directory to place the generated meta and targets folders")
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.keysize = None
|
||||
self.expiration = None
|
||||
self.keystore = None
|
||||
self.server = None
|
||||
self.client = None
|
||||
|
||||
def finalize_options(self):
|
||||
if self.keysize is None:
|
||||
self.keysize = 1024
|
||||
if self.expiration is None:
|
||||
# today's date + 30 days
|
||||
t = time.time()
|
||||
t += 30 * 24 * 60 * 60
|
||||
dt = datetime.date.fromtimestamp(t)
|
||||
self.expiration = dt.strftime('%d/%m/%Y')
|
||||
if self.keystore is None:
|
||||
self.keystore = '../keystore'
|
||||
if self.server is None:
|
||||
self.server = os.getcwd()
|
||||
if self.client is None:
|
||||
self.client = os.getcwd()
|
||||
|
||||
def run(self):
|
||||
args = ["quickstart.py",
|
||||
"-k",
|
||||
self.keystore,
|
||||
"-t",
|
||||
"1",
|
||||
"-l",
|
||||
self.server,
|
||||
"-r",
|
||||
os.getcwd(),
|
||||
"-e",
|
||||
self.expiration]
|
||||
subprocess.call(args)
|
||||
retry(copy_metadata, self.client, self.server)
|
||||
|
||||
class update_updater(Command):
|
||||
|
||||
# Brief (40-50 characters) description of the command
|
||||
description = "Pushes new updates to a preexisting update server"
|
||||
|
||||
# List of option tuples: long name, short name (None if no short
|
||||
# name), and help string.
|
||||
user_options = [ ('keystore', None, "The path to look for the keystore at"),
|
||||
('server', None, "The directory to place the generated meta and targets folders"),
|
||||
('client', None, "The directory to place the generate client package"),
|
||||
('step', None, "The step in the process to perform")
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.keystore = None
|
||||
self.server = None
|
||||
self.password = None
|
||||
self.client = None
|
||||
self.step = None
|
||||
|
||||
def finalize_options(self):
|
||||
if self.keystore is None:
|
||||
self.keystore = '../keystore'
|
||||
if self.server is None:
|
||||
self.server = os.getcwd()
|
||||
if self.password is None:
|
||||
self.password = ''
|
||||
if self.client is None:
|
||||
self.client = '.'
|
||||
if self.step is None:
|
||||
self.step = 1
|
||||
|
||||
def run(self):
|
||||
args = ["quickstart.py",
|
||||
"-k",
|
||||
self.keystore,
|
||||
"-l",
|
||||
self.server,
|
||||
"-r",
|
||||
os.getcwd(),
|
||||
"-c",
|
||||
self.server + '/' + 'root.cfg',
|
||||
"--step",
|
||||
str(self.step),
|
||||
"-u"]
|
||||
subprocess.call(args)
|
||||
retry(copy_metadata, self.client, self.server)
|
||||
|
||||
class update(Command):
|
||||
|
||||
# Brief (40-50 characters) description of the command
|
||||
description = "Performs an update in the current directory"
|
||||
|
||||
# List of option tuples: long name, short name (None if no short
|
||||
# name), and help string.
|
||||
user_options = [('url', None, "The url of the remote update server")]
|
||||
|
||||
def initialize_options(self):
|
||||
self.url = None
|
||||
|
||||
def finalize_options(self):
|
||||
if self.url is None:
|
||||
raise Exception("No software update URL specified")
|
||||
|
||||
def run(self):
|
||||
do_update(self.url)
|
||||
0
src/tuf/client/__init__.py
Executable file
0
src/tuf/client/__init__.py
Executable file
1684
src/tuf/client/updater.py
Executable file
1684
src/tuf/client/updater.py
Executable file
File diff suppressed because it is too large
Load diff
33
src/tuf/conf.py
Executable file
33
src/tuf/conf.py
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
"""
|
||||
<Program Name>
|
||||
conf.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
April 4, 2012. Based a previous version by Geremy Condra.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
A central location for TUF configuration settings.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# 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 directory under which metadata for all repositories will be
|
||||
# stored. This is not a simple cache because each repository's root of
|
||||
# trust (root.txt) will need to already be stored below here and should
|
||||
# not be deleted. At a minimum, each key in the mirrors dictionary
|
||||
# below should have a directory under 'repository_directory'
|
||||
# which already exists and within that directory should have the file
|
||||
# 'metadata/current/root.txt'. This must be set!
|
||||
repository_directory = None
|
||||
233
src/tuf/download.py
Executable file
233
src/tuf/download.py
Executable file
|
|
@ -0,0 +1,233 @@
|
|||
"""
|
||||
<Program Name>
|
||||
download.py
|
||||
|
||||
<Started>
|
||||
February 21, 2012. Based on previous version by Geremy Condra.
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Perform any file downloads and check their validity. This means that the
|
||||
hash and length of a downloaded file has to match the hash and length
|
||||
supplied by the metadata of that file. The downloaded file is technically a
|
||||
file-like object that will automatically destroys itself once closed. Note
|
||||
that the file-like object, 'tuf.util.TempFile', is returned by the
|
||||
'download_url_to_tempfileobj()' function.
|
||||
|
||||
"""
|
||||
|
||||
import urllib2
|
||||
import logging
|
||||
|
||||
import tuf.hash
|
||||
import tuf.util
|
||||
import tuf.formats
|
||||
|
||||
# See 'log.py' to learn how logging is handled in TUF.
|
||||
logger = logging.getLogger('tuf.download')
|
||||
|
||||
|
||||
def _open_connection(url):
|
||||
"""
|
||||
<Purpose>
|
||||
Helper function that opens a connection to the url. urllib2 supports http,
|
||||
ftp, and file. In python (2.6+) where the ssl module is available, urllib2
|
||||
also supports https.
|
||||
|
||||
TODO: Do proper ssl cert/name checking.
|
||||
TODO: Disallow SSLv2.
|
||||
TODO: Support ssl with MCrypto.
|
||||
TODO: Determine whether this follows http redirects and decide if we like
|
||||
that. For example, would we not want to allow redirection from ssl to
|
||||
non-ssl urls?
|
||||
|
||||
<Arguments>
|
||||
url:
|
||||
URL string (e.g., 'http://...' or 'ftp://...' or 'file://...')
|
||||
|
||||
<Exceptions>
|
||||
urllib2.URLError
|
||||
|
||||
<Side Effects>
|
||||
Opens a connection to a remote server.
|
||||
|
||||
<Returns>
|
||||
File-like object.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
# urllib2.Request produces a Request object that allows for a finer control
|
||||
# of the requesting process. Request object allows to add headers or data to
|
||||
# the HTTP request. For instance, request method add_header(key, val) can be
|
||||
# used to change/spoof 'User-Agent' from default Python-urllib/x.y to
|
||||
# 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)' this can be useful if
|
||||
# servers do not recognize connections that originates from
|
||||
# Python-urllib/x.y.
|
||||
request = urllib2.Request(url)
|
||||
connection = urllib2.urlopen(request)
|
||||
except Exception, e:
|
||||
raise tuf.DownloadError(e)
|
||||
|
||||
# urllib2.urlopen returns a file-like object, I think of it as a handle to the
|
||||
# remote data.
|
||||
|
||||
return connection
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _check_hashes(input_file, trusted_hashes):
|
||||
"""
|
||||
<Purpose>
|
||||
Helper function that verifies multiple secure hashes of the downloaded file.
|
||||
If any of these fail it raises an exception. This is to conform with the
|
||||
TUF specs, which support clients with different hashing algorithms. The
|
||||
'hash.py' module is used to compute the hashes of the 'input_file'.
|
||||
|
||||
<Arguments>
|
||||
input_file:
|
||||
A file or file-like object.
|
||||
|
||||
trusted_hashes:
|
||||
A dictionary with hash-algorithm names as keys and hashes as dict values.
|
||||
The hashes should be in the hexdigest format.
|
||||
|
||||
<Exceptions>
|
||||
tuf.BadHash, if the hashes don't match.
|
||||
|
||||
<Side Effects>
|
||||
Hash digest object is created using tfh 'tuf.hash' module.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
# Verify each trusted hash of 'trusted_hashes'. Raise exception if
|
||||
# any of the hashes are incorrect and return if all are correct.
|
||||
for algorithm, trusted_hash in trusted_hashes.items():
|
||||
digest_object = tuf.hash.digest(algorithm)
|
||||
digest_object.update(input_file.read())
|
||||
computed_hash = digest_object.hexdigest()
|
||||
if trusted_hash != computed_hash:
|
||||
msg = 'Hashes do not match. Expected '+trusted_hash+' got '+computed_hash
|
||||
raise tuf.BadHash(msg)
|
||||
else:
|
||||
logger.info('The file\'s '+algorithm+' hash is correct: '+trusted_hash)
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def download_url_to_tempfileobj(url, required_hashes=None, required_length=None):
|
||||
"""
|
||||
<Purpose>
|
||||
Given the url, hashes and length of the desired file, this function
|
||||
opens a connection to 'url' and downloads the file while ensuring its
|
||||
length and hashes match 'required_hashes' and 'required_length'.
|
||||
|
||||
tuf.util.TempFile is used instead of regular tempfile object because of
|
||||
additional functionality provided by 'tuf.util.TempFile'.
|
||||
|
||||
<Arguments>
|
||||
url:
|
||||
A url string that represents the location of the file.
|
||||
|
||||
required_hashes:
|
||||
A dictionary, where the keys represent the hashing algorithm used to
|
||||
hash the file and the dict values the hexdigest.
|
||||
|
||||
For instance, a hash pair might look something like this:
|
||||
{'md5': '37544f383be1fc1a32f42801c9c4b4d6'}
|
||||
|
||||
required_length:
|
||||
An integer value representing the length of the file.
|
||||
|
||||
<Side Effects>
|
||||
'tuf.util.TempFile' object is created.
|
||||
|
||||
<Exceptions>
|
||||
tuf.DownloadError, if there was an error while downloading the file.
|
||||
|
||||
tuf.FormatError, if any of the arguments are improperly formatted.
|
||||
|
||||
<Returns>
|
||||
'tuf.util.TempFile' instance.
|
||||
|
||||
"""
|
||||
|
||||
# Do all of the arguments have the appropriate format?
|
||||
# Raise 'tuf.FormatError' if there is a mismatch.
|
||||
tuf.formats.URL_SCHEMA.check_match(url)
|
||||
if required_hashes is not None:
|
||||
tuf.formats.HASHDICT_SCHEMA.check_match(required_hashes)
|
||||
if required_length is not None:
|
||||
tuf.formats.LENGTH_SCHEMA.check_match(required_length)
|
||||
|
||||
# 'url.replace()' is for compatibility with Windows-based systems because they
|
||||
# might put back-slashes in place of forward-slashes. This converts it to the
|
||||
# common format.
|
||||
url = url.replace('\\','/')
|
||||
logger.info('Downloading '+url)
|
||||
connection = _open_connection(url)
|
||||
temp_file = tuf.util.TempFile()
|
||||
|
||||
# Keep track of total bytes downloaded.
|
||||
total_downloaded = 0
|
||||
|
||||
try:
|
||||
# info().get('Content-Length') gets the length of the url file.
|
||||
file_length = int(connection.info().get('Content-Length'))
|
||||
|
||||
# Does the url's 'file_length' match 'required_length'?
|
||||
if required_length is not None and file_length != required_length:
|
||||
message = 'Incorrect length for '+url+'. Expected '+str(required_length)+ \
|
||||
', got '+str(file_length)+'.'
|
||||
raise tuf.DownloadError(message)
|
||||
|
||||
# While-block reads data from connection 8192-bytes at a time, or less,
|
||||
# until 'file_length' is reached.
|
||||
while True:
|
||||
data = connection.read(min(8192, file_length - total_downloaded))
|
||||
# We might have no more data to read. Let us check bytes downloaded.
|
||||
if not data:
|
||||
message = 'Downloaded '+str(total_downloaded)+'/' \
|
||||
+str(file_length)+' bytes'
|
||||
logger.debug(message)
|
||||
# Did we download the correct amount indicated by 'Content-Length'?
|
||||
if total_downloaded != file_length:
|
||||
message = 'Downloaded '+str(total_downloaded)+'. Expected '+ \
|
||||
str(file_length)+' for '+url
|
||||
raise tuf.DownloadError(message)
|
||||
# Did we download the correct amount indicated by the user?
|
||||
if required_length is not None and total_downloaded != required_length:
|
||||
message = 'The user-required length of '+str(required_length)+ \
|
||||
'did not match the '+str(len(total_downloaded))+' downloaded'
|
||||
raise tuf.DownloadError(message)
|
||||
break
|
||||
# Data successfully read from the connection. Store it.
|
||||
temp_file.write(data)
|
||||
total_downloaded = total_downloaded + len(data)
|
||||
|
||||
# We appear to have downloaded the correct amount. Check the hashes.
|
||||
connection.close()
|
||||
if required_length is not None and required_hashes is not None:
|
||||
_check_hashes(temp_file, required_hashes)
|
||||
|
||||
# Exception is a base class for all non-exiting exceptions.
|
||||
except Exception, e:
|
||||
# Closing 'temp_file'. The 'temp_file' data is destroyed.
|
||||
temp_file.close_temp_file()
|
||||
logger.error(str(e))
|
||||
raise tuf.DownloadError(e)
|
||||
|
||||
return temp_file
|
||||
80
src/tuf/examples/example_client.py
Executable file
80
src/tuf/examples/example_client.py
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""
|
||||
Example script demonstrating custom python code a software updater
|
||||
utilizing The Update Framework may write to securely update files.
|
||||
The 'basic_client.py' script can be used on the command-line to perform
|
||||
an update that will download and update all available targets; writing
|
||||
custom code is not required in this case. The custom examples below
|
||||
demonstrate: (1) updating all targets (2) updating all the targets of
|
||||
a specified role (3) updating a specific target explicitely named.
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import tuf.client.updater
|
||||
|
||||
# Uncomment the line below to enable printing of debugging information.
|
||||
#tuf.log.set_log_level(logging.DEBUG)
|
||||
|
||||
# Set the local repository directory containing the metadata files.
|
||||
tuf.conf.repository_directory = '.'
|
||||
|
||||
# Set the repository mirrors. This dictionary is needed by the repository
|
||||
# class of updater.py. The client will download metadata and target
|
||||
# files from any one of these mirrors.
|
||||
repository_mirrors = {'mirror1': {'url_prefix': 'http://localhost:8001',
|
||||
'metadata_path': 'metadata',
|
||||
'targets_path': 'targets',
|
||||
'confined_target_paths': ['']}}
|
||||
|
||||
# Create the repository object using the updater name 'tuf-example'
|
||||
# and the repository mirrors defined above.
|
||||
updater = tuf.client.updater.Updater('tuf-example', repository_mirrors)
|
||||
|
||||
# Set the local destination directory to save the target files.
|
||||
destination_directory = './targets'
|
||||
|
||||
# Refresh the repository's top-level roles, store the target information for
|
||||
# all the targets tracked, and determine which of these targets have been
|
||||
# updated.
|
||||
updater.refresh()
|
||||
all_targets = updater.all_targets()
|
||||
updated_targets = updater.updated_targets(all_targets, destination_directory)
|
||||
|
||||
# Download each of these updated targets and save them locally.
|
||||
for target in updated_targets:
|
||||
try:
|
||||
updater.download_target(target, destination_directory)
|
||||
except tuf.DownloadError, e:
|
||||
pass
|
||||
|
||||
# Remove any files from the destination directory that are no longer being
|
||||
# tracked.
|
||||
updater.remove_obsolete_targets(destination_directory)
|
||||
|
||||
|
||||
"""
|
||||
# Example demonstrating an update that only downloads the targets of
|
||||
# a specific role (i.e., 'targets/role1')
|
||||
|
||||
updater.refresh()
|
||||
targets_of_role1 = updater.targets_of_role('targets/role1')
|
||||
updated_targets = updater.updated_targets(targets_of_role1, destination_directory)
|
||||
|
||||
for target in updated_targets:
|
||||
updater.download_target(target, destination_directory)
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
# Example demonstrating an update that downloads a specific target.
|
||||
|
||||
updater.refresh()
|
||||
target = updater.target('LICENSE.txt')
|
||||
updated_target = updater.updated_targets([target], destination_directory)
|
||||
|
||||
for target in updated_target:
|
||||
updater.download_target(target, destination_directory)
|
||||
"""
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "9411398cb36dbce77727208a3ed8f634a194134a117c5980d3ff22bc54a25de4",
|
||||
"method": "evp",
|
||||
"sig": "61020111d9e1e1082d79690596d176d6eb5bad3bd0e695eb42930c39a018d18a64d52d7f4114272c8ee7c1b954708d95e0d7c6cafd95078d122dc67b3709484cef2d823376bbb9010f6cfa8034992df814c55c9f70e9411879ce7b54c4d6f1e4ed91ba328db0c26be0532d404358561ba347f1d55aad0a0eb83d9ba224b6760ad3d47dbcdc90d7ba7bf1597992288ec960a1181a1ffe1ff3b0eb7b5c4423bb1a20c01af2ddf5579e3a0704b4d7a58a8a0745edb6ce41c680239819b3772ae3f20486dc344ca7828c9581ba63af4ec11855dbf6213e2a260cc03b281659fa77c9fceeb0b7a25e1ea08bf1bbe5663bfb502c2145f6b5057005d989a8b35e56ae9b3f1f80c3c74f229d4c0bd3c807a7b6acb221ad42da90bf3137716a7d10aa9004e52ab74ceb706eeb6bb1834df1c994e83a64b3f6dae99a5586668803777d788fcc63e3ed3186c5b1186ec2dcf7fd2d7afe120470d667611ddae9b8617bd4ce1f2f0e6ae37b733a739c1d08814c53b25944f50b5be3f40488f683d3b89eb07127"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Release",
|
||||
"expires": "2013-12-17 00:31:02",
|
||||
"meta": {
|
||||
"root.txt": {
|
||||
"hashes": {
|
||||
"sha256": "72925bfeb643bd56c2368c890eb62af735d35f81d5518f42974c5d90863d4bad"
|
||||
},
|
||||
"length": 6571
|
||||
},
|
||||
"targets.txt": {
|
||||
"hashes": {
|
||||
"sha256": "e502a5d0079e59e122274f115fcb9a3c84f6d660bb50910fb3a757f58df00fe6"
|
||||
},
|
||||
"length": 1346
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:02"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "86e0a0c096d2244436911f15d89ee3a647b6978ca156f93b7eba730038e5327b",
|
||||
"method": "evp",
|
||||
"sig": "91fb132a9ebf78fb2a688178dc5dcbbbe389baab063dd34fa9e7ccf8cb613a9356eb78ded2f7a12d454e2311b71ca0c75a2548515f1d1d35c1206da478d1535326eb172383a2687087e12f1d19b13b6adf0b4e35b9b992fecef93b0841aafb0143b19fb0e6e2dae49cd4052ddb0c1bcc4efdf3adbede95a509f04057082d80241cdbb5cb190559ce0a57906017b6352cbc0c837ca0d3428519a776454767dbcd0af1cadc5020e9e2d90cd4b8eebd4ca06859486575401aaa8aa6c3603a642d678e1f5b7849949903b28bb9bf88fab62ca30fbd160368eb998ce1c8a90a29573f02f6fcaa13bf1baa1dd72295c4de3350f72f8abbdf6d780810957c38ff2ec499c5928f241f4339c835e71b0013f6dcc41d01a7d43d8def9f8de8a46a73f9845a7f82b2a856096d6cbb9639146b94828ce1bbbf1f4927ab4d4100292d689ccc6efcf9d8fe61077224f4984bdafca6a8848020104130dc18fdea7fb53052d9bc70f94589522e8504a40518b657288ca6c9efaaf6baa562e83bd1e671f8199cafe8"
|
||||
},
|
||||
{
|
||||
"keyid": "4ac0adf8fac99d3603b02ee88715b0b18ab9203c639a220f3db7aff78d784ab2",
|
||||
"method": "evp",
|
||||
"sig": "845528efb13d16600a492201d1d656d81ab819bab60d123b2dfc9bfc04647bcf8129872a7c1b0db21d9c1005e4cd55c91282f5410c9939ca3e8acb70d55eced6194c94152d70e00193298730b9019a3282196dd23543f15c1d6f5431efb53a1b04c3d40f0e177d7ad6ecafbda363e89d5dd4e2ce8b7ac6ab2808ff361d6dabeeb443358dfcb763aa9b9857826fc587a2c4b72a0ce7d0e2d073ea4f2fe453a5b72afbcf160e3029f969b290f2e0250225a2ce88895e10fcb6c1638c70bca5d2add74324de9d581e54e9b99c9c000904b55d37a775e8496ca97aae384b500c83e2694403f4687479fb2cc56d981afa2c45b9ce968817e168f87fc0d7419e973c4162baa2e59c339ce82ec5a565a4b3993b2f3e885a1d38240996834b530de306b600d2cfbcb2660ac903b5a7b6e270081fe72f98cf6e29047915ade88cde3c860a4d1637a4f8057a6b7815e21111388bd7a678c8b3221b8adcfc1f4629ff75ebadd3160abd88b71bbb175592cd99f0769bde1466174e0a8a63a07cefbba766fcef"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Root",
|
||||
"expires": "2014-12-17 00:31:01",
|
||||
"keys": {
|
||||
"4ac0adf8fac99d3603b02ee88715b0b18ab9203c639a220f3db7aff78d784ab2": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAuFzNjOV+tTnkMzB+UUWi\no/n0hWE5nKeBie+erzGHsRqYTwY2afsUORnogFkt2erdR9zR4TAWGMIvJbnDujQi\npkhUIueZB6vGCRBT2Mfk8vOUTQ+8Xc6FNCyhLZfJKL8fxEOAaF9HSOt3u9BugPVB\nL5QLc/fcWNgBiTErR/kcLH1LvaDeFijOuSC6VDq4GjONK70n1d/CtGmPYIMQj89L\nsIThUrREIH2oHRXFIUDJLHmAJXgw31chpC4CI9/ehnShcM1AaQTfpNwsOFkzNpfk\nCweYN3w9h6I+/3hPlD1oPluolSpz3MTQ2YXnRDBijptGBz4aPW2V6R5fE9Tx2xZr\neI2wlzsObFSvJ9V8guVc/yVM6PwCeVuQknzFo2xXYvQgzpKTSSgk1+0tiJqZkuIk\n7ENhglKwTL9vu40pTgE+gkKRC+CgDPwJ4+VMZJANsFPfCZxMw8+ddufTL70PtSp0\nKDRshTy1E4v8hMacSprTttDsXyyHswp3BARFkPZDy/c3AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"7e6d7b7d2d60f175d4e48b11347f8f648e2c58093cd47986531e80747bd2c559": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsCSACdXyAzAjYWk5acxx\nzlEZgftJULKXvzpcbEzCtwC5Izst23twvlKtGYYcJqw4sPKgxgnHy7WgUxKiVbLX\nQvop4kSKqwpfV4t8ZBHvvHypIzS6ZBknwNU4SvDeYdajbzOlNkPvrANtLyfpKtxT\nxuhmEP7lGFL5pIukNvTqIs2J+3NHNG9O0AU2iwydncohpB10utor6GRgkBj2d9N7\neZQrHyQPtMKodtmlzN2ZUwf859neBPvzd9iGLl+K2zfIHKc0AOyn2kUxFvM81FDy\nHaMuWFqfnKWDftAp8UKZqHFuljvt2zk/HH0t5w9mKiy68qL/jGAoaPcKpWgGAHN4\nTj/6ugSij6/GimYdgOr2taD5BWMyxSjyvCE0FvH/Gi+4+DT6Ll9Ri6K1iLOYjan3\njFsTMBkZ7Gdavc1vIkSgn3joZMGvdVK2JYzPy/ZJ15KEnAUbymnA+CyKAA+gSRzo\nYXSDM6JGlCci3pLl235gDu5lI03Wuf/GlPEwXs4pkry/AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"86e0a0c096d2244436911f15d89ee3a647b6978ca156f93b7eba730038e5327b": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA3yIiwviVYlg8NW0B8gxR\nltqmrKL0MfXjoq1UxtE0G/ySki6w8uOznnqiR+G4dOQvHbrTZytTq655v+BySCIC\nQWLyO4x7c2vm4APpiOhIfMjz23uxLhbR9ZVhk0dSv1wykP4O4TIGaRPfLTTTEADS\nqgIIXKOm4zjlztdIzjmlh5vLknI3w1VfRPYT2jBQSZ9Vld0SVNJz3JYW2ylI2jrP\njTPNnrb5GQ/pthrZZFGe1B/w7rfQvO7pA4BfNPT1TI8sL9NpFz8HXAE+bJ8EkP+a\ne01jHNhLtzJxDJRa+BXQtMh5QtJffrLNnwxBGPVBZdIOJm+FSf+D4v+W+Y/M/ayK\nI9TdOlKExYoWQ99tIgG4J7J8L88Mfh9+dduTtDT/jEfQCx4olxsqgJtxpzjTC3Nr\nDpm27djxeX74otXXNNOEN2Wzs/VvEBCWsHfElIyfdElYIImlzd2FyD1AQyhUoe01\nQOI9cjmmLybQKgIXgyoeQ0SRxZFNYAM3kPw4w5/JAsM7AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"9411398cb36dbce77727208a3ed8f634a194134a117c5980d3ff22bc54a25de4": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyFShMexVSDMDMs5wGt0G\nQBOR8XyTV8JpkCKdSRMW3IsSMueagdI754q202SbfIRa0JFah99roPxa3IAIHo0X\n3GUO9HJWp5gSrGcKUXrOiz6CMsGwSh6N9xVCNFqpc8EsPceG/hCaqIX1FIGizYdZ\nN2+LwpGvfpBQfAPUau0NREEA/HgFYB4UptOSyg+3kRFCcXr+nJmiDNc5qXp/HQZ2\nqn6yhuhLMh/I1KsqN4HQGYDOg8L7ybuRzEgGabI0/ZVSnVJpOeHogtH6WHPKGZXX\nA3aY0sr4l++DTwe2wCXTkVYzSBwG/AM/zfVwfazQZFHG2d2VnlS2VN+otbpv+qyG\nq+dF2TmqxnnaOoWTXsQ/XTiIHLlSmySE4WZR7pZREdx12ahgChTA0/0FVrM28DIb\nvh0T0NGyxfAGYEWDvr+xjFyks8RhgsA3ftUSkq7nrmsM4iJyvSN01la4MZNHBQGK\n+jSHvtn7AGMAkr4VY/uv4NWGZHiWt33oL/TK6EAHJmA9AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"c57c4439c8cd88e0571d651696b17c069702b637d59f2e9c4f88894d2cb5cabf": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAuN8verErqJFpNYjzWCaV\nXk8f4uow950aJTwt2+BMK97KMccgIpOTKemN80iiDHHLfsBmNozYpF2WuWtWpUGQ\nKlbnafbEuzmTrRSMjhEjTxfvGNTh4NPnfHCJFRql/C0Zln/1YVnRdF7qcc+C+ru8\nc0OhMT1Z4tu4JoqK3jz4Pf5jq+5Vm3SaJ+h0eBo/IArqDoXEHKVZDggsDRM9gVhv\nov79O43Qt3/pdP+o1hAhifHADs6nMse+0PrkP+bYMu9UQ5TqUmKDdaVnYETP7VYX\nDMG1Ipj5GGhPu4bJzahf1j+4RzFAsb8yIojjgHsKxD42rp9mAZt50tr2meSDEO2Z\nLtjH3zaPdaP5WCcQyHhHPVhibdyP8hLmw9FpSTW5pYRRDT8HHcfp/YTpE5TBKApC\n80io16g/FCveJL6pV4leLEZXeroKBB5SCCAPzXoAL7ITUHM3DSmNNcaf+ClXK8Wf\n+s/EUe3qGD2VF0/xs36rsxnsCwwspgjlh6/B+27/i2QRAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"release": {
|
||||
"keyids": [
|
||||
"9411398cb36dbce77727208a3ed8f634a194134a117c5980d3ff22bc54a25de4"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"root": {
|
||||
"keyids": [
|
||||
"86e0a0c096d2244436911f15d89ee3a647b6978ca156f93b7eba730038e5327b",
|
||||
"4ac0adf8fac99d3603b02ee88715b0b18ab9203c639a220f3db7aff78d784ab2"
|
||||
],
|
||||
"threshold": 2
|
||||
},
|
||||
"targets": {
|
||||
"keyids": [
|
||||
"7e6d7b7d2d60f175d4e48b11347f8f648e2c58093cd47986531e80747bd2c559"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"timestamp": {
|
||||
"keyids": [
|
||||
"c57c4439c8cd88e0571d651696b17c069702b637d59f2e9c4f88894d2cb5cabf"
|
||||
],
|
||||
"threshold": 1
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:01"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "7e6d7b7d2d60f175d4e48b11347f8f648e2c58093cd47986531e80747bd2c559",
|
||||
"method": "evp",
|
||||
"sig": "959294bc789a7490e235d6313507bcc8b4c6296a32fd923c370ba7529914d6a41c15b4857cd7c31a75e40af3e16ad66055cc79632828ecd08609ebb1f9dcba1b841ba713a3b1535644385b36d8c882192bfd7e64b94a7a71fa9d1b6976466946e08cce0257ed1b3ba7cf28dd7dc6315a6782d366eef0dde2a2222eb7ced1d6fa3680c51ab83490b46de49eb2e1793bbb08bb6b9c16511987beafb143032b7d86700a3a8ab6f62f29afb8da0bcf6f4fbf6e3b9299f9541b5b90d6f6e71ed8bb05fe14c5c6c6a949e22b3ca2fac00c981c18147fad2c92cb7b7a63e5fc9fa9636c0d94597850f4045147b88ae06f49fa1ba4b80ec09d5e9e13b090a6e877dc30d98bfeadf24402767b2c12f1ded8b886f9b8e09c3c4bf192bc40a2b7976580bfc513127f282a26ebb52d4ac29facf5cfc0d372064a0a06a63f50fd1ae932b1909ac5ebb611184c980a169ec4550e1b23da3169351fe42cb4db69865a027690f0489210b1ea3951fd0089cf46ca8cdc9564ba59031d1aa404f8cff157c08ca6bb3b"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Targets",
|
||||
"expires": "2013-12-17 00:31:02",
|
||||
"targets": {
|
||||
"LICENSE.txt": {
|
||||
"hashes": {
|
||||
"sha256": "cd65721176ce5fdbb05773c0b1349f993b94ce77a51062cfa7a78b34cc82fc71"
|
||||
},
|
||||
"length": 3286
|
||||
},
|
||||
"helloworld.py": {
|
||||
"hashes": {
|
||||
"sha256": "c7861802c53ca2ce7a9717fc1544a902508576799074be9cc21630553a9471da"
|
||||
},
|
||||
"length": 21
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:02"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "c57c4439c8cd88e0571d651696b17c069702b637d59f2e9c4f88894d2cb5cabf",
|
||||
"method": "evp",
|
||||
"sig": "401f0b342d8ca5d8ccda13ae006eccff6b970ccb5a40102d5688314c3ddcb62aaa5e71e5f711231e5f0db75ab8764ca7e8d9688736aec25c49575c2a224c3c281b5c5c6ba561e95ab7900f0928b6fa74655b11572d878cabcdbf475f3928d5898a41464739beb2296d01b18017f09658438e32791b6297dda9918716ee9011c5eca22be29b705b7dde33f0f29363eab5348e8b2026c415ea65e6d40c8068e499a3e27c1b85c3f9d15ed251e94b991e469675ce2dadfca21c05ac9f400679bd03c9ff4989b7dbfe696f995697511c0ba80830d51347792ef2a6bc2e8f6ae5f8623c08d616ea94cf2f88c03d8cf2e56f09e189748a66fad393464add092464a1f8842610d96122c372da00638fe90f0ffffe38b2002837f4993e59eb35bb942db2d1e93faa6a3f432be1fbaeb32d60cc612eac1b72d4d5b03c21c2042c720b412266b221bc8917353a7a9140ae4fe5cc6a703e9fb8f898b6cf45e12af440ee06c86d4aab92631f0ae1017a461141e4098df6e7dbda00624e502eb293987053d787"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Timestamp",
|
||||
"expires": "2013-12-17 00:31:02",
|
||||
"meta": {
|
||||
"release.txt": {
|
||||
"hashes": {
|
||||
"sha256": "3a5a6ec1f3530b21a68a5bbe42a7fa5422dae9c4f211a99c678208ddedce36e0"
|
||||
},
|
||||
"length": 1340
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:02"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "9411398cb36dbce77727208a3ed8f634a194134a117c5980d3ff22bc54a25de4",
|
||||
"method": "evp",
|
||||
"sig": "61020111d9e1e1082d79690596d176d6eb5bad3bd0e695eb42930c39a018d18a64d52d7f4114272c8ee7c1b954708d95e0d7c6cafd95078d122dc67b3709484cef2d823376bbb9010f6cfa8034992df814c55c9f70e9411879ce7b54c4d6f1e4ed91ba328db0c26be0532d404358561ba347f1d55aad0a0eb83d9ba224b6760ad3d47dbcdc90d7ba7bf1597992288ec960a1181a1ffe1ff3b0eb7b5c4423bb1a20c01af2ddf5579e3a0704b4d7a58a8a0745edb6ce41c680239819b3772ae3f20486dc344ca7828c9581ba63af4ec11855dbf6213e2a260cc03b281659fa77c9fceeb0b7a25e1ea08bf1bbe5663bfb502c2145f6b5057005d989a8b35e56ae9b3f1f80c3c74f229d4c0bd3c807a7b6acb221ad42da90bf3137716a7d10aa9004e52ab74ceb706eeb6bb1834df1c994e83a64b3f6dae99a5586668803777d788fcc63e3ed3186c5b1186ec2dcf7fd2d7afe120470d667611ddae9b8617bd4ce1f2f0e6ae37b733a739c1d08814c53b25944f50b5be3f40488f683d3b89eb07127"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Release",
|
||||
"expires": "2013-12-17 00:31:02",
|
||||
"meta": {
|
||||
"root.txt": {
|
||||
"hashes": {
|
||||
"sha256": "72925bfeb643bd56c2368c890eb62af735d35f81d5518f42974c5d90863d4bad"
|
||||
},
|
||||
"length": 6571
|
||||
},
|
||||
"targets.txt": {
|
||||
"hashes": {
|
||||
"sha256": "e502a5d0079e59e122274f115fcb9a3c84f6d660bb50910fb3a757f58df00fe6"
|
||||
},
|
||||
"length": 1346
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:02"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "86e0a0c096d2244436911f15d89ee3a647b6978ca156f93b7eba730038e5327b",
|
||||
"method": "evp",
|
||||
"sig": "91fb132a9ebf78fb2a688178dc5dcbbbe389baab063dd34fa9e7ccf8cb613a9356eb78ded2f7a12d454e2311b71ca0c75a2548515f1d1d35c1206da478d1535326eb172383a2687087e12f1d19b13b6adf0b4e35b9b992fecef93b0841aafb0143b19fb0e6e2dae49cd4052ddb0c1bcc4efdf3adbede95a509f04057082d80241cdbb5cb190559ce0a57906017b6352cbc0c837ca0d3428519a776454767dbcd0af1cadc5020e9e2d90cd4b8eebd4ca06859486575401aaa8aa6c3603a642d678e1f5b7849949903b28bb9bf88fab62ca30fbd160368eb998ce1c8a90a29573f02f6fcaa13bf1baa1dd72295c4de3350f72f8abbdf6d780810957c38ff2ec499c5928f241f4339c835e71b0013f6dcc41d01a7d43d8def9f8de8a46a73f9845a7f82b2a856096d6cbb9639146b94828ce1bbbf1f4927ab4d4100292d689ccc6efcf9d8fe61077224f4984bdafca6a8848020104130dc18fdea7fb53052d9bc70f94589522e8504a40518b657288ca6c9efaaf6baa562e83bd1e671f8199cafe8"
|
||||
},
|
||||
{
|
||||
"keyid": "4ac0adf8fac99d3603b02ee88715b0b18ab9203c639a220f3db7aff78d784ab2",
|
||||
"method": "evp",
|
||||
"sig": "845528efb13d16600a492201d1d656d81ab819bab60d123b2dfc9bfc04647bcf8129872a7c1b0db21d9c1005e4cd55c91282f5410c9939ca3e8acb70d55eced6194c94152d70e00193298730b9019a3282196dd23543f15c1d6f5431efb53a1b04c3d40f0e177d7ad6ecafbda363e89d5dd4e2ce8b7ac6ab2808ff361d6dabeeb443358dfcb763aa9b9857826fc587a2c4b72a0ce7d0e2d073ea4f2fe453a5b72afbcf160e3029f969b290f2e0250225a2ce88895e10fcb6c1638c70bca5d2add74324de9d581e54e9b99c9c000904b55d37a775e8496ca97aae384b500c83e2694403f4687479fb2cc56d981afa2c45b9ce968817e168f87fc0d7419e973c4162baa2e59c339ce82ec5a565a4b3993b2f3e885a1d38240996834b530de306b600d2cfbcb2660ac903b5a7b6e270081fe72f98cf6e29047915ade88cde3c860a4d1637a4f8057a6b7815e21111388bd7a678c8b3221b8adcfc1f4629ff75ebadd3160abd88b71bbb175592cd99f0769bde1466174e0a8a63a07cefbba766fcef"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Root",
|
||||
"expires": "2014-12-17 00:31:01",
|
||||
"keys": {
|
||||
"4ac0adf8fac99d3603b02ee88715b0b18ab9203c639a220f3db7aff78d784ab2": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAuFzNjOV+tTnkMzB+UUWi\no/n0hWE5nKeBie+erzGHsRqYTwY2afsUORnogFkt2erdR9zR4TAWGMIvJbnDujQi\npkhUIueZB6vGCRBT2Mfk8vOUTQ+8Xc6FNCyhLZfJKL8fxEOAaF9HSOt3u9BugPVB\nL5QLc/fcWNgBiTErR/kcLH1LvaDeFijOuSC6VDq4GjONK70n1d/CtGmPYIMQj89L\nsIThUrREIH2oHRXFIUDJLHmAJXgw31chpC4CI9/ehnShcM1AaQTfpNwsOFkzNpfk\nCweYN3w9h6I+/3hPlD1oPluolSpz3MTQ2YXnRDBijptGBz4aPW2V6R5fE9Tx2xZr\neI2wlzsObFSvJ9V8guVc/yVM6PwCeVuQknzFo2xXYvQgzpKTSSgk1+0tiJqZkuIk\n7ENhglKwTL9vu40pTgE+gkKRC+CgDPwJ4+VMZJANsFPfCZxMw8+ddufTL70PtSp0\nKDRshTy1E4v8hMacSprTttDsXyyHswp3BARFkPZDy/c3AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"7e6d7b7d2d60f175d4e48b11347f8f648e2c58093cd47986531e80747bd2c559": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsCSACdXyAzAjYWk5acxx\nzlEZgftJULKXvzpcbEzCtwC5Izst23twvlKtGYYcJqw4sPKgxgnHy7WgUxKiVbLX\nQvop4kSKqwpfV4t8ZBHvvHypIzS6ZBknwNU4SvDeYdajbzOlNkPvrANtLyfpKtxT\nxuhmEP7lGFL5pIukNvTqIs2J+3NHNG9O0AU2iwydncohpB10utor6GRgkBj2d9N7\neZQrHyQPtMKodtmlzN2ZUwf859neBPvzd9iGLl+K2zfIHKc0AOyn2kUxFvM81FDy\nHaMuWFqfnKWDftAp8UKZqHFuljvt2zk/HH0t5w9mKiy68qL/jGAoaPcKpWgGAHN4\nTj/6ugSij6/GimYdgOr2taD5BWMyxSjyvCE0FvH/Gi+4+DT6Ll9Ri6K1iLOYjan3\njFsTMBkZ7Gdavc1vIkSgn3joZMGvdVK2JYzPy/ZJ15KEnAUbymnA+CyKAA+gSRzo\nYXSDM6JGlCci3pLl235gDu5lI03Wuf/GlPEwXs4pkry/AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"86e0a0c096d2244436911f15d89ee3a647b6978ca156f93b7eba730038e5327b": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA3yIiwviVYlg8NW0B8gxR\nltqmrKL0MfXjoq1UxtE0G/ySki6w8uOznnqiR+G4dOQvHbrTZytTq655v+BySCIC\nQWLyO4x7c2vm4APpiOhIfMjz23uxLhbR9ZVhk0dSv1wykP4O4TIGaRPfLTTTEADS\nqgIIXKOm4zjlztdIzjmlh5vLknI3w1VfRPYT2jBQSZ9Vld0SVNJz3JYW2ylI2jrP\njTPNnrb5GQ/pthrZZFGe1B/w7rfQvO7pA4BfNPT1TI8sL9NpFz8HXAE+bJ8EkP+a\ne01jHNhLtzJxDJRa+BXQtMh5QtJffrLNnwxBGPVBZdIOJm+FSf+D4v+W+Y/M/ayK\nI9TdOlKExYoWQ99tIgG4J7J8L88Mfh9+dduTtDT/jEfQCx4olxsqgJtxpzjTC3Nr\nDpm27djxeX74otXXNNOEN2Wzs/VvEBCWsHfElIyfdElYIImlzd2FyD1AQyhUoe01\nQOI9cjmmLybQKgIXgyoeQ0SRxZFNYAM3kPw4w5/JAsM7AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"9411398cb36dbce77727208a3ed8f634a194134a117c5980d3ff22bc54a25de4": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyFShMexVSDMDMs5wGt0G\nQBOR8XyTV8JpkCKdSRMW3IsSMueagdI754q202SbfIRa0JFah99roPxa3IAIHo0X\n3GUO9HJWp5gSrGcKUXrOiz6CMsGwSh6N9xVCNFqpc8EsPceG/hCaqIX1FIGizYdZ\nN2+LwpGvfpBQfAPUau0NREEA/HgFYB4UptOSyg+3kRFCcXr+nJmiDNc5qXp/HQZ2\nqn6yhuhLMh/I1KsqN4HQGYDOg8L7ybuRzEgGabI0/ZVSnVJpOeHogtH6WHPKGZXX\nA3aY0sr4l++DTwe2wCXTkVYzSBwG/AM/zfVwfazQZFHG2d2VnlS2VN+otbpv+qyG\nq+dF2TmqxnnaOoWTXsQ/XTiIHLlSmySE4WZR7pZREdx12ahgChTA0/0FVrM28DIb\nvh0T0NGyxfAGYEWDvr+xjFyks8RhgsA3ftUSkq7nrmsM4iJyvSN01la4MZNHBQGK\n+jSHvtn7AGMAkr4VY/uv4NWGZHiWt33oL/TK6EAHJmA9AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"c57c4439c8cd88e0571d651696b17c069702b637d59f2e9c4f88894d2cb5cabf": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAuN8verErqJFpNYjzWCaV\nXk8f4uow950aJTwt2+BMK97KMccgIpOTKemN80iiDHHLfsBmNozYpF2WuWtWpUGQ\nKlbnafbEuzmTrRSMjhEjTxfvGNTh4NPnfHCJFRql/C0Zln/1YVnRdF7qcc+C+ru8\nc0OhMT1Z4tu4JoqK3jz4Pf5jq+5Vm3SaJ+h0eBo/IArqDoXEHKVZDggsDRM9gVhv\nov79O43Qt3/pdP+o1hAhifHADs6nMse+0PrkP+bYMu9UQ5TqUmKDdaVnYETP7VYX\nDMG1Ipj5GGhPu4bJzahf1j+4RzFAsb8yIojjgHsKxD42rp9mAZt50tr2meSDEO2Z\nLtjH3zaPdaP5WCcQyHhHPVhibdyP8hLmw9FpSTW5pYRRDT8HHcfp/YTpE5TBKApC\n80io16g/FCveJL6pV4leLEZXeroKBB5SCCAPzXoAL7ITUHM3DSmNNcaf+ClXK8Wf\n+s/EUe3qGD2VF0/xs36rsxnsCwwspgjlh6/B+27/i2QRAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"release": {
|
||||
"keyids": [
|
||||
"9411398cb36dbce77727208a3ed8f634a194134a117c5980d3ff22bc54a25de4"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"root": {
|
||||
"keyids": [
|
||||
"86e0a0c096d2244436911f15d89ee3a647b6978ca156f93b7eba730038e5327b",
|
||||
"4ac0adf8fac99d3603b02ee88715b0b18ab9203c639a220f3db7aff78d784ab2"
|
||||
],
|
||||
"threshold": 2
|
||||
},
|
||||
"targets": {
|
||||
"keyids": [
|
||||
"7e6d7b7d2d60f175d4e48b11347f8f648e2c58093cd47986531e80747bd2c559"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"timestamp": {
|
||||
"keyids": [
|
||||
"c57c4439c8cd88e0571d651696b17c069702b637d59f2e9c4f88894d2cb5cabf"
|
||||
],
|
||||
"threshold": 1
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:01"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "7e6d7b7d2d60f175d4e48b11347f8f648e2c58093cd47986531e80747bd2c559",
|
||||
"method": "evp",
|
||||
"sig": "959294bc789a7490e235d6313507bcc8b4c6296a32fd923c370ba7529914d6a41c15b4857cd7c31a75e40af3e16ad66055cc79632828ecd08609ebb1f9dcba1b841ba713a3b1535644385b36d8c882192bfd7e64b94a7a71fa9d1b6976466946e08cce0257ed1b3ba7cf28dd7dc6315a6782d366eef0dde2a2222eb7ced1d6fa3680c51ab83490b46de49eb2e1793bbb08bb6b9c16511987beafb143032b7d86700a3a8ab6f62f29afb8da0bcf6f4fbf6e3b9299f9541b5b90d6f6e71ed8bb05fe14c5c6c6a949e22b3ca2fac00c981c18147fad2c92cb7b7a63e5fc9fa9636c0d94597850f4045147b88ae06f49fa1ba4b80ec09d5e9e13b090a6e877dc30d98bfeadf24402767b2c12f1ded8b886f9b8e09c3c4bf192bc40a2b7976580bfc513127f282a26ebb52d4ac29facf5cfc0d372064a0a06a63f50fd1ae932b1909ac5ebb611184c980a169ec4550e1b23da3169351fe42cb4db69865a027690f0489210b1ea3951fd0089cf46ca8cdc9564ba59031d1aa404f8cff157c08ca6bb3b"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Targets",
|
||||
"expires": "2013-12-17 00:31:02",
|
||||
"targets": {
|
||||
"LICENSE.txt": {
|
||||
"hashes": {
|
||||
"sha256": "cd65721176ce5fdbb05773c0b1349f993b94ce77a51062cfa7a78b34cc82fc71"
|
||||
},
|
||||
"length": 3286
|
||||
},
|
||||
"helloworld.py": {
|
||||
"hashes": {
|
||||
"sha256": "c7861802c53ca2ce7a9717fc1544a902508576799074be9cc21630553a9471da"
|
||||
},
|
||||
"length": 21
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:02"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "c57c4439c8cd88e0571d651696b17c069702b637d59f2e9c4f88894d2cb5cabf",
|
||||
"method": "evp",
|
||||
"sig": "401f0b342d8ca5d8ccda13ae006eccff6b970ccb5a40102d5688314c3ddcb62aaa5e71e5f711231e5f0db75ab8764ca7e8d9688736aec25c49575c2a224c3c281b5c5c6ba561e95ab7900f0928b6fa74655b11572d878cabcdbf475f3928d5898a41464739beb2296d01b18017f09658438e32791b6297dda9918716ee9011c5eca22be29b705b7dde33f0f29363eab5348e8b2026c415ea65e6d40c8068e499a3e27c1b85c3f9d15ed251e94b991e469675ce2dadfca21c05ac9f400679bd03c9ff4989b7dbfe696f995697511c0ba80830d51347792ef2a6bc2e8f6ae5f8623c08d616ea94cf2f88c03d8cf2e56f09e189748a66fad393464add092464a1f8842610d96122c372da00638fe90f0ffffe38b2002837f4993e59eb35bb942db2d1e93faa6a3f432be1fbaeb32d60cc612eac1b72d4d5b03c21c2042c720b412266b221bc8917353a7a9140ae4fe5cc6a703e9fb8f898b6cf45e12af440ee06c86d4aab92631f0ae1017a461141e4098df6e7dbda00624e502eb293987053d787"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Timestamp",
|
||||
"expires": "2013-12-17 00:31:02",
|
||||
"meta": {
|
||||
"release.txt": {
|
||||
"hashes": {
|
||||
"sha256": "3a5a6ec1f3530b21a68a5bbe42a7fa5422dae9c4f211a99c678208ddedce36e0"
|
||||
},
|
||||
"length": 1340
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:02"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
23
src/tuf/examples/repository_basic/repository/config.cfg
Normal file
23
src/tuf/examples/repository_basic/repository/config.cfg
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
[expiration]
|
||||
days = 730
|
||||
years = 0
|
||||
minutes = 0
|
||||
hours = 0
|
||||
seconds = 0
|
||||
|
||||
[release]
|
||||
keyids = 9411398cb36dbce77727208a3ed8f634a194134a117c5980d3ff22bc54a25de4
|
||||
threshold = 1
|
||||
|
||||
[timestamp]
|
||||
keyids = c57c4439c8cd88e0571d651696b17c069702b637d59f2e9c4f88894d2cb5cabf
|
||||
threshold = 1
|
||||
|
||||
[root]
|
||||
keyids = 86e0a0c096d2244436911f15d89ee3a647b6978ca156f93b7eba730038e5327b,4ac0adf8fac99d3603b02ee88715b0b18ab9203c639a220f3db7aff78d784ab2
|
||||
threshold = 2
|
||||
|
||||
[targets]
|
||||
keyids = 7e6d7b7d2d60f175d4e48b11347f8f648e2c58093cd47986531e80747bd2c559
|
||||
threshold = 1
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "9411398cb36dbce77727208a3ed8f634a194134a117c5980d3ff22bc54a25de4",
|
||||
"method": "evp",
|
||||
"sig": "61020111d9e1e1082d79690596d176d6eb5bad3bd0e695eb42930c39a018d18a64d52d7f4114272c8ee7c1b954708d95e0d7c6cafd95078d122dc67b3709484cef2d823376bbb9010f6cfa8034992df814c55c9f70e9411879ce7b54c4d6f1e4ed91ba328db0c26be0532d404358561ba347f1d55aad0a0eb83d9ba224b6760ad3d47dbcdc90d7ba7bf1597992288ec960a1181a1ffe1ff3b0eb7b5c4423bb1a20c01af2ddf5579e3a0704b4d7a58a8a0745edb6ce41c680239819b3772ae3f20486dc344ca7828c9581ba63af4ec11855dbf6213e2a260cc03b281659fa77c9fceeb0b7a25e1ea08bf1bbe5663bfb502c2145f6b5057005d989a8b35e56ae9b3f1f80c3c74f229d4c0bd3c807a7b6acb221ad42da90bf3137716a7d10aa9004e52ab74ceb706eeb6bb1834df1c994e83a64b3f6dae99a5586668803777d788fcc63e3ed3186c5b1186ec2dcf7fd2d7afe120470d667611ddae9b8617bd4ce1f2f0e6ae37b733a739c1d08814c53b25944f50b5be3f40488f683d3b89eb07127"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Release",
|
||||
"expires": "2013-12-17 00:31:02",
|
||||
"meta": {
|
||||
"root.txt": {
|
||||
"hashes": {
|
||||
"sha256": "72925bfeb643bd56c2368c890eb62af735d35f81d5518f42974c5d90863d4bad"
|
||||
},
|
||||
"length": 6571
|
||||
},
|
||||
"targets.txt": {
|
||||
"hashes": {
|
||||
"sha256": "e502a5d0079e59e122274f115fcb9a3c84f6d660bb50910fb3a757f58df00fe6"
|
||||
},
|
||||
"length": 1346
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:02"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "86e0a0c096d2244436911f15d89ee3a647b6978ca156f93b7eba730038e5327b",
|
||||
"method": "evp",
|
||||
"sig": "91fb132a9ebf78fb2a688178dc5dcbbbe389baab063dd34fa9e7ccf8cb613a9356eb78ded2f7a12d454e2311b71ca0c75a2548515f1d1d35c1206da478d1535326eb172383a2687087e12f1d19b13b6adf0b4e35b9b992fecef93b0841aafb0143b19fb0e6e2dae49cd4052ddb0c1bcc4efdf3adbede95a509f04057082d80241cdbb5cb190559ce0a57906017b6352cbc0c837ca0d3428519a776454767dbcd0af1cadc5020e9e2d90cd4b8eebd4ca06859486575401aaa8aa6c3603a642d678e1f5b7849949903b28bb9bf88fab62ca30fbd160368eb998ce1c8a90a29573f02f6fcaa13bf1baa1dd72295c4de3350f72f8abbdf6d780810957c38ff2ec499c5928f241f4339c835e71b0013f6dcc41d01a7d43d8def9f8de8a46a73f9845a7f82b2a856096d6cbb9639146b94828ce1bbbf1f4927ab4d4100292d689ccc6efcf9d8fe61077224f4984bdafca6a8848020104130dc18fdea7fb53052d9bc70f94589522e8504a40518b657288ca6c9efaaf6baa562e83bd1e671f8199cafe8"
|
||||
},
|
||||
{
|
||||
"keyid": "4ac0adf8fac99d3603b02ee88715b0b18ab9203c639a220f3db7aff78d784ab2",
|
||||
"method": "evp",
|
||||
"sig": "845528efb13d16600a492201d1d656d81ab819bab60d123b2dfc9bfc04647bcf8129872a7c1b0db21d9c1005e4cd55c91282f5410c9939ca3e8acb70d55eced6194c94152d70e00193298730b9019a3282196dd23543f15c1d6f5431efb53a1b04c3d40f0e177d7ad6ecafbda363e89d5dd4e2ce8b7ac6ab2808ff361d6dabeeb443358dfcb763aa9b9857826fc587a2c4b72a0ce7d0e2d073ea4f2fe453a5b72afbcf160e3029f969b290f2e0250225a2ce88895e10fcb6c1638c70bca5d2add74324de9d581e54e9b99c9c000904b55d37a775e8496ca97aae384b500c83e2694403f4687479fb2cc56d981afa2c45b9ce968817e168f87fc0d7419e973c4162baa2e59c339ce82ec5a565a4b3993b2f3e885a1d38240996834b530de306b600d2cfbcb2660ac903b5a7b6e270081fe72f98cf6e29047915ade88cde3c860a4d1637a4f8057a6b7815e21111388bd7a678c8b3221b8adcfc1f4629ff75ebadd3160abd88b71bbb175592cd99f0769bde1466174e0a8a63a07cefbba766fcef"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Root",
|
||||
"expires": "2014-12-17 00:31:01",
|
||||
"keys": {
|
||||
"4ac0adf8fac99d3603b02ee88715b0b18ab9203c639a220f3db7aff78d784ab2": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAuFzNjOV+tTnkMzB+UUWi\no/n0hWE5nKeBie+erzGHsRqYTwY2afsUORnogFkt2erdR9zR4TAWGMIvJbnDujQi\npkhUIueZB6vGCRBT2Mfk8vOUTQ+8Xc6FNCyhLZfJKL8fxEOAaF9HSOt3u9BugPVB\nL5QLc/fcWNgBiTErR/kcLH1LvaDeFijOuSC6VDq4GjONK70n1d/CtGmPYIMQj89L\nsIThUrREIH2oHRXFIUDJLHmAJXgw31chpC4CI9/ehnShcM1AaQTfpNwsOFkzNpfk\nCweYN3w9h6I+/3hPlD1oPluolSpz3MTQ2YXnRDBijptGBz4aPW2V6R5fE9Tx2xZr\neI2wlzsObFSvJ9V8guVc/yVM6PwCeVuQknzFo2xXYvQgzpKTSSgk1+0tiJqZkuIk\n7ENhglKwTL9vu40pTgE+gkKRC+CgDPwJ4+VMZJANsFPfCZxMw8+ddufTL70PtSp0\nKDRshTy1E4v8hMacSprTttDsXyyHswp3BARFkPZDy/c3AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"7e6d7b7d2d60f175d4e48b11347f8f648e2c58093cd47986531e80747bd2c559": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsCSACdXyAzAjYWk5acxx\nzlEZgftJULKXvzpcbEzCtwC5Izst23twvlKtGYYcJqw4sPKgxgnHy7WgUxKiVbLX\nQvop4kSKqwpfV4t8ZBHvvHypIzS6ZBknwNU4SvDeYdajbzOlNkPvrANtLyfpKtxT\nxuhmEP7lGFL5pIukNvTqIs2J+3NHNG9O0AU2iwydncohpB10utor6GRgkBj2d9N7\neZQrHyQPtMKodtmlzN2ZUwf859neBPvzd9iGLl+K2zfIHKc0AOyn2kUxFvM81FDy\nHaMuWFqfnKWDftAp8UKZqHFuljvt2zk/HH0t5w9mKiy68qL/jGAoaPcKpWgGAHN4\nTj/6ugSij6/GimYdgOr2taD5BWMyxSjyvCE0FvH/Gi+4+DT6Ll9Ri6K1iLOYjan3\njFsTMBkZ7Gdavc1vIkSgn3joZMGvdVK2JYzPy/ZJ15KEnAUbymnA+CyKAA+gSRzo\nYXSDM6JGlCci3pLl235gDu5lI03Wuf/GlPEwXs4pkry/AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"86e0a0c096d2244436911f15d89ee3a647b6978ca156f93b7eba730038e5327b": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA3yIiwviVYlg8NW0B8gxR\nltqmrKL0MfXjoq1UxtE0G/ySki6w8uOznnqiR+G4dOQvHbrTZytTq655v+BySCIC\nQWLyO4x7c2vm4APpiOhIfMjz23uxLhbR9ZVhk0dSv1wykP4O4TIGaRPfLTTTEADS\nqgIIXKOm4zjlztdIzjmlh5vLknI3w1VfRPYT2jBQSZ9Vld0SVNJz3JYW2ylI2jrP\njTPNnrb5GQ/pthrZZFGe1B/w7rfQvO7pA4BfNPT1TI8sL9NpFz8HXAE+bJ8EkP+a\ne01jHNhLtzJxDJRa+BXQtMh5QtJffrLNnwxBGPVBZdIOJm+FSf+D4v+W+Y/M/ayK\nI9TdOlKExYoWQ99tIgG4J7J8L88Mfh9+dduTtDT/jEfQCx4olxsqgJtxpzjTC3Nr\nDpm27djxeX74otXXNNOEN2Wzs/VvEBCWsHfElIyfdElYIImlzd2FyD1AQyhUoe01\nQOI9cjmmLybQKgIXgyoeQ0SRxZFNYAM3kPw4w5/JAsM7AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"9411398cb36dbce77727208a3ed8f634a194134a117c5980d3ff22bc54a25de4": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyFShMexVSDMDMs5wGt0G\nQBOR8XyTV8JpkCKdSRMW3IsSMueagdI754q202SbfIRa0JFah99roPxa3IAIHo0X\n3GUO9HJWp5gSrGcKUXrOiz6CMsGwSh6N9xVCNFqpc8EsPceG/hCaqIX1FIGizYdZ\nN2+LwpGvfpBQfAPUau0NREEA/HgFYB4UptOSyg+3kRFCcXr+nJmiDNc5qXp/HQZ2\nqn6yhuhLMh/I1KsqN4HQGYDOg8L7ybuRzEgGabI0/ZVSnVJpOeHogtH6WHPKGZXX\nA3aY0sr4l++DTwe2wCXTkVYzSBwG/AM/zfVwfazQZFHG2d2VnlS2VN+otbpv+qyG\nq+dF2TmqxnnaOoWTXsQ/XTiIHLlSmySE4WZR7pZREdx12ahgChTA0/0FVrM28DIb\nvh0T0NGyxfAGYEWDvr+xjFyks8RhgsA3ftUSkq7nrmsM4iJyvSN01la4MZNHBQGK\n+jSHvtn7AGMAkr4VY/uv4NWGZHiWt33oL/TK6EAHJmA9AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"c57c4439c8cd88e0571d651696b17c069702b637d59f2e9c4f88894d2cb5cabf": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAuN8verErqJFpNYjzWCaV\nXk8f4uow950aJTwt2+BMK97KMccgIpOTKemN80iiDHHLfsBmNozYpF2WuWtWpUGQ\nKlbnafbEuzmTrRSMjhEjTxfvGNTh4NPnfHCJFRql/C0Zln/1YVnRdF7qcc+C+ru8\nc0OhMT1Z4tu4JoqK3jz4Pf5jq+5Vm3SaJ+h0eBo/IArqDoXEHKVZDggsDRM9gVhv\nov79O43Qt3/pdP+o1hAhifHADs6nMse+0PrkP+bYMu9UQ5TqUmKDdaVnYETP7VYX\nDMG1Ipj5GGhPu4bJzahf1j+4RzFAsb8yIojjgHsKxD42rp9mAZt50tr2meSDEO2Z\nLtjH3zaPdaP5WCcQyHhHPVhibdyP8hLmw9FpSTW5pYRRDT8HHcfp/YTpE5TBKApC\n80io16g/FCveJL6pV4leLEZXeroKBB5SCCAPzXoAL7ITUHM3DSmNNcaf+ClXK8Wf\n+s/EUe3qGD2VF0/xs36rsxnsCwwspgjlh6/B+27/i2QRAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"release": {
|
||||
"keyids": [
|
||||
"9411398cb36dbce77727208a3ed8f634a194134a117c5980d3ff22bc54a25de4"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"root": {
|
||||
"keyids": [
|
||||
"86e0a0c096d2244436911f15d89ee3a647b6978ca156f93b7eba730038e5327b",
|
||||
"4ac0adf8fac99d3603b02ee88715b0b18ab9203c639a220f3db7aff78d784ab2"
|
||||
],
|
||||
"threshold": 2
|
||||
},
|
||||
"targets": {
|
||||
"keyids": [
|
||||
"7e6d7b7d2d60f175d4e48b11347f8f648e2c58093cd47986531e80747bd2c559"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"timestamp": {
|
||||
"keyids": [
|
||||
"c57c4439c8cd88e0571d651696b17c069702b637d59f2e9c4f88894d2cb5cabf"
|
||||
],
|
||||
"threshold": 1
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:01"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "7e6d7b7d2d60f175d4e48b11347f8f648e2c58093cd47986531e80747bd2c559",
|
||||
"method": "evp",
|
||||
"sig": "959294bc789a7490e235d6313507bcc8b4c6296a32fd923c370ba7529914d6a41c15b4857cd7c31a75e40af3e16ad66055cc79632828ecd08609ebb1f9dcba1b841ba713a3b1535644385b36d8c882192bfd7e64b94a7a71fa9d1b6976466946e08cce0257ed1b3ba7cf28dd7dc6315a6782d366eef0dde2a2222eb7ced1d6fa3680c51ab83490b46de49eb2e1793bbb08bb6b9c16511987beafb143032b7d86700a3a8ab6f62f29afb8da0bcf6f4fbf6e3b9299f9541b5b90d6f6e71ed8bb05fe14c5c6c6a949e22b3ca2fac00c981c18147fad2c92cb7b7a63e5fc9fa9636c0d94597850f4045147b88ae06f49fa1ba4b80ec09d5e9e13b090a6e877dc30d98bfeadf24402767b2c12f1ded8b886f9b8e09c3c4bf192bc40a2b7976580bfc513127f282a26ebb52d4ac29facf5cfc0d372064a0a06a63f50fd1ae932b1909ac5ebb611184c980a169ec4550e1b23da3169351fe42cb4db69865a027690f0489210b1ea3951fd0089cf46ca8cdc9564ba59031d1aa404f8cff157c08ca6bb3b"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Targets",
|
||||
"expires": "2013-12-17 00:31:02",
|
||||
"targets": {
|
||||
"LICENSE.txt": {
|
||||
"hashes": {
|
||||
"sha256": "cd65721176ce5fdbb05773c0b1349f993b94ce77a51062cfa7a78b34cc82fc71"
|
||||
},
|
||||
"length": 3286
|
||||
},
|
||||
"helloworld.py": {
|
||||
"hashes": {
|
||||
"sha256": "c7861802c53ca2ce7a9717fc1544a902508576799074be9cc21630553a9471da"
|
||||
},
|
||||
"length": 21
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:02"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "c57c4439c8cd88e0571d651696b17c069702b637d59f2e9c4f88894d2cb5cabf",
|
||||
"method": "evp",
|
||||
"sig": "401f0b342d8ca5d8ccda13ae006eccff6b970ccb5a40102d5688314c3ddcb62aaa5e71e5f711231e5f0db75ab8764ca7e8d9688736aec25c49575c2a224c3c281b5c5c6ba561e95ab7900f0928b6fa74655b11572d878cabcdbf475f3928d5898a41464739beb2296d01b18017f09658438e32791b6297dda9918716ee9011c5eca22be29b705b7dde33f0f29363eab5348e8b2026c415ea65e6d40c8068e499a3e27c1b85c3f9d15ed251e94b991e469675ce2dadfca21c05ac9f400679bd03c9ff4989b7dbfe696f995697511c0ba80830d51347792ef2a6bc2e8f6ae5f8623c08d616ea94cf2f88c03d8cf2e56f09e189748a66fad393464add092464a1f8842610d96122c372da00638fe90f0ffffe38b2002837f4993e59eb35bb942db2d1e93faa6a3f432be1fbaeb32d60cc612eac1b72d4d5b03c21c2042c720b412266b221bc8917353a7a9140ae4fe5cc6a703e9fb8f898b6cf45e12af440ee06c86d4aab92631f0ae1017a461141e4098df6e7dbda00624e502eb293987053d787"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Timestamp",
|
||||
"expires": "2013-12-17 00:31:02",
|
||||
"meta": {
|
||||
"release.txt": {
|
||||
"hashes": {
|
||||
"sha256": "3a5a6ec1f3530b21a68a5bbe42a7fa5422dae9c4f211a99c678208ddedce36e0"
|
||||
},
|
||||
"length": 1340
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 00:31:02"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
This file contains the license for TUF: The Update Framework.
|
||||
|
||||
It also lists license information for components and source
|
||||
code used by TUF: The Update Framework.
|
||||
|
||||
If you got this file as a part of a larger bundle,
|
||||
there may be other license terms that you should be aware of.
|
||||
|
||||
===============================================================================
|
||||
TUF: The Update Framework is distributed under this license:
|
||||
|
||||
Copyright (c) 2010, Justin Samuel and Justin Cappos.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and/or hardware specification (the “Work”) to deal in the Work
|
||||
without restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies of the Work,
|
||||
and to permit persons to whom the Work is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Work.
|
||||
|
||||
THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER
|
||||
DEALINGS IN THE WORK.
|
||||
===============================================================================
|
||||
Many files are modified from Thandy and are licensed under the
|
||||
following license:
|
||||
|
||||
Thandy is distributed under this license:
|
||||
|
||||
Copyright (c) 2008, The Tor Project, Inc.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* Neither the names of the copyright owners nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
===============================================================================
|
||||
1
src/tuf/examples/repository_basic/repository/targets/helloworld.py
Executable file
1
src/tuf/examples/repository_basic/repository/targets/helloworld.py
Executable file
|
|
@ -0,0 +1 @@
|
|||
print 'hello world!'
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "b6422d4c8a4acb07317b032b89f7d03cc8b42a8b464355141d06a61c13849d92",
|
||||
"method": "evp",
|
||||
"sig": "4d8826441628b5d024478808ccb972a6869e357e31ce6770f1b1e5abf93640326f16441ded5c43a99beed7326757e1aec65f72707e9a38a52d8138d5880e628e6a4275fd245c12ca5c846ae031b1990a78699ed425efa0deb5187567661d4eb213b3977751d5b41550d795c063b07fdf601a69f0ec47a11f8ece99e1784bd8b2ab23766dc562dae739b32460331e2ae6dfce9b3e71ccca31ed566fa7be4ad00563e3ad71c95cb7794a689006a242e910036e503a465b2500a1b9ff3ae2c3cec86070c64fb4600a0d1f2d2dc424d770e4836904bc7b254c15511b5915c96303d5d50d175aa721bce98c50f7aad761abd057560dd2d8caef6b2c046bb6db76025ac8effc751d448d4ce4afe813de6e1ccc3a247b98c92497ddcbb8e87cf759a5e53d4c0f5e64440a530f39535771eee63761bb1f3bf65523b15284057c429204eab2eaccbd70307c91c2f313a3ee7a210fe9f6bceba0d390e160b2e25c141bd0c01963d5f12ec2ac10804be445bf15ccd4cef137cb896664b51b2db0a3950f9f34"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Release",
|
||||
"expires": "2013-12-17 01:38:35",
|
||||
"meta": {
|
||||
"root.txt": {
|
||||
"hashes": {
|
||||
"sha256": "5adad012a2d1b3a8d695ac4f17056d205f127d59686d1a4b0c446c8d0beb2acf"
|
||||
},
|
||||
"length": 4804
|
||||
},
|
||||
"targets.txt": {
|
||||
"hashes": {
|
||||
"sha256": "ca3f216421c9ebdc910c9416a294723d15bd0bd0747d56071b37560b738834a8"
|
||||
},
|
||||
"length": 1194
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 01:38:35"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "caa188c0925736af382a61fcdc62ce68e360e9ac5f50abefacb749e20dad23d8",
|
||||
"method": "evp",
|
||||
"sig": "47a01d62fc6ffcaaca0c1e3de74306d603a67e554acf00c125aa8b2aaefe73269cfe5875f230e2359eacf75c2cf84f71ae43d4dad9415edc80973bfcc620579f7521fdbd00a8386c54ea9459f714fdc305ba8544b5f157632844cddb18b33c95419490db52cafb564456af403a2db33a4f5ba0588db8736e6bc1c8718979a3818b7c9309d9d6a9b0ad686d48fd3148128bb2b0749f27d8b9e2640e278f0082decd56468e3c1002316b0ea9dc1d244499774c9e4e20896307b6cfcb747f89ed953ab6523dade8de33bd5a5ae5351d6be3d3b4140f131e62d9236313a9df644cc4f365de2107427ce8785a454da122eae1dfca6ccbc9b48eca88ac5c7ded1efffc8b2b0ee9f8cecef170592438aed1153b111d0ca0259c6cc131708a17d901349d6e4a6694b7cd770a40613da3420408da1d83fc34c218e378e1862c893e6a91cd8e8313ce1aed4ef293139762b6b5ed5be15fcc06504ecdc3c18bed1c73b18eac0d55a9788faa99a1dbacf16000da941380a3368a726c3d94990090ce5a0eb0ed"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Root",
|
||||
"expires": "2014-12-17 01:38:35",
|
||||
"keys": {
|
||||
"0e4b16ecd2fdde03651fe4d7e069ae3edeabbf781b7f6a2e56a71cc1955e202f": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAw3GRZaihBaYj7TkcoQdJ\n20Oj9GKJaT1P80G4HcjwuRTrP9bF6K2yEmVvsisM7ZXY0tCo7F4LLnVxUf00Y9Cb\nwf7wg238cAF72HfMCU4j8mbxwkEK3yh1+N6A8qXfhJGmmftGzfqqo1e3e4dejZXh\nERiUPc34c2T5Z+ReG0GHMOoQNqES1in90AIUvvCMXkHs7BvWTIMJ34b2+JqF6L1t\ngF4Yax/7nJ+BOGmb21lpfPYdijCnLdI7GWeovqVY+fy7DCtQrrpybCBIn2gp/Cn1\n0EXGhMvKDXNQN1sJsi3M/i81tyCfouTOy//p5/zygEd3WkK986JSzB9SX0oK1X7B\nmytRvG2hVfNb4UarZd4Jfi44y5UPOr5VWd/YiLBnI2QkOLChhpSCus8GGi+yXJr8\nmQ7X0sctgsYxAJoREGB4sm1XmYXnGNuSbH+KSD1jYFhJKYPnJQCth3YCs7p/lfER\nWxMw/YVy+JFT2azLC5dgBXz5dxtTeCPL7mM5OB0ea+/tAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"b6422d4c8a4acb07317b032b89f7d03cc8b42a8b464355141d06a61c13849d92": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxKsZG4IWgn1vMIzIQtxr\nbXrP2sfeGsqMFpGKJS0/KJXtwTpOAb25fqHW25m9Xku/tSWqZw6EiqKKugeQtPHU\nYV0msWLUiSVYG6UQ4MZkO9wMnEaqF85upf0VALm7uVTcqcoZs0Gc1gIzO0Kl75sq\nB+vo92cqo7/MNAi9Dk2D3rGeYmca0y1QphkRHt2wmtuZyVkurSyXHXERM7d0+gVT\n5eajbP+E9xk70Hm4j4o3VcLBJtldNEclPp70j/8nLoV8S06DcxuPG4szrDuzPLas\nGAtKpJLDsp5nxTFn+6BhC/F6fLdO+I5lsxNg4ZBHVUwkbiwDPMSBb+TRjszFHOZ+\nzYuM7f+r6Ed537LUIJEXu2RKdoY1wsKkyzXiKEo+4Asyzz3dPnuuvuXFAN5jRBhA\nitkKPnyrSL2l1wDUazOfXlcP8uMA3jQrKZam9SqLo8sFnEMwgXAHeK8sNITOrnj6\nVMC39D24sfOLBhRo+N6f2FtgP4p/3MoQ6aw/fvjirWqDAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"c577a726a369d412247f1eb5d14234a90d84332b295d31bea42cf482fec0c885": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA4vn+PtG6ZJB7JnG7D5wJ\nz8QszLm8EmIznxt50e6nivVj9ttTbJFIk1Mgi/ob91ZvMlys23aYi8AjI74yZZlp\nkunvLSpwAgz4tPnbHy4G5WP/TzDjCsts+CSqcFAZZw7Tuk31Ewvh2cn0XpDWnFhF\ni8M1S6EEX+IifHndPa6pHQWCQHus+VieJuJXfr0COsL1tYFAEk1mOlm9EemBd4iI\nQUDKm89vwpj3/kcYT2EvfKDszF9SV7FO5DxcVNbqoUdkeUYuUVw5tUmFvHb0P00N\n4ak4LoHtR3x/ov4xOJ1IQt+qGa0DFYAre/KpRq+CYOH0OnAhOqmZ5t7zuMDJFhy9\nqiOi8tI2LU6LwUmTjgcmSta2dZudejFH/TxYsixQgY3l0sT27TZcPP9ul0rQHcav\nad1OUOyFtlk3SIM9UM1NA69L78EwEXrikIZCon68Vql56nKBb6XXSTPsQEUeH1gf\nw459RJOIA5iTBAEYJOYEd84QAR6Ut2DjApXSyJFzZAt3AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"caa188c0925736af382a61fcdc62ce68e360e9ac5f50abefacb749e20dad23d8": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAm2ASrXfbkNOmQ/TQ4uUM\n2o95S6ebcIKhg/h6HWY/HctMblpPaLfJq/5GEFdsNKs3gyo7uJfDUk6Ab5+BjBKm\nMqIDubUUJ7TNrE7EJYM+ml+Wf1MQooglFk+L1sRAh8uFSFOR4Q39cFQ1TS5+BjUF\n0fISXYcPZGL2cp0Fbwo3dD1UM3bXeFGij8KeXZ18Aijjbmkbsfgx4x8/HRO3wsAQ\nKNyqyQvKfkHO0HrzC8r/CLas98rfK79Ib76fheBhizPez4Efom9BljsaAPEaTvYs\nRoYk1UTefXWf+J5cOZcst+C+f6a3Yd108O6FUTVzMZVGp4PUQjtR1GgWhNUPCsyM\nsKenk3X1qTeD2+n4wY69BtBPV4i+8YZ/Qdx1BV6kdFg+WWV8YrPER+0zyUCBZgKx\naKmC5xc1yrcWW1gRrdAZ8HPvzoo6eHUWWSewU40D3xCRHcRXuP3vsAzX62Kpa+Rv\nszCveUWJK8oiwVt0XhtpCHP2ifWGlnUG9MWvI5wdEdXxAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"release": {
|
||||
"keyids": [
|
||||
"b6422d4c8a4acb07317b032b89f7d03cc8b42a8b464355141d06a61c13849d92"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"root": {
|
||||
"keyids": [
|
||||
"caa188c0925736af382a61fcdc62ce68e360e9ac5f50abefacb749e20dad23d8"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"targets": {
|
||||
"keyids": [
|
||||
"0e4b16ecd2fdde03651fe4d7e069ae3edeabbf781b7f6a2e56a71cc1955e202f"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"timestamp": {
|
||||
"keyids": [
|
||||
"c577a726a369d412247f1eb5d14234a90d84332b295d31bea42cf482fec0c885"
|
||||
],
|
||||
"threshold": 1
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 01:38:35"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "0e4b16ecd2fdde03651fe4d7e069ae3edeabbf781b7f6a2e56a71cc1955e202f",
|
||||
"method": "evp",
|
||||
"sig": "90f771fa0df7cdf4e55708ba7479e732de405e4b2fefe7eafebbd76f0d52734395c6610de17b6a0477918e1a35139a669bae6235999d50427d01defa7c8ad80b4c040164a1c0bb935f038fde000d02f0ce5ee30df27102fa16c8bec14ce4727a7f11c398b4f5539df2bfb8fb16565d2e07be4b73c09adb6017f336919240bf94a31ce527ddf5b031d77da90e2794ed1edda21673aef907ccfb99913617542b331bd7b6624409dea592e9dcff2420ab7a9f20e5debb83a5697ac18c3d9f53e7455ef56be414de6fe82b275e975e27a5f848427b056e2a1de773a6ee64a63310c3e8c1c10cd03da9c1b08b9284bba9d950855191ce4cb3e543a95a9c963cd1afc1f023a943bc38a145477599179f6d3ec851c3a7590f290ef8e37d3fa9233f36fbca1213c4000e86d329e581464761f592c8e4f10941a6e8fe67d81c9e54b82438f95fbabd02783b8ef036d5e9b02ef0b2944372de16db0781fde020bab54f74bf269f3b9df9f331288e80ba86ae398ca9aedb05ec42c2e8a33430fcf5d2365e85"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Targets",
|
||||
"expires": "2013-12-17 01:38:35",
|
||||
"targets": {
|
||||
"LICENSE.txt": {
|
||||
"hashes": {
|
||||
"sha256": "cd65721176ce5fdbb05773c0b1349f993b94ce77a51062cfa7a78b34cc82fc71"
|
||||
},
|
||||
"length": 3286
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 01:38:35"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "c577a726a369d412247f1eb5d14234a90d84332b295d31bea42cf482fec0c885",
|
||||
"method": "evp",
|
||||
"sig": "43e9e5c2474bc0a5b50f0b3bfb796fa8d0a255f2be3fa7100689614cd7516a06b305e72a193c39f49cd561d698d7ba8ac734e1516e4daf0cde111fe09fa94c83b4a062a387c34f892a6ee196ce29f4d0f1c97524cae404d8f4dad476db970a701e9bd1d940b93c0ff1276ff25b42f28fa8bc9b639c26a214e6f0d2058eb4c1a7b5ce3d415a8cc1a4067722deca5ba32f61440147623cf55297bde85e3e51c77a665c3884f08b7983fd20cd6e62e5a9246fb3c355f74e4e1a353319599cc04b115b04a0560a1fb81a3f45fdd7260e42aa07cb27f4249d1787fef04d9041e3a7395e444981f2b4af4be1506c793f2f8a86b131a2b8a4136eca73f4792fde3136adf5bb5acc22ebd84bf60d76b83e0f0ad2b6af51e16deaa8406f2a6b2dec2d3485c03d8017a7b933304d1f8b847e7df116cecfbcfe2cc191aa8961e75b527d99ae7b8c5c52f2490f7fb3ded81a4a9b841403638c2ff76b6eb54482d54c39f74f4eeac3f334ced98d9ecda7f28439b07a7947cb980f41a67fb9f2b24673532d4eb6"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Timestamp",
|
||||
"expires": "2013-12-17 01:38:36",
|
||||
"meta": {
|
||||
"release.txt": {
|
||||
"hashes": {
|
||||
"sha256": "c059dc4a07385cc3818491e25021169be1d35d405c6ea4aa53d2b7cc8c0801e6"
|
||||
},
|
||||
"length": 1340
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 01:38:36"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "b6422d4c8a4acb07317b032b89f7d03cc8b42a8b464355141d06a61c13849d92",
|
||||
"method": "evp",
|
||||
"sig": "4d8826441628b5d024478808ccb972a6869e357e31ce6770f1b1e5abf93640326f16441ded5c43a99beed7326757e1aec65f72707e9a38a52d8138d5880e628e6a4275fd245c12ca5c846ae031b1990a78699ed425efa0deb5187567661d4eb213b3977751d5b41550d795c063b07fdf601a69f0ec47a11f8ece99e1784bd8b2ab23766dc562dae739b32460331e2ae6dfce9b3e71ccca31ed566fa7be4ad00563e3ad71c95cb7794a689006a242e910036e503a465b2500a1b9ff3ae2c3cec86070c64fb4600a0d1f2d2dc424d770e4836904bc7b254c15511b5915c96303d5d50d175aa721bce98c50f7aad761abd057560dd2d8caef6b2c046bb6db76025ac8effc751d448d4ce4afe813de6e1ccc3a247b98c92497ddcbb8e87cf759a5e53d4c0f5e64440a530f39535771eee63761bb1f3bf65523b15284057c429204eab2eaccbd70307c91c2f313a3ee7a210fe9f6bceba0d390e160b2e25c141bd0c01963d5f12ec2ac10804be445bf15ccd4cef137cb896664b51b2db0a3950f9f34"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Release",
|
||||
"expires": "2013-12-17 01:38:35",
|
||||
"meta": {
|
||||
"root.txt": {
|
||||
"hashes": {
|
||||
"sha256": "5adad012a2d1b3a8d695ac4f17056d205f127d59686d1a4b0c446c8d0beb2acf"
|
||||
},
|
||||
"length": 4804
|
||||
},
|
||||
"targets.txt": {
|
||||
"hashes": {
|
||||
"sha256": "ca3f216421c9ebdc910c9416a294723d15bd0bd0747d56071b37560b738834a8"
|
||||
},
|
||||
"length": 1194
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 01:38:35"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "caa188c0925736af382a61fcdc62ce68e360e9ac5f50abefacb749e20dad23d8",
|
||||
"method": "evp",
|
||||
"sig": "47a01d62fc6ffcaaca0c1e3de74306d603a67e554acf00c125aa8b2aaefe73269cfe5875f230e2359eacf75c2cf84f71ae43d4dad9415edc80973bfcc620579f7521fdbd00a8386c54ea9459f714fdc305ba8544b5f157632844cddb18b33c95419490db52cafb564456af403a2db33a4f5ba0588db8736e6bc1c8718979a3818b7c9309d9d6a9b0ad686d48fd3148128bb2b0749f27d8b9e2640e278f0082decd56468e3c1002316b0ea9dc1d244499774c9e4e20896307b6cfcb747f89ed953ab6523dade8de33bd5a5ae5351d6be3d3b4140f131e62d9236313a9df644cc4f365de2107427ce8785a454da122eae1dfca6ccbc9b48eca88ac5c7ded1efffc8b2b0ee9f8cecef170592438aed1153b111d0ca0259c6cc131708a17d901349d6e4a6694b7cd770a40613da3420408da1d83fc34c218e378e1862c893e6a91cd8e8313ce1aed4ef293139762b6b5ed5be15fcc06504ecdc3c18bed1c73b18eac0d55a9788faa99a1dbacf16000da941380a3368a726c3d94990090ce5a0eb0ed"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Root",
|
||||
"expires": "2014-12-17 01:38:35",
|
||||
"keys": {
|
||||
"0e4b16ecd2fdde03651fe4d7e069ae3edeabbf781b7f6a2e56a71cc1955e202f": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAw3GRZaihBaYj7TkcoQdJ\n20Oj9GKJaT1P80G4HcjwuRTrP9bF6K2yEmVvsisM7ZXY0tCo7F4LLnVxUf00Y9Cb\nwf7wg238cAF72HfMCU4j8mbxwkEK3yh1+N6A8qXfhJGmmftGzfqqo1e3e4dejZXh\nERiUPc34c2T5Z+ReG0GHMOoQNqES1in90AIUvvCMXkHs7BvWTIMJ34b2+JqF6L1t\ngF4Yax/7nJ+BOGmb21lpfPYdijCnLdI7GWeovqVY+fy7DCtQrrpybCBIn2gp/Cn1\n0EXGhMvKDXNQN1sJsi3M/i81tyCfouTOy//p5/zygEd3WkK986JSzB9SX0oK1X7B\nmytRvG2hVfNb4UarZd4Jfi44y5UPOr5VWd/YiLBnI2QkOLChhpSCus8GGi+yXJr8\nmQ7X0sctgsYxAJoREGB4sm1XmYXnGNuSbH+KSD1jYFhJKYPnJQCth3YCs7p/lfER\nWxMw/YVy+JFT2azLC5dgBXz5dxtTeCPL7mM5OB0ea+/tAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"b6422d4c8a4acb07317b032b89f7d03cc8b42a8b464355141d06a61c13849d92": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxKsZG4IWgn1vMIzIQtxr\nbXrP2sfeGsqMFpGKJS0/KJXtwTpOAb25fqHW25m9Xku/tSWqZw6EiqKKugeQtPHU\nYV0msWLUiSVYG6UQ4MZkO9wMnEaqF85upf0VALm7uVTcqcoZs0Gc1gIzO0Kl75sq\nB+vo92cqo7/MNAi9Dk2D3rGeYmca0y1QphkRHt2wmtuZyVkurSyXHXERM7d0+gVT\n5eajbP+E9xk70Hm4j4o3VcLBJtldNEclPp70j/8nLoV8S06DcxuPG4szrDuzPLas\nGAtKpJLDsp5nxTFn+6BhC/F6fLdO+I5lsxNg4ZBHVUwkbiwDPMSBb+TRjszFHOZ+\nzYuM7f+r6Ed537LUIJEXu2RKdoY1wsKkyzXiKEo+4Asyzz3dPnuuvuXFAN5jRBhA\nitkKPnyrSL2l1wDUazOfXlcP8uMA3jQrKZam9SqLo8sFnEMwgXAHeK8sNITOrnj6\nVMC39D24sfOLBhRo+N6f2FtgP4p/3MoQ6aw/fvjirWqDAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"c577a726a369d412247f1eb5d14234a90d84332b295d31bea42cf482fec0c885": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA4vn+PtG6ZJB7JnG7D5wJ\nz8QszLm8EmIznxt50e6nivVj9ttTbJFIk1Mgi/ob91ZvMlys23aYi8AjI74yZZlp\nkunvLSpwAgz4tPnbHy4G5WP/TzDjCsts+CSqcFAZZw7Tuk31Ewvh2cn0XpDWnFhF\ni8M1S6EEX+IifHndPa6pHQWCQHus+VieJuJXfr0COsL1tYFAEk1mOlm9EemBd4iI\nQUDKm89vwpj3/kcYT2EvfKDszF9SV7FO5DxcVNbqoUdkeUYuUVw5tUmFvHb0P00N\n4ak4LoHtR3x/ov4xOJ1IQt+qGa0DFYAre/KpRq+CYOH0OnAhOqmZ5t7zuMDJFhy9\nqiOi8tI2LU6LwUmTjgcmSta2dZudejFH/TxYsixQgY3l0sT27TZcPP9ul0rQHcav\nad1OUOyFtlk3SIM9UM1NA69L78EwEXrikIZCon68Vql56nKBb6XXSTPsQEUeH1gf\nw459RJOIA5iTBAEYJOYEd84QAR6Ut2DjApXSyJFzZAt3AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"caa188c0925736af382a61fcdc62ce68e360e9ac5f50abefacb749e20dad23d8": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAm2ASrXfbkNOmQ/TQ4uUM\n2o95S6ebcIKhg/h6HWY/HctMblpPaLfJq/5GEFdsNKs3gyo7uJfDUk6Ab5+BjBKm\nMqIDubUUJ7TNrE7EJYM+ml+Wf1MQooglFk+L1sRAh8uFSFOR4Q39cFQ1TS5+BjUF\n0fISXYcPZGL2cp0Fbwo3dD1UM3bXeFGij8KeXZ18Aijjbmkbsfgx4x8/HRO3wsAQ\nKNyqyQvKfkHO0HrzC8r/CLas98rfK79Ib76fheBhizPez4Efom9BljsaAPEaTvYs\nRoYk1UTefXWf+J5cOZcst+C+f6a3Yd108O6FUTVzMZVGp4PUQjtR1GgWhNUPCsyM\nsKenk3X1qTeD2+n4wY69BtBPV4i+8YZ/Qdx1BV6kdFg+WWV8YrPER+0zyUCBZgKx\naKmC5xc1yrcWW1gRrdAZ8HPvzoo6eHUWWSewU40D3xCRHcRXuP3vsAzX62Kpa+Rv\nszCveUWJK8oiwVt0XhtpCHP2ifWGlnUG9MWvI5wdEdXxAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"release": {
|
||||
"keyids": [
|
||||
"b6422d4c8a4acb07317b032b89f7d03cc8b42a8b464355141d06a61c13849d92"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"root": {
|
||||
"keyids": [
|
||||
"caa188c0925736af382a61fcdc62ce68e360e9ac5f50abefacb749e20dad23d8"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"targets": {
|
||||
"keyids": [
|
||||
"0e4b16ecd2fdde03651fe4d7e069ae3edeabbf781b7f6a2e56a71cc1955e202f"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"timestamp": {
|
||||
"keyids": [
|
||||
"c577a726a369d412247f1eb5d14234a90d84332b295d31bea42cf482fec0c885"
|
||||
],
|
||||
"threshold": 1
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 01:38:35"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "0e4b16ecd2fdde03651fe4d7e069ae3edeabbf781b7f6a2e56a71cc1955e202f",
|
||||
"method": "evp",
|
||||
"sig": "90f771fa0df7cdf4e55708ba7479e732de405e4b2fefe7eafebbd76f0d52734395c6610de17b6a0477918e1a35139a669bae6235999d50427d01defa7c8ad80b4c040164a1c0bb935f038fde000d02f0ce5ee30df27102fa16c8bec14ce4727a7f11c398b4f5539df2bfb8fb16565d2e07be4b73c09adb6017f336919240bf94a31ce527ddf5b031d77da90e2794ed1edda21673aef907ccfb99913617542b331bd7b6624409dea592e9dcff2420ab7a9f20e5debb83a5697ac18c3d9f53e7455ef56be414de6fe82b275e975e27a5f848427b056e2a1de773a6ee64a63310c3e8c1c10cd03da9c1b08b9284bba9d950855191ce4cb3e543a95a9c963cd1afc1f023a943bc38a145477599179f6d3ec851c3a7590f290ef8e37d3fa9233f36fbca1213c4000e86d329e581464761f592c8e4f10941a6e8fe67d81c9e54b82438f95fbabd02783b8ef036d5e9b02ef0b2944372de16db0781fde020bab54f74bf269f3b9df9f331288e80ba86ae398ca9aedb05ec42c2e8a33430fcf5d2365e85"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Targets",
|
||||
"expires": "2013-12-17 01:38:35",
|
||||
"targets": {
|
||||
"LICENSE.txt": {
|
||||
"hashes": {
|
||||
"sha256": "cd65721176ce5fdbb05773c0b1349f993b94ce77a51062cfa7a78b34cc82fc71"
|
||||
},
|
||||
"length": 3286
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 01:38:35"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "c577a726a369d412247f1eb5d14234a90d84332b295d31bea42cf482fec0c885",
|
||||
"method": "evp",
|
||||
"sig": "43e9e5c2474bc0a5b50f0b3bfb796fa8d0a255f2be3fa7100689614cd7516a06b305e72a193c39f49cd561d698d7ba8ac734e1516e4daf0cde111fe09fa94c83b4a062a387c34f892a6ee196ce29f4d0f1c97524cae404d8f4dad476db970a701e9bd1d940b93c0ff1276ff25b42f28fa8bc9b639c26a214e6f0d2058eb4c1a7b5ce3d415a8cc1a4067722deca5ba32f61440147623cf55297bde85e3e51c77a665c3884f08b7983fd20cd6e62e5a9246fb3c355f74e4e1a353319599cc04b115b04a0560a1fb81a3f45fdd7260e42aa07cb27f4249d1787fef04d9041e3a7395e444981f2b4af4be1506c793f2f8a86b131a2b8a4136eca73f4792fde3136adf5bb5acc22ebd84bf60d76b83e0f0ad2b6af51e16deaa8406f2a6b2dec2d3485c03d8017a7b933304d1f8b847e7df116cecfbcfe2cc191aa8961e75b527d99ae7b8c5c52f2490f7fb3ded81a4a9b841403638c2ff76b6eb54482d54c39f74f4eeac3f334ced98d9ecda7f28439b07a7947cb980f41a67fb9f2b24673532d4eb6"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Timestamp",
|
||||
"expires": "2013-12-17 01:38:36",
|
||||
"meta": {
|
||||
"release.txt": {
|
||||
"hashes": {
|
||||
"sha256": "c059dc4a07385cc3818491e25021169be1d35d405c6ea4aa53d2b7cc8c0801e6"
|
||||
},
|
||||
"length": 1340
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 01:38:36"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,23 @@
|
|||
[expiration]
|
||||
days = 730
|
||||
years = 0
|
||||
minutes = 0
|
||||
hours = 0
|
||||
seconds = 0
|
||||
|
||||
[release]
|
||||
keyids = b6422d4c8a4acb07317b032b89f7d03cc8b42a8b464355141d06a61c13849d92
|
||||
threshold = 1
|
||||
|
||||
[timestamp]
|
||||
keyids = c577a726a369d412247f1eb5d14234a90d84332b295d31bea42cf482fec0c885
|
||||
threshold = 1
|
||||
|
||||
[root]
|
||||
keyids = caa188c0925736af382a61fcdc62ce68e360e9ac5f50abefacb749e20dad23d8
|
||||
threshold = 1
|
||||
|
||||
[targets]
|
||||
keyids = 0e4b16ecd2fdde03651fe4d7e069ae3edeabbf781b7f6a2e56a71cc1955e202f
|
||||
threshold = 1
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "b6422d4c8a4acb07317b032b89f7d03cc8b42a8b464355141d06a61c13849d92",
|
||||
"method": "evp",
|
||||
"sig": "6f9af25e35afe5bb450a48f9af3bb030a1a7665cfde346cd8e32a858608c86cf7cac21c9c61a01f7f466063b9956e09cc97bf480ecbe89be706474cc07b1d3c9ff67980bdc3196856eef59d3fe76856ab5f8a375a3c339057c68eeaf742ad7d78c22f9fea5935287ae458de3b294b42ba84d7447922b578daddf2dcf1d9188eea26351583878699ed1d7088d5e51612e24d6412f19c6bbfd307d3adb91471d740d7aec7a9fafdf0744fbe041cac4c8c4e36c50710e4b79f96e0d57f6d77bf4c9f081c2f68817cbfbe94ec5f3d987d7f60978c9f0594f095546aea25d838a89569ffca5a0d25fc45aa629b38f1551f19cbb948b7347294d6140a2113f526eec997b7dc72ad9250a83596ad5e80a81dbfbdce2d47f9d73dc2acd9313030d8b767e1837d10dcff9033199b24839f04d2bf43c8d893374a9223a920d1f81e6af5a24f75f511c5c26b8d1a4d713ec85969a173976d5efa86436e98ce602e9bda91d6c709695819e095e8e85f6b9fd28d35a66640c5a913b96c7bc9b9eea6a88c899fe"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Release",
|
||||
"expires": "2013-12-17 02:19:33",
|
||||
"meta": {
|
||||
"root.txt": {
|
||||
"hashes": {
|
||||
"sha256": "5adad012a2d1b3a8d695ac4f17056d205f127d59686d1a4b0c446c8d0beb2acf"
|
||||
},
|
||||
"length": 4804
|
||||
},
|
||||
"targets.txt": {
|
||||
"hashes": {
|
||||
"sha256": "3de89d8b0bea0a39aa533d08b57c76704f235428a274e21b55cf5f9e8ae05373"
|
||||
},
|
||||
"length": 2272
|
||||
},
|
||||
"targets/role1.txt": {
|
||||
"hashes": {
|
||||
"sha256": "666e84b3f67efe68e084a22584fbd1f554e232dadd8d0db14b1d43b86092006d"
|
||||
},
|
||||
"length": 2292
|
||||
},
|
||||
"targets/role1/role2.txt": {
|
||||
"hashes": {
|
||||
"sha256": "a1b60c9e758a9fe9812fb4a5fe0cf769bccb93a1eb066393c9252f0c702b3624"
|
||||
},
|
||||
"length": 1208
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 02:19:33"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "caa188c0925736af382a61fcdc62ce68e360e9ac5f50abefacb749e20dad23d8",
|
||||
"method": "evp",
|
||||
"sig": "47a01d62fc6ffcaaca0c1e3de74306d603a67e554acf00c125aa8b2aaefe73269cfe5875f230e2359eacf75c2cf84f71ae43d4dad9415edc80973bfcc620579f7521fdbd00a8386c54ea9459f714fdc305ba8544b5f157632844cddb18b33c95419490db52cafb564456af403a2db33a4f5ba0588db8736e6bc1c8718979a3818b7c9309d9d6a9b0ad686d48fd3148128bb2b0749f27d8b9e2640e278f0082decd56468e3c1002316b0ea9dc1d244499774c9e4e20896307b6cfcb747f89ed953ab6523dade8de33bd5a5ae5351d6be3d3b4140f131e62d9236313a9df644cc4f365de2107427ce8785a454da122eae1dfca6ccbc9b48eca88ac5c7ded1efffc8b2b0ee9f8cecef170592438aed1153b111d0ca0259c6cc131708a17d901349d6e4a6694b7cd770a40613da3420408da1d83fc34c218e378e1862c893e6a91cd8e8313ce1aed4ef293139762b6b5ed5be15fcc06504ecdc3c18bed1c73b18eac0d55a9788faa99a1dbacf16000da941380a3368a726c3d94990090ce5a0eb0ed"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Root",
|
||||
"expires": "2014-12-17 01:38:35",
|
||||
"keys": {
|
||||
"0e4b16ecd2fdde03651fe4d7e069ae3edeabbf781b7f6a2e56a71cc1955e202f": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAw3GRZaihBaYj7TkcoQdJ\n20Oj9GKJaT1P80G4HcjwuRTrP9bF6K2yEmVvsisM7ZXY0tCo7F4LLnVxUf00Y9Cb\nwf7wg238cAF72HfMCU4j8mbxwkEK3yh1+N6A8qXfhJGmmftGzfqqo1e3e4dejZXh\nERiUPc34c2T5Z+ReG0GHMOoQNqES1in90AIUvvCMXkHs7BvWTIMJ34b2+JqF6L1t\ngF4Yax/7nJ+BOGmb21lpfPYdijCnLdI7GWeovqVY+fy7DCtQrrpybCBIn2gp/Cn1\n0EXGhMvKDXNQN1sJsi3M/i81tyCfouTOy//p5/zygEd3WkK986JSzB9SX0oK1X7B\nmytRvG2hVfNb4UarZd4Jfi44y5UPOr5VWd/YiLBnI2QkOLChhpSCus8GGi+yXJr8\nmQ7X0sctgsYxAJoREGB4sm1XmYXnGNuSbH+KSD1jYFhJKYPnJQCth3YCs7p/lfER\nWxMw/YVy+JFT2azLC5dgBXz5dxtTeCPL7mM5OB0ea+/tAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"b6422d4c8a4acb07317b032b89f7d03cc8b42a8b464355141d06a61c13849d92": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxKsZG4IWgn1vMIzIQtxr\nbXrP2sfeGsqMFpGKJS0/KJXtwTpOAb25fqHW25m9Xku/tSWqZw6EiqKKugeQtPHU\nYV0msWLUiSVYG6UQ4MZkO9wMnEaqF85upf0VALm7uVTcqcoZs0Gc1gIzO0Kl75sq\nB+vo92cqo7/MNAi9Dk2D3rGeYmca0y1QphkRHt2wmtuZyVkurSyXHXERM7d0+gVT\n5eajbP+E9xk70Hm4j4o3VcLBJtldNEclPp70j/8nLoV8S06DcxuPG4szrDuzPLas\nGAtKpJLDsp5nxTFn+6BhC/F6fLdO+I5lsxNg4ZBHVUwkbiwDPMSBb+TRjszFHOZ+\nzYuM7f+r6Ed537LUIJEXu2RKdoY1wsKkyzXiKEo+4Asyzz3dPnuuvuXFAN5jRBhA\nitkKPnyrSL2l1wDUazOfXlcP8uMA3jQrKZam9SqLo8sFnEMwgXAHeK8sNITOrnj6\nVMC39D24sfOLBhRo+N6f2FtgP4p/3MoQ6aw/fvjirWqDAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"c577a726a369d412247f1eb5d14234a90d84332b295d31bea42cf482fec0c885": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA4vn+PtG6ZJB7JnG7D5wJ\nz8QszLm8EmIznxt50e6nivVj9ttTbJFIk1Mgi/ob91ZvMlys23aYi8AjI74yZZlp\nkunvLSpwAgz4tPnbHy4G5WP/TzDjCsts+CSqcFAZZw7Tuk31Ewvh2cn0XpDWnFhF\ni8M1S6EEX+IifHndPa6pHQWCQHus+VieJuJXfr0COsL1tYFAEk1mOlm9EemBd4iI\nQUDKm89vwpj3/kcYT2EvfKDszF9SV7FO5DxcVNbqoUdkeUYuUVw5tUmFvHb0P00N\n4ak4LoHtR3x/ov4xOJ1IQt+qGa0DFYAre/KpRq+CYOH0OnAhOqmZ5t7zuMDJFhy9\nqiOi8tI2LU6LwUmTjgcmSta2dZudejFH/TxYsixQgY3l0sT27TZcPP9ul0rQHcav\nad1OUOyFtlk3SIM9UM1NA69L78EwEXrikIZCon68Vql56nKBb6XXSTPsQEUeH1gf\nw459RJOIA5iTBAEYJOYEd84QAR6Ut2DjApXSyJFzZAt3AgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
"caa188c0925736af382a61fcdc62ce68e360e9ac5f50abefacb749e20dad23d8": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAm2ASrXfbkNOmQ/TQ4uUM\n2o95S6ebcIKhg/h6HWY/HctMblpPaLfJq/5GEFdsNKs3gyo7uJfDUk6Ab5+BjBKm\nMqIDubUUJ7TNrE7EJYM+ml+Wf1MQooglFk+L1sRAh8uFSFOR4Q39cFQ1TS5+BjUF\n0fISXYcPZGL2cp0Fbwo3dD1UM3bXeFGij8KeXZ18Aijjbmkbsfgx4x8/HRO3wsAQ\nKNyqyQvKfkHO0HrzC8r/CLas98rfK79Ib76fheBhizPez4Efom9BljsaAPEaTvYs\nRoYk1UTefXWf+J5cOZcst+C+f6a3Yd108O6FUTVzMZVGp4PUQjtR1GgWhNUPCsyM\nsKenk3X1qTeD2+n4wY69BtBPV4i+8YZ/Qdx1BV6kdFg+WWV8YrPER+0zyUCBZgKx\naKmC5xc1yrcWW1gRrdAZ8HPvzoo6eHUWWSewU40D3xCRHcRXuP3vsAzX62Kpa+Rv\nszCveUWJK8oiwVt0XhtpCHP2ifWGlnUG9MWvI5wdEdXxAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"release": {
|
||||
"keyids": [
|
||||
"b6422d4c8a4acb07317b032b89f7d03cc8b42a8b464355141d06a61c13849d92"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"root": {
|
||||
"keyids": [
|
||||
"caa188c0925736af382a61fcdc62ce68e360e9ac5f50abefacb749e20dad23d8"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"targets": {
|
||||
"keyids": [
|
||||
"0e4b16ecd2fdde03651fe4d7e069ae3edeabbf781b7f6a2e56a71cc1955e202f"
|
||||
],
|
||||
"threshold": 1
|
||||
},
|
||||
"timestamp": {
|
||||
"keyids": [
|
||||
"c577a726a369d412247f1eb5d14234a90d84332b295d31bea42cf482fec0c885"
|
||||
],
|
||||
"threshold": 1
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 01:38:35"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "0e4b16ecd2fdde03651fe4d7e069ae3edeabbf781b7f6a2e56a71cc1955e202f",
|
||||
"method": "evp",
|
||||
"sig": "aca8ff23c3a666c7c7fe9569db5c84751f1fa173165a2dd9bb7797de572fd6ffad2f4eda95d9fc1cef7e2da04f63e45a3ef39665be01806f8eabe665339016bd00781af7178f399c2b85d815dfc7db58db18485a4a6b5ecbf3e5117163917fd21d4dbfc33c568eb33476241f13b5168b2685e3cd16848d5a249bd90a0876fa6bc1956237c61a25816732c25665a4d6bb08c7fb4d0aee4a6f77a856b34581045cbbf20490756ef2dbb798f4972411abaeaae2e299c41f202f38d7c551026a20db1ca9e3944bd88a4feff9cd3eb2d989beb41b585942ffbe3b86d9971753c5cb9d6836af75748d875cca9c83800d55f3394ac9b13838121dd15c57ef38566f530d1b40130ba7d5f9c9bef34d7e478b33ada036d3dc55eafe542ac67bd932cffc2b93fe93317d919f104b0d2576d084f743f7185d9d899ea4fc6ded1aaba4c9de51ba30af909c49c33292eaf093eb8945b61066fc286eb65beabf8b1cd92afb34d03c49000a77571eb13ffd51bba4015dac87f5c60af7b4f192456e9b8e35620b18"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Targets",
|
||||
"delegations": {
|
||||
"keys": {
|
||||
"649077bc2acbc020e340a4c8f13a106787ca75d026ec60db5c2c0af3b9b60085": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAycGNqAn3w3H92H3uLnbw\nBoO/JHuPJSS+w/0d14h0Sn9lHigGKkDNBd3xyQNTTTL20WweFcYRS4T6Y/dLl6hH\njudMlHE6ieqDLKGT59zQdRM4X1L6kXxLH/0lwrF10MVz1KLhzFENQFe86sRqjoTP\nr6EoL72xnIvBiuUvRptpIIx5cighaOk7/3e72Xp27XMZWFW9rkjrMxP5MxEEU4Zk\nU9lmdKbYjVctVOtJ+b4XY6JgIGMgiNMUme+K7Jh/Ch7z4BGp83Bt1e6ZSlXAl51S\no/F8qzV32YlydRpbCZGdN0zXlsP46UhzWK08sehmL4o9BIlJEethVYHSl7tXq3jM\nq+BImbM/myghYyLvKKRt0yrdUz5k4Z1eRVtUnq8WAoXfJjLuQdJiPIZZIcL92GQu\nXfyEOef0Ov5V1dqrVrzUykO5S7VeE+hxrPOp/P1VS8GuK5AtVG4cUVXagevJRQ7x\n3mRikxFbXIkM+3NGu1PiulhceB06IQ0vB54bf97c03exAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"targets/role1": {
|
||||
"keyids": [
|
||||
"649077bc2acbc020e340a4c8f13a106787ca75d026ec60db5c2c0af3b9b60085"
|
||||
],
|
||||
"paths": [
|
||||
"role1/delegated_target1.txt"
|
||||
],
|
||||
"threshold": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"expires": "2013-12-17 01:38:35",
|
||||
"targets": {
|
||||
"LICENSE.txt": {
|
||||
"hashes": {
|
||||
"sha256": "cd65721176ce5fdbb05773c0b1349f993b94ce77a51062cfa7a78b34cc82fc71"
|
||||
},
|
||||
"length": 3286
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 01:38:35"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "649077bc2acbc020e340a4c8f13a106787ca75d026ec60db5c2c0af3b9b60085",
|
||||
"method": "evp",
|
||||
"sig": "773e4867b722a0a99d38403e03bbf8bd4ba6fbd9138358d1b5f53e2a5310be751ba13201ce1e922d3ce5ea5b5213226ef3eaecf0449db294f460e79b4c40ba086bdb469b9af2dea42338d31b376fde33f0ac91a45ab5df97f2b67ed71e17d86c03c292301e1fb63d5532a1a95cab9a5f9132feefbe7138799ea8fba8c963be696ef478a18f8be8162752aa2e2be14d123514762e1334aefabc6be5f84a22a947ed260fdf1d50dbf0bae7fff92af259c4f18ee16696c2f0b3e9d245bc9fba2ed9a4a770cfd6f51c6b0a75daf7896a943370af36c170b0e92b47cd17b6b5e68fd48e42a31ea12879c89a9914198e446c8cd60f9883545a5c8a59b1f7721c6e06669ecee4716934f5a02b583d0067670068656b00578b54ef0e0d84e1b620c52779d1550260b4c0dd8bfe3d20ecf19ad2e6381fb2de06a2d01729040c593d56da0208b2a3571eba2330efa907dfb467c524795012f3a776da0b3b84220cc89461fb67aa69f1f56bb521f211c0b8e8ecfd83b8495fbee433b9743923d57d864768a3"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Targets",
|
||||
"delegations": {
|
||||
"keys": {
|
||||
"026d535ad9bd3a7b9dd399612f264fb4be4aa88f9668ac6fb86bfc1f891458d8": {
|
||||
"keytype": "rsa",
|
||||
"keyval": {
|
||||
"private": "",
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAvmDFVDT7uzuINW61XGNz\nAbQTmiyWJse/+oyucZNlVuGZgxlKur81eYyVFr0oEcFBDBk9hKlFZkJmGRENKL9l\nVELKFqtuBetl4ullCQL7wZrGlsBXIYelCTZL6jar+4jYY+pObE3VDa7EDOwOBfbi\nfOq61pom5fwJ04Aprwf4HAI1M/C7N0fo+1Oy1kZfI+PFOgT8Ed3adu7UfRq3I8VY\nsiJZTgsyrvHlD/wHhwIQsxWiZZgXy9ziSeIbjXISznMI6Xsi2rCoihepOT3VonwD\n6emgt69EeOamxjUH/AH5iorMd3yWD3pN6EP0SAxQMIK96005NgfTflQ6mN733wMQ\nkQ1xpXSHlP1XRMuNMH7TovjGE7T9bVsipuDcTQ9HrLMRZfchh/GSZIHu3nC3z8L8\nAgeeGqqin3cLjoHYpXJbtQKZ/qlqo7EsExEoPhC6XYS6A5KFoDWc7YmUhmtDb5KM\nvyXoqeivdGjWSDY2WdSqGPzxdFz6UnZ1LFSHAkLWcJWtAgMBAAE=\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"targets/role1/role2": {
|
||||
"keyids": [
|
||||
"026d535ad9bd3a7b9dd399612f264fb4be4aa88f9668ac6fb86bfc1f891458d8"
|
||||
],
|
||||
"paths": [
|
||||
"role2/delegated_target2.txt"
|
||||
],
|
||||
"threshold": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"expires": "2013-12-17 01:57:16",
|
||||
"targets": {
|
||||
"role1/delegated_target1.txt": {
|
||||
"hashes": {
|
||||
"sha256": "863309c54db9b7fb109dec25c54d0dabcbc931ff94516168e10c36de5c9107eb"
|
||||
},
|
||||
"length": 19
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 01:57:16"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "026d535ad9bd3a7b9dd399612f264fb4be4aa88f9668ac6fb86bfc1f891458d8",
|
||||
"method": "evp",
|
||||
"sig": "b3df7b4eba55cd57fce111ee733f4befeb6f04d61c0bd7ea107142d87d36895e20ee90c13a1b346d9ad5399042ee101782d5b9399a6b8eb5b5ea58606dabd0aca5206ac7051bab37cb9e1a9251b22a26bcbc5745e524d847591eeef4c9e0c88c8bac9f0ec77a362fe279463e08307f5e712e6ccc1261fa406f7fe76aef463c2b6fb16368e85ae8c9816ed8fa1d808d9020ac7157a5ec58bb357cf0c815fd06f5c606c69d5aa40dad35d42c57f8ee4da16d0401454f65875e3f75976f2cd1413a7806adaef8d1f2fcb1afa7beae57196fc8ffb9484e597b60c6f5714fce33df462293f2d00a1e94b2159c016db3cc9af2bdb4497fe1d0879f22343574b803dc27b8c86bd6310981ce46e436ecf2dd50a61f49330fbc8bda42789c6c9e1720434d00fb006afe619a0a7a35b70b1dc5feca1a46fb5353827e37c19d0c5e84b8605ce130e80f8f781bddbdad2a452fb25107c28e26f3f8aa07b54b969971951bd20e8a13561c9b94801b216a5ab08f01ff1fe03177c3498820888c0097d18aceb77a"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Targets",
|
||||
"expires": "2013-12-17 02:00:05",
|
||||
"targets": {
|
||||
"role2/delegated_target2.txt": {
|
||||
"hashes": {
|
||||
"sha256": "a93f4bdd1cf4219da2ff4cb04e79f596d5bcc9948ef9400615967a2627d0256d"
|
||||
},
|
||||
"length": 19
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 02:00:05"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "c577a726a369d412247f1eb5d14234a90d84332b295d31bea42cf482fec0c885",
|
||||
"method": "evp",
|
||||
"sig": "1b1c21f942c337b4d3eaa69df927f11093ba17126ca1863442db146ecd81d6c949ff611f53b4f3192a265b422b86e8a832b27c20c15bbfe72cd8aadb036fcdbf6bbe7c1ba37f37f27eb44f610ba4c109421209a07e8a54b44f8817d430e11f64c29376f20802c8b0d0a2b4b79386c4582a9add07910ace07cf8e9a05ece325e4c6f2ae6af4eff4589f1038e07bf93aeca0a7da40aca6711259bc9f3a22a87bdcbaf313e2b0dd431ad3431d36604927047f74f2c24436a28d32676753f512081b1447a4f77354d3eb451dc47cb4d75a716856c9653f2a91e7d05c485ad966d7d990851fcfa007011d6545bb446c114ff486a226f71af36479a1cf7cb14f4525a27624bb837c26146deb6721820a577c233047b3bf7aa3fc1938702962fedbf933b8468f9f170cab4de707a40e9b7e708cfca9ba630c2922eb9a8dfc19d814060c101c6c576253d3fa85eb4a991ee52be8fb316ce26f15a0e5acb7affdcc8c0f899da7fb733952fb1cd19e22501a12f9d7cb98680c1abdbb811848cdd9cb07718d"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "Timestamp",
|
||||
"expires": "2013-12-17 02:20:17",
|
||||
"meta": {
|
||||
"release.txt": {
|
||||
"hashes": {
|
||||
"sha256": "8675d50257f7e12d8fadd252e6a15b819c8b99c34ceca198e9f439ea63a8435b"
|
||||
},
|
||||
"length": 1662
|
||||
}
|
||||
},
|
||||
"ts": "2012-12-17 02:20:17"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
This file contains the license for TUF: The Update Framework.
|
||||
|
||||
It also lists license information for components and source
|
||||
code used by TUF: The Update Framework.
|
||||
|
||||
If you got this file as a part of a larger bundle,
|
||||
there may be other license terms that you should be aware of.
|
||||
|
||||
===============================================================================
|
||||
TUF: The Update Framework is distributed under this license:
|
||||
|
||||
Copyright (c) 2010, Justin Samuel and Justin Cappos.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and/or hardware specification (the “Work”) to deal in the Work
|
||||
without restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies of the Work,
|
||||
and to permit persons to whom the Work is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Work.
|
||||
|
||||
THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER
|
||||
DEALINGS IN THE WORK.
|
||||
===============================================================================
|
||||
Many files are modified from Thandy and are licensed under the
|
||||
following license:
|
||||
|
||||
Thandy is distributed under this license:
|
||||
|
||||
Copyright (c) 2008, The Tor Project, Inc.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* Neither the names of the copyright owners nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
===============================================================================
|
||||
|
|
@ -0,0 +1 @@
|
|||
delegated target 1
|
||||
|
|
@ -0,0 +1 @@
|
|||
delegated target 2
|
||||
1162
src/tuf/formats.py
Executable file
1162
src/tuf/formats.py
Executable file
File diff suppressed because it is too large
Load diff
299
src/tuf/hash.py
Executable file
299
src/tuf/hash.py
Executable file
|
|
@ -0,0 +1,299 @@
|
|||
"""
|
||||
<Program Name>
|
||||
hash.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
February 28, 2012. Based on a previous version of this module.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Support multiple implementations of secure hash and message digest
|
||||
algorithms. Any hash-related routines that TUF requires should be
|
||||
located in this module. Ensuring that a secure hash algorithm is
|
||||
available to TUF, 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.
|
||||
Hashlib and pycrypto hash algorithms currently supported.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Import tuf Exceptions.
|
||||
import tuf
|
||||
|
||||
# Import tuf logger to log warning messages.
|
||||
import logging
|
||||
logger = logging.getLogger('tuf.hash')
|
||||
|
||||
# The list of hash libraries imported successfully.
|
||||
_supported_libraries = []
|
||||
|
||||
# Hash libraries currently supported by tuf.hash.
|
||||
_SUPPORTED_LIB_LIST = ['hashlib', 'pycrypto']
|
||||
|
||||
# Let's try importing the pycrypto hash algorithms. Pycrypto will
|
||||
# not be added to the supported list of libraries if the specified
|
||||
# hash algorithms below cannot all be imported.
|
||||
try:
|
||||
from Crypto.Hash import MD5
|
||||
from Crypto.Hash import SHA
|
||||
from Crypto.Hash import SHA224
|
||||
from Crypto.Hash import SHA256
|
||||
from Crypto.Hash import SHA384
|
||||
from Crypto.Hash import SHA512
|
||||
_supported_libraries.append('pycrypto')
|
||||
except ImportError:
|
||||
logger.warn('Pycrypto hash algorithms could not be imported. '
|
||||
'Supported libraries: '+str(_SUPPORTED_LIB_LIST))
|
||||
|
||||
pass
|
||||
|
||||
# Python <=2.4 does not have the hashlib module by default.
|
||||
# Let's try importing hashlib and adding it to our supported list.
|
||||
try:
|
||||
import hashlib
|
||||
_supported_libraries.append('hashlib')
|
||||
except ImportError:
|
||||
logger.warn('Hashlib could not be imported. '
|
||||
'Supported libraries: '+str(_SUPPORTED_LIB_LIST))
|
||||
pass
|
||||
|
||||
# Were we able to import any hash libraries?
|
||||
if not _supported_libraries:
|
||||
# This is fatal, we'll have no way of generating hashes.
|
||||
raise tuf.Error('Unable to import a hash library from the '
|
||||
'following supported list: '+str(_SUPPORTED_LIB_LIST))
|
||||
|
||||
|
||||
_DEFAULT_HASH_ALGORITHM = 'sha256'
|
||||
_DEFAULT_HASH_LIBRARY = 'hashlib'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def digest(algorithm=_DEFAULT_HASH_ALGORITHM,
|
||||
hash_library=_DEFAULT_HASH_LIBRARY):
|
||||
"""
|
||||
<Purpose>
|
||||
Provide the caller with the ability to create
|
||||
digest objects without having to worry about hash
|
||||
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 = tuf.hash.digest()
|
||||
digest_object = tuf.hash.digest('sha384')
|
||||
digest_object = tuf.hash.digest('pycrypto')
|
||||
|
||||
# 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 = tuf.hash.digest_fileobject(file_object)
|
||||
digest_object = tuf.hash.digest_filename(filename)
|
||||
|
||||
<Arguments>
|
||||
algorithm:
|
||||
The hash algorithm (e.g., md5, sha1, sha256).
|
||||
|
||||
hash_library:
|
||||
The library providing the hash algorithms
|
||||
(e.g., pycrypto, hashlib).
|
||||
|
||||
<Exceptions>
|
||||
tuf.UnsupportedAlgorithmError
|
||||
tuf.UnsupportedLibraryError
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
Digest object (e.g., hashlib.new(algorithm) or
|
||||
algorithm.new() # pycrypto).
|
||||
|
||||
"""
|
||||
|
||||
# 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 tuf.UnsupportedAlgorithmError(algorithm)
|
||||
|
||||
# Was a pycrypto digest object requested and is it supported?
|
||||
elif hash_library == 'pycrypto' and hash_library in _supported_libraries:
|
||||
# Pycrypto does not offer a comparable hashlib.new(hashname).
|
||||
# Let's first check the 'algorithm' argument before returning
|
||||
# the correct pycrypto digest object using pycrypto's object construction.
|
||||
if algorithm == 'md5':
|
||||
return MD5.new()
|
||||
elif algorithm == 'sha1':
|
||||
return SHA.new()
|
||||
elif algorithm == 'sha224':
|
||||
return SHA224.new()
|
||||
elif algorithm == 'sha256':
|
||||
return SHA256.new()
|
||||
elif algorithm == 'sha384':
|
||||
return SHA384.new()
|
||||
elif algorithm == 'sha512':
|
||||
return SHA512.new()
|
||||
else:
|
||||
raise tuf.UnsupportedAlgorithmError(algorithm)
|
||||
|
||||
# The requested hash library is not supported.
|
||||
else:
|
||||
raise tuf.UnsupportedLibraryError('Unsupported library requested. '
|
||||
'Supported hash libraries: '+str(_SUPPORTED_LIB_LIST))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def digest_fileobject(file_object, algorithm=_DEFAULT_HASH_ALGORITHM,
|
||||
hash_library=_DEFAULT_HASH_LIBRARY):
|
||||
"""
|
||||
<Purpose>
|
||||
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.
|
||||
|
||||
<Arguments>
|
||||
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., pycrypto, hashlib).
|
||||
|
||||
<Exceptions>
|
||||
tuf.UnsupportedAlgorithmError
|
||||
tuf.Error
|
||||
|
||||
<Side Effects>
|
||||
Calls tuf.hash.digest() to create the actual digest object.
|
||||
|
||||
<Returns>
|
||||
Digest object (e.g., hashlib.new(algorithm) or
|
||||
algorithm.new() # pycrypto).
|
||||
|
||||
"""
|
||||
|
||||
# Digest object returned whose hash will be updated using 'file_object'.
|
||||
# digest() raises:
|
||||
# tuf.UnsupportedAlgorithmError
|
||||
# tuf.Error
|
||||
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:
|
||||
chunksize = 4096
|
||||
data = file_object.read(chunksize)
|
||||
if not data:
|
||||
break
|
||||
digest_object.update(data_to_string(data))
|
||||
return digest_object
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def digest_filename(filename, algorithm=_DEFAULT_HASH_ALGORITHM,
|
||||
hash_library=_DEFAULT_HASH_LIBRARY):
|
||||
"""
|
||||
<Purpose>
|
||||
Generate a digest object, update its hash using a file object
|
||||
specified by filename, and then return it to the caller.
|
||||
|
||||
<Arguments>
|
||||
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., pycrypto, hashlib).
|
||||
|
||||
<Exceptions>
|
||||
tuf.UnsupportedAlgorithmError
|
||||
tuf.Error
|
||||
|
||||
<Side Effects>
|
||||
Calls tuf.hash.digest_fileobject() after opening 'filename'.
|
||||
File closed before returning.
|
||||
|
||||
<Returns>
|
||||
Digest object (e.g., hashlib.new(algorithm) or
|
||||
algorithm.new() # pycrypto).
|
||||
|
||||
"""
|
||||
|
||||
# Open 'filename' in read+binary mode.
|
||||
file_object = open(filename, 'rb')
|
||||
|
||||
# Create digest_object and update its hash data from file_object.
|
||||
# digest_fileobject() raises:
|
||||
# tuf.UnsupportedAlgorithmError
|
||||
# tuf.Error
|
||||
digest_object = digest_fileobject(file_object, algorithm, hash_library)
|
||||
|
||||
file_object.close()
|
||||
return digest_object
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def data_to_string(data):
|
||||
"""
|
||||
<Purpose>
|
||||
Return 'data' as a string. The update() function of a digest object
|
||||
only accepts strings, however, TUF will often need to feed this function
|
||||
non-strings. This utility function circumvents this issue and decides how
|
||||
exactly to convert these objects TUF might use.
|
||||
|
||||
<Arguments>
|
||||
data:
|
||||
The data object to be returned as a string.
|
||||
|
||||
<Exceptions>
|
||||
None.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
String.
|
||||
|
||||
"""
|
||||
|
||||
if isinstance(data, str):
|
||||
return data
|
||||
elif isinstance(data, unicode):
|
||||
return data.encode("utf-8")
|
||||
else:
|
||||
return str(data)
|
||||
269
src/tuf/keydb.py
Executable file
269
src/tuf/keydb.py
Executable file
|
|
@ -0,0 +1,269 @@
|
|||
"""
|
||||
<Program Name>
|
||||
keydb.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
March 21, 2012. Based on a previous version of this module by Geremy Condra.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Represent a collection of keys and their organization. This module ensures
|
||||
the layout of the collection remain consistent and easily verifiable.
|
||||
Provided are functions to add and delete keys from the database, retrieve a
|
||||
single key, and assemble a collection from keys stored in TUF 'Root' Metadata
|
||||
files.
|
||||
|
||||
RSA keys are currently supported and a collection of keys is organized as a
|
||||
dictionary indexed by key ID. Key IDs are used as identifiers for keys (e.g.,
|
||||
RSA key). They are the hexadecimal representations of the hash of key objects
|
||||
(specifically, the key object containing only the public key). See 'rsa_key.py'
|
||||
and the '_get_keyid()' function 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']).
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
import tuf.formats
|
||||
import tuf.rsa_key
|
||||
|
||||
# See 'log.py' to learn how logging is handled in TUF.
|
||||
logger = logging.getLogger('tuf.keydb')
|
||||
|
||||
# The key database.
|
||||
_keydb_dict = {}
|
||||
|
||||
|
||||
def create_keydb_from_root_metadata(root_metadata):
|
||||
"""
|
||||
<Purpose>
|
||||
Populate the key database with the unique keys found in 'root_metadata'.
|
||||
The database dictionary will conform to 'tuf.formats.KEYDB_SCHEMA' and
|
||||
have the form: {keyid: key, ...}.
|
||||
The 'keyid' conforms to 'tuf.formats.KEYID_SCHEMA' and 'key' to its
|
||||
respective type. In the case of RSA keys, this object would match
|
||||
'RSAKEY_SCHEMA'.
|
||||
|
||||
<Arguments>
|
||||
root_metadata:
|
||||
A dictionary conformant to 'tuf.formats.ROOT_SCHEMA'. The keys found
|
||||
in the 'keys' field of 'root_metadata' are needed by this function.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'root_metadata' does not have the correct format.
|
||||
|
||||
<Side Effects>
|
||||
A function to add the key to the database is called. In the case of RSA
|
||||
keys, this function is add_rsakey().
|
||||
|
||||
The old keydb key database is replaced.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
# Does 'root_metadata' have the correct format?
|
||||
# This check will ensure 'root_metadata' has the appropriate number of objects
|
||||
# and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.ROOT_SCHEMA.check_match(root_metadata)
|
||||
|
||||
# Clear the key database.
|
||||
_keydb_dict.clear()
|
||||
|
||||
# Iterate through the keys found in 'root_metadata' by converting
|
||||
# them to 'RSAKEY_SCHEMA' if their type is 'rsa', and then
|
||||
# adding them the database. Duplicates are avoided.
|
||||
for keyid, key_metadata in root_metadata['keys'].items():
|
||||
if key_metadata['keytype'] == 'rsa':
|
||||
# 'key_metadata' is stored in 'KEY_SCHEMA' format. Call
|
||||
# create_from_metadata_format() to get the key in 'RSAKEY_SCHEMA'
|
||||
# format, which is the format expected by 'add_rsakey()'.
|
||||
rsakey_dict = tuf.rsa_key.create_from_metadata_format(key_metadata)
|
||||
try:
|
||||
add_rsakey(rsakey_dict, keyid)
|
||||
# 'tuf.Error' raised if keyid does not match the keyid for 'rsakey_dict'.
|
||||
except tuf.Error, e:
|
||||
logger.error(e)
|
||||
continue
|
||||
except tuf.KeyAlreadyExistsError, e:
|
||||
logger.warn(e)
|
||||
continue
|
||||
else:
|
||||
logger.warn('Root Metadata file contains a key with an invalid keytype.')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def add_rsakey(rsakey_dict, keyid=None):
|
||||
"""
|
||||
<Purpose>
|
||||
Add 'rsakey_dict' to the key database while avoiding duplicates.
|
||||
If keyid is provided, verify it is the correct keyid for 'rsakey_dict'
|
||||
and raise an exception if it is not.
|
||||
|
||||
<Arguments>
|
||||
rsakey_dict:
|
||||
A dictionary conformant to 'tuf.formats.RSAKEY_SCHEMA'.
|
||||
It has the form:
|
||||
{'keytype': 'rsa',
|
||||
'keyid': keyid,
|
||||
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
||||
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
||||
|
||||
keyid:
|
||||
An object conformant to 'KEYID_SCHEMA'. It is used as an identifier
|
||||
for RSA keys.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rsakey_dict' or 'keyid' does not have the
|
||||
correct format.
|
||||
|
||||
tuf.Error, if 'keyid' does not match the keyid for 'rsakey_dict'.
|
||||
|
||||
tuf.KeyAlreadyExistsError, if 'rsakey_dict' is found in the key database.
|
||||
|
||||
<Side Effects>
|
||||
The keydb key database is modified.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Does 'rsakey_dict' have the correct format?
|
||||
# This check will ensure 'rsakey_dict' has the appropriate number of objects
|
||||
# and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError if the check fails.
|
||||
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
|
||||
|
||||
# Does 'keyid' have the correct format?
|
||||
if keyid is not None:
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.KEYID_SCHEMA.check_match(keyid)
|
||||
|
||||
# Check if the keyid found in 'rsakey_dict' matches 'keyid'.
|
||||
if keyid != rsakey_dict['keyid']:
|
||||
raise tuf.Error('Incorrect keyid '+rsakey_dict['keyid']+' expected '+keyid)
|
||||
|
||||
# Check if the keyid belonging to 'rsakey_dict' is not already
|
||||
# available in the key database before returning.
|
||||
keyid = rsakey_dict['keyid']
|
||||
if keyid in _keydb_dict:
|
||||
raise tuf.KeyAlreadyExistsError('Key: '+keyid)
|
||||
|
||||
_keydb_dict[keyid] = rsakey_dict
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_key(keyid):
|
||||
"""
|
||||
<Purpose>
|
||||
Return the key belonging to 'keyid'.
|
||||
|
||||
<Arguments>
|
||||
keyid:
|
||||
An object conformant to 'tuf.formats.KEYID_SCHEMA'. It is used as an
|
||||
identifier for keys.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'keyid' does not have the correct format.
|
||||
|
||||
tuf.UnknownKeyError, if 'keyid' is not found in the keydb database.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
The key matching 'keyid'. In the case of RSA keys, a dictionary conformant
|
||||
to 'tuf.formats.RSAKEY_SCHEMA' is returned.
|
||||
|
||||
"""
|
||||
|
||||
# Does 'keyid' have the correct format?
|
||||
# This check will ensure 'keyid' has the appropriate number of objects
|
||||
# and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' is the match fails.
|
||||
tuf.formats.KEYID_SCHEMA.check_match(keyid)
|
||||
|
||||
# Return the key belonging to 'keyid', if found in the key database.
|
||||
try:
|
||||
return _keydb_dict[keyid]
|
||||
except KeyError:
|
||||
raise tuf.UnknownKeyError('Key: '+keyid)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def remove_key(keyid):
|
||||
"""
|
||||
<Purpose>
|
||||
Remove the key belonging to 'keyid'.
|
||||
|
||||
<Arguments>
|
||||
keyid:
|
||||
An object conformant to 'tuf.formats.KEYID_SCHEMA'. It is used as an
|
||||
identifier for keys.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'keyid' does not have the correct format.
|
||||
|
||||
tuf.UnknownKeyError, if 'keyid' is not found in key database.
|
||||
|
||||
<Side Effects>
|
||||
The key, identified by 'keyid', is deleted from the key database.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
# Does 'keyid' have the correct format?
|
||||
# This check will ensure 'keyid' has the appropriate number of objects
|
||||
# and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' is the match fails.
|
||||
tuf.formats.KEYID_SCHEMA.check_match(keyid)
|
||||
|
||||
# Remove the key belonging to 'keyid' if found in the key database.
|
||||
if keyid in _keydb_dict:
|
||||
del _keydb_dict[keyid]
|
||||
else:
|
||||
raise tuf.UnknownKeyError('Key: '+keyid)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def clear_keydb():
|
||||
"""
|
||||
<Purpose>
|
||||
Clear the keydb key database.
|
||||
|
||||
<Arguments>
|
||||
None.
|
||||
|
||||
<Exceptions>
|
||||
None.
|
||||
|
||||
<Side Effects>
|
||||
The keydb key database is reset.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
_keydb_dict.clear()
|
||||
106
src/tuf/log.py
Executable file
106
src/tuf/log.py
Executable file
|
|
@ -0,0 +1,106 @@
|
|||
"""
|
||||
<Program Name>
|
||||
log.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
April 4, 2012. Based on a previous version of this module by Geremy Condra.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
A central location for all logging-related configuration.
|
||||
This module should be imported once by the main program.
|
||||
If other modules wish to incorporate 'tuf' logging, they
|
||||
should do the following:
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('tuf')
|
||||
|
||||
'logging' refers to the module name. logging.getLogger() is a function of
|
||||
the module 'logging'. logging.getLogger(name) returns a Logger instance
|
||||
associated with 'name'. Calling getLogger(name) will always return the same
|
||||
instance. In this 'log.py' module, we perform the initial setup for the name
|
||||
'tuf'. The 'log.py' module should only be imported once by the main program.
|
||||
When any other module does a logging.getLogger('tuf'), it is referring to the
|
||||
same 'tuf' instance and its associated settings we set up here in 'log.py'.
|
||||
See http://docs.python.org/library/logging.html#logger-objects
|
||||
for more information.
|
||||
|
||||
We use multiple handlers to process log messages in various ways and to
|
||||
configure each one independently. Instead of using one single manner of
|
||||
processing log messages, we can use two built-in handlers that have already
|
||||
been configured for us. For example, the built-in FileHandler will catch
|
||||
log message and dump them to a file. If we wanted, we could set this file
|
||||
handler to only catch CRITICAL (and greater) messages and save them to a
|
||||
file. The other stream handler would still handle DEBUG-level (and greater)
|
||||
messages.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
_DEFAULT_LOG_LEVEL = logging.INFO
|
||||
_DEFAULT_LOG_FILENAME = 'tuf.log'
|
||||
|
||||
# Set the format for logging messages.
|
||||
_FORMAT_STRING = "[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s"
|
||||
formatter = logging.Formatter(_FORMAT_STRING)
|
||||
|
||||
# Set the handlers for the logger.
|
||||
# The built-in stream handler will log
|
||||
# messages to 'sys.stderr' and capture
|
||||
# '_DEFAULT_LOG_LEVEL' messages.
|
||||
stream_handler = logging.StreamHandler()
|
||||
stream_handler.setLevel(_DEFAULT_LOG_LEVEL)
|
||||
stream_handler.setFormatter(formatter)
|
||||
|
||||
# Set the built-in file handler. Messages
|
||||
# will be logged to '_DEFAULT_LOG_FILENAME'
|
||||
# and use the logger's default log level.
|
||||
# The file will be opened in append mode.
|
||||
file_handler = logging.FileHandler(_DEFAULT_LOG_FILENAME)
|
||||
file_handler.setFormatter(formatter)
|
||||
|
||||
# Set the logger and its settings.
|
||||
logger = logging.getLogger('tuf')
|
||||
logger.setLevel(_DEFAULT_LOG_LEVEL)
|
||||
logger.addHandler(stream_handler)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Silently ignore logger exceptions.
|
||||
logging.raiseExceptions = False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def set_log_level(log_level):
|
||||
"""
|
||||
<Purpose>
|
||||
Allow the default log level to be overridden.
|
||||
|
||||
<Arguments>
|
||||
log_level:
|
||||
The log level to set for the logger and handler(s).
|
||||
E.g., logging.INFO; logging.CRITICAL.
|
||||
|
||||
<Exceptions>
|
||||
None.
|
||||
|
||||
<Side Effects>
|
||||
Overrides the logging level for the internal
|
||||
'logger' and 'handler'.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
logger.setLevel(log_level)
|
||||
stream_handler.setLevel(log_level)
|
||||
96
src/tuf/mirrors.py
Executable file
96
src/tuf/mirrors.py
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
"""
|
||||
<Program Name>
|
||||
mirrors.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
Derived from original mirrors.py written by Geremy Condra.
|
||||
|
||||
<Started>
|
||||
March 12, 2012
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
This module extracts a list of mirror urls corresponding to the file type and
|
||||
the location of the file with respect to the base url.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import urllib
|
||||
|
||||
import tuf.util
|
||||
import tuf.formats
|
||||
|
||||
|
||||
def get_list_of_mirrors(file_type, file_path, mirrors_dict):
|
||||
"""
|
||||
<Purpose>
|
||||
Gets a list of mirror urls from a mirrors dictionary, provided the type of
|
||||
the file and the path of the file with respect to the base url.
|
||||
|
||||
<Arguments>
|
||||
file_type:
|
||||
Type of data needed for download, must correspond to one of the strings in
|
||||
the list ['meta', 'target', 'targets']. 'meta' for metadata file type or
|
||||
'target' or 'targets' for target file type. It should correspond to
|
||||
FILETYPE_SCHEMA format.
|
||||
|
||||
file_path:
|
||||
Path to the file. For instance if the file is a target then we
|
||||
will have something like this: 'http://urlbase/targets_path/file_path'.
|
||||
It should correspond to RELPATH_SCHEMA format.
|
||||
|
||||
mirrors_dict:
|
||||
A mirrors_dict object that corresponds to MIRRORDICT_SCHEMA, where the
|
||||
dict keys are strings and the dict values MIRROR_SCHEMA. An example format
|
||||
of MIRROR_SCHEMA:
|
||||
|
||||
{'url_prefix':
|
||||
'metadata_path': 'metadata/'
|
||||
'targets_path': 'targets/'
|
||||
'confined_target_paths': ['targets/release1', ...]
|
||||
'custom': {...}}
|
||||
|
||||
The 'custom' field is optional.
|
||||
|
||||
<Exceptions>
|
||||
tuf.Error on unknown file type.
|
||||
tuf.FormatError on bad argument.
|
||||
|
||||
<Return>
|
||||
List of mirror urls corresponding to the file_type and file_path. If no
|
||||
match is found, empty list is returned.
|
||||
|
||||
"""
|
||||
|
||||
# Checking if all the arguments have appropriate format.
|
||||
tuf.formats.RELPATH_SCHEMA.check_match(file_path)
|
||||
tuf.formats.MIRRORDICT_SCHEMA.check_match(mirrors_dict)
|
||||
tuf.formats.NAME_SCHEMA.check_match(file_type)
|
||||
|
||||
# Reference to 'path_in_confined_paths' function.
|
||||
in_confined = tuf.util.path_in_confined_paths
|
||||
|
||||
list_of_mirrors = []
|
||||
for mirror_name, mirror_info in mirrors_dict.items():
|
||||
if file_type == 'meta':
|
||||
base = mirror_info['url_prefix']+'/'+mirror_info['metadata_path']
|
||||
|
||||
else:
|
||||
targets_path = mirror_info['targets_path']
|
||||
full_filepath = os.path.join(targets_path, file_path)
|
||||
if not in_confined(full_filepath, mirror_info['confined_target_paths']):
|
||||
continue
|
||||
base = mirror_info['url_prefix']+'/'+mirror_info['targets_path']
|
||||
|
||||
# urllib.quote(string) replaces special characters in string using the %xx
|
||||
# escape. This is done to avoid parsing issues of the URL on the server
|
||||
# side.
|
||||
file_path = urllib.quote(file_path)
|
||||
url = base+'/'+file_path
|
||||
list_of_mirrors.append(url)
|
||||
|
||||
return list_of_mirrors
|
||||
0
src/tuf/pushtools/__init__.py
Normal file
0
src/tuf/pushtools/__init__.py
Normal file
10
src/tuf/pushtools/push.cfg.sample
Executable file
10
src/tuf/pushtools/push.cfg.sample
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
[general]
|
||||
transfer_module = scp
|
||||
metadata_path = targets.txt
|
||||
|
||||
[scp]
|
||||
host =
|
||||
user =
|
||||
identity_file =
|
||||
# remote_dir must be similarly configured on the repository side.
|
||||
remote_dir = ~/test/pushes
|
||||
104
src/tuf/pushtools/push.py
Executable file
104
src/tuf/pushtools/push.py
Executable file
|
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2010 The Update Framework. See LICENSE for licensing information.
|
||||
"""
|
||||
This script provides a way for developers to push a signed targets metadata
|
||||
file and the referenced targets to a repository. The repository adds these
|
||||
files to the repository by running the receivetools/receive.py script.
|
||||
|
||||
Usage:
|
||||
./push.py COMMAND COMMAND_ARGS
|
||||
|
||||
Known commands:
|
||||
push
|
||||
|
||||
Example:
|
||||
./push.py push push.cfg targets.txt targetfile1 targetfile2
|
||||
|
||||
Details of 'push' command:
|
||||
|
||||
The developer provides the path to a configuration file that lists:
|
||||
* The path to the targets metadata file.
|
||||
* The name of the transfer module to use for transferring the
|
||||
files to the repository (e.g. 'scp').
|
||||
* Configuration information that is specific to the transfer
|
||||
module.
|
||||
|
||||
See the push.cfg.sample file for an example configuration file.
|
||||
|
||||
The transfer module needs the following functionality:
|
||||
* A way to transfer target files and the new metadata file to the
|
||||
repository.
|
||||
|
||||
The transfer module may also include the following functionality:
|
||||
* A way to determine whether the repository has rejected the push and, if
|
||||
so, the reason for the rejection.
|
||||
"""
|
||||
|
||||
import ConfigParser
|
||||
import sys
|
||||
|
||||
import tuf
|
||||
|
||||
|
||||
def _read_config_file(filename):
|
||||
"""Return a dictionary where the keys are section names and the values
|
||||
are dictionaries of keys/values in that section.
|
||||
"""
|
||||
config = ConfigParser.RawConfigParser()
|
||||
config.read(filename)
|
||||
configdict = {}
|
||||
for section in config.sections():
|
||||
configdict[section] = {}
|
||||
for key, value in config.items(section):
|
||||
if key in ['seconds', 'minutes', 'days', 'hours']:
|
||||
value = int(value)
|
||||
elif key in ['keyids']:
|
||||
value = value.split(',')
|
||||
if key in configdict[section]:
|
||||
configdict[section][key] = []
|
||||
else:
|
||||
configdict[section][key] = value
|
||||
return configdict
|
||||
|
||||
|
||||
def _get_transfer_module(modulename):
|
||||
__import__("transfer.%s" % modulename)
|
||||
return sys.modules["transfer.%s" % modulename]
|
||||
|
||||
|
||||
def push(args):
|
||||
config = _read_config_file(args[0])
|
||||
targets = args[1:]
|
||||
transfermod = _get_transfer_module(config['general']['transfer_module'])
|
||||
|
||||
context = transfermod.TransferContext(config['scp'])
|
||||
context.transfer(targets, config['general']['metadata_path'])
|
||||
context.finalize()
|
||||
|
||||
|
||||
def getstatus():
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def usage():
|
||||
print "Known commands:"
|
||||
print " push config_file target [target ...]"
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
usage()
|
||||
cmd = sys.argv[1]
|
||||
args = sys.argv[2:]
|
||||
if cmd in ["push", "getstatus"]:
|
||||
try:
|
||||
globals()[cmd](args)
|
||||
except tuf.BadPasswordError:
|
||||
print >> sys.stderr, "Password incorrect."
|
||||
else:
|
||||
usage()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
463
src/tuf/pushtools/receivetools/receive.py
Executable file
463
src/tuf/pushtools/receivetools/receive.py
Executable file
|
|
@ -0,0 +1,463 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2010 The Update Framework. See LICENSE for licensing information.
|
||||
"""
|
||||
This script can be run on a repository to import new targets metadata and
|
||||
target files into the repository. This is intended to work with the
|
||||
developer push tools. When this script finds a new directory pushed
|
||||
by a developer, it checks the metadata and target files and, if everything
|
||||
is good, adds the files to the repository.
|
||||
|
||||
Usage:
|
||||
./receive.py
|
||||
|
||||
Arguments:
|
||||
None
|
||||
|
||||
Details:
|
||||
|
||||
The script looks in a set of pre-defined push locations that one or more
|
||||
developers may have uploaded files to using the push tools. If it finds
|
||||
a valid push, it moves the push directory to a 'processing' directory and
|
||||
also copies the pushed files to a temporary directory (these files are the
|
||||
ones used by this script).
|
||||
|
||||
Once the repository has received and copied a set of target files and the
|
||||
corresponding targets metadata file, it performs the following checks that:
|
||||
|
||||
* The metadata file is newer than the last metadata file of that type.
|
||||
* The metadata has not expired.
|
||||
* The metadata is signed by a threshold of keys that belong to the
|
||||
appropriate role.
|
||||
* The target files described in the metadata are the same target files as
|
||||
were provided.
|
||||
|
||||
Once the verification is completed, the script backs up the files to be replaced
|
||||
or obsoleted and then adds the new files to the repository. The script then
|
||||
moves the push directory from the pushroot's 'processing' directory to its
|
||||
'processed' directory and write a 'received.result' file to the push directory
|
||||
that contains either the word SUCCESS or FAILURE. There may also be a
|
||||
received.log file written, as well. The client can check these files to determine
|
||||
whether the push was accepted and, if not, what the problem was.
|
||||
|
||||
This script does not generate a new release.txt file or timestamp.txt file.
|
||||
That needs to be done after this script runs if any pushes have been received.
|
||||
In some cases, it may make sense to have this script operate on a non-live
|
||||
copy of the repository and then rsync the files after all changes have been
|
||||
made.
|
||||
|
||||
This script does not handle delegated targets metadata. When the time comes to
|
||||
implement that here, care needs to be taken to ensure that a delegated targets
|
||||
metadata file can't replace a target it shouldn't. Such untrusted files would
|
||||
not trick clients, but they would prevent clients from obtaining updates. It
|
||||
may be the case that making this script general enough to handle delegated
|
||||
targets metadata may not be worth it. Such situations may be better suited to
|
||||
customization per-project because the script could then leverage knowledge
|
||||
about how the delegation is supposed to be done.
|
||||
|
||||
Example output of this script:
|
||||
|
||||
$ python receivetools/receive.py
|
||||
[2010-05-12 15:54:55,683] [tuf] [DEBUG] Looking for pushes in pushroot /tmp/tuf/test/pushes
|
||||
[2010-05-12 15:54:55,684] [tuf] [INFO] Processing /tmp/tuf/test/pushes/1273704893.55
|
||||
[2010-05-12 15:54:55,684] [tuf] [DEBUG] Moving push directory to
|
||||
/tmp/tuf/test/pushes/processing/1273704893.55
|
||||
[2010-05-12 15:54:55,693] [tuf] [DEBUG] Metadata timestamp is 2010-05-06 00:13:46
|
||||
(replacing metdata with timestamp 2010-05-06 00:13:46)
|
||||
[2010-05-12 15:54:55,693] [tuf] [DEBUG] Metadata will expire at 2011-05-06 00:13:46
|
||||
[2010-05-12 15:54:55,693] [tuf] [DEBUG] Signatures: threshold: 1 / good:
|
||||
[u'50792c6713637cf09e1aeb3805fc6d18f80d0a4f4ab7895f4a7cdf1abd7f5b0a'] / bad [] /
|
||||
unrecognized: [] / unauthorized: [] / unknown method: []
|
||||
[2010-05-12 15:54:55,693] [tuf] [INFO] Number of targets specified: 1
|
||||
[2010-05-12 15:54:55,694] [tuf] [DEBUG] Size of target
|
||||
/tmp/tuf/test/pushes/processing/1273704893.55/targets/test.txt is correct (5 bytes).
|
||||
[2010-05-12 15:54:55,694] [tuf] [DEBUG] 1 hashes to check.
|
||||
[2010-05-12 15:54:55,694] [tuf] [DEBUG] sha256 hash of target
|
||||
/tmp/tuf/test/pushes/processing/1273704893.55/targets/test.txt is correct
|
||||
(f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2).
|
||||
[2010-05-12 15:54:55,694] [tuf] [INFO] Backing up target
|
||||
/var/tuf/repo/targets/test.txt to /var/tuf/replaced/1273704893.55Tsr9QJ/targets/test.txt
|
||||
[2010-05-12 15:54:55,695] [tuf] [INFO] Backing up old metadata
|
||||
/var/tuf/repo/meta/targets.txt to /var/tuf/replaced/1273704893.55Tsr9QJ/targets.txt
|
||||
[2010-05-12 15:54:55,695] [tuf] [INFO] Adding target to repo: /var/tuf/repo/targets/test.txt
|
||||
[2010-05-12 15:54:55,695] [tuf] [INFO] Adding new targets metadata to repo:
|
||||
/var/tuf/repo/meta/targets.txt
|
||||
[2010-05-12 15:54:55,696] [tuf] [DEBUG] Moving push directory to
|
||||
/tmp/tuf/test/pushes/processed/1273704893.55
|
||||
[2010-05-12 15:54:55,696] [tuf] [INFO] Completed processing of all push roots.
|
||||
Push successes = 1, failures = 0.
|
||||
|
||||
"""
|
||||
|
||||
import errno
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import tuf.formats
|
||||
import tuf.hash
|
||||
import tuf.keydb
|
||||
import tuf.log
|
||||
import tuf.sig
|
||||
|
||||
logger = tuf.log.get_logger()
|
||||
|
||||
|
||||
# These are the locations where developers may push files to using the
|
||||
# push tools. Each push will be in its own directory with the
|
||||
# developer's push root. Each developer's push either must have the
|
||||
# directories 'processed' and 'processing' which are writable by this
|
||||
# script.
|
||||
PUSHROOTS = ['/home/SOMEUSER/pushes']
|
||||
|
||||
# This is the directory where the repository resides. As far as this
|
||||
# script is concerned, this is the live repository. Changes will be made
|
||||
# directory to this repository.
|
||||
REPODIR = '/var/tuf/repo'
|
||||
|
||||
# This is the metadata directory within the repository.
|
||||
METADIR = os.path.join(REPODIR, 'meta')
|
||||
|
||||
# This is the targets directory within the repository.
|
||||
TARGETSDIR = os.path.join(REPODIR, 'targets')
|
||||
|
||||
# Where replaced files will be stored. This will be used globally rather
|
||||
# than a separate backup/replaced files directory for each pushroot.
|
||||
BACKUPDIR = '/var/tuf/replaced'
|
||||
|
||||
|
||||
def run():
|
||||
"""Look for and process pushes found in any PUSHROOTS."""
|
||||
successcount = 0
|
||||
failurecount = 0
|
||||
for pushroot in PUSHROOTS:
|
||||
if not os.path.exists(pushroot):
|
||||
logger.error("The pushroot %s does not exist. Skipping." % pushroot)
|
||||
continue
|
||||
logger.debug('Looking for pushes in pushroot %s' % pushroot)
|
||||
if not os.path.exists(os.path.join(pushroot, 'processed')):
|
||||
os.mkdir(os.path.join(pushroot), 'processed')
|
||||
if not os.path.exists(os.path.join(pushroot, 'processing')):
|
||||
os.mkdir(os.path.join(pushroot), 'processing')
|
||||
# TODO: use only the newest push and move the others to the 'processed'
|
||||
# directory, adding an appropriate log file.
|
||||
for name in os.listdir(pushroot):
|
||||
pushpath = os.path.join(pushroot, name)
|
||||
if name == 'processed' or name == 'processing':
|
||||
continue
|
||||
if os.path.isdir(pushpath):
|
||||
if not os.path.exists(os.path.join(pushpath, 'info')):
|
||||
logger.warn("Skipping incomplete push %s (no info file)."
|
||||
% pushpath)
|
||||
continue
|
||||
success = process_new_push(pushroot, name)
|
||||
if success:
|
||||
successcount += 1
|
||||
else:
|
||||
failurecount += 1
|
||||
logger.info("Completed processing of all push roots. "
|
||||
"Push successes = %s, failures = %s." %
|
||||
(successcount, failurecount))
|
||||
|
||||
|
||||
def process_old_push(pushroot, pushname):
|
||||
"""When there are multiple pushes, only the newest is used. All of the
|
||||
older ones are ignored. This function makes the appropriate logs for
|
||||
an old push and moves it into the 'processed' directory."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def append_to_receive_log(pushpath, msg):
|
||||
"""Appends msg to [pushpath]/receive.log"""
|
||||
try:
|
||||
fp = open(os.path.join(pushpath, 'receive.log'), 'a')
|
||||
except IOError, e:
|
||||
raise tuf.Error('Unable to open receive log file: %s' % e)
|
||||
try:
|
||||
fp.write(msg)
|
||||
fp.write('\n')
|
||||
finally:
|
||||
fp.close()
|
||||
|
||||
|
||||
def record_receive_result(pushpath, success):
|
||||
"""Writes the [pushpath]/receive.result file that indicates SUCCESS or
|
||||
FAILURE."""
|
||||
try:
|
||||
fp = open(os.path.join(pushpath, 'receive.result'), 'w')
|
||||
except IOError, e:
|
||||
raise tuf.Error('Unable to open receive result file: %s' % e)
|
||||
try:
|
||||
if success:
|
||||
fp.write("SUCCESS")
|
||||
else:
|
||||
fp.write("FAILURE")
|
||||
fp.write('\n')
|
||||
finally:
|
||||
fp.close()
|
||||
|
||||
|
||||
def process_new_push(pushroot, pushname):
|
||||
"""Process a push.
|
||||
|
||||
This will check the validity of targets metadata in the push (including
|
||||
whether the signatures are trusted) and, if valid, will copy the targets
|
||||
metadata and target files to the repository.
|
||||
|
||||
Args:
|
||||
pushroot:
|
||||
pushname:
|
||||
"""
|
||||
logger.info("Processing %s/%s" % (pushroot, pushname))
|
||||
|
||||
pushpath = os.path.join(pushroot, 'processing', pushname)
|
||||
logger.debug("Moving push directory to %s" % pushpath)
|
||||
os.rename(os.path.join(pushroot, pushname), pushpath)
|
||||
|
||||
# Copy the contents of pushpath to a temp directory. We don't want the
|
||||
# user to be able to modify the files we work with.
|
||||
tempdir = tempfile.mkdtemp()
|
||||
pushtempdir = os.path.join(tempdir, 'push')
|
||||
shutil.copytree(pushpath, pushtempdir)
|
||||
|
||||
try:
|
||||
try:
|
||||
_process_copied_push(pushpath)
|
||||
record_receive_result(pushpath, True)
|
||||
return True
|
||||
except (tuf.Error, OSError), e:
|
||||
record_receive_result(pushpath, False)
|
||||
append_to_receive_log(pushpath, str(e))
|
||||
logger.exception("Processing failed for push: %s/%s" %
|
||||
(pushroot, pushname))
|
||||
return False
|
||||
finally:
|
||||
processedpath = os.path.join(pushroot, 'processed', pushname)
|
||||
logger.debug("Moving push directory to %s" % processedpath)
|
||||
os.rename(pushpath, processedpath)
|
||||
|
||||
|
||||
def _process_copied_push(pushpath):
|
||||
"""Helper function for process_new_push.
|
||||
|
||||
This does the actual work of copying pushpath to a temp directory,
|
||||
checking the metadata and targets, and copying the files to the
|
||||
repository on success. The push is valid and successfully processed
|
||||
if no exception is raised.
|
||||
|
||||
Raises:
|
||||
OSError or tuf.Error.
|
||||
"""
|
||||
pushname = os.path.basename(pushpath)
|
||||
|
||||
# Read the metadata of the current repository.
|
||||
rootmetapath = os.path.join(METADIR, 'root.txt')
|
||||
root_json = tuf.util.load_json_file(rootmetapath)
|
||||
root_meta = root_json['signed']
|
||||
root_obj = tuf.formats.RootFile.from_meta(root_meta)
|
||||
keydb = tuf.keydb.KeyDB.create_from_root(root_obj)
|
||||
|
||||
# Determine the name of the targets metadata file that was pushed.
|
||||
targetsmetafile = None
|
||||
try:
|
||||
fp = open(os.path.join(pushpath, 'info'), 'r')
|
||||
except IOError, e:
|
||||
raise tuf.Error('Unable to open push info file: %s' % e)
|
||||
try:
|
||||
for line in fp:
|
||||
parts = line.strip().split('=')
|
||||
if parts[0] == 'metadata':
|
||||
if parts[1] != 'targets.txt':
|
||||
raise NotImplementedError('No support yet for pushing ' +
|
||||
'delegated targets metadata.')
|
||||
else:
|
||||
targetsmetafile = parts[1]
|
||||
break
|
||||
else:
|
||||
raise tuf.Error('No metadata= line in push info file.')
|
||||
finally:
|
||||
fp.close()
|
||||
|
||||
# Read the new metadata that was pushed.
|
||||
targetsmetapath = os.path.join(pushpath, targetsmetafile)
|
||||
targets_json = tuf.util.load_json_file(targetsmetapath)
|
||||
|
||||
# Read the existing metadata from the repository.
|
||||
repotargetsmetapath = os.path.join(METADIR, targetsmetafile)
|
||||
|
||||
# Check the metadata. This is mostly to make sure we don't replace good
|
||||
# metadata with bad metadata as clients do their own security checking.
|
||||
# This is what we check:
|
||||
# * it is newer than the last metadata.
|
||||
# * it has not expired.
|
||||
# * all signatures valid.
|
||||
# * a threshold of trusted signatures. only check the delegating
|
||||
# role rather than the trust hierachy all the way up.
|
||||
# * all of the files listed in the metadata were provided and have
|
||||
# the sizes and hashes listed in the metadata.
|
||||
|
||||
# Check that the new metadata is newer than the existing metadata.
|
||||
if os.path.exists(repotargetsmetapath):
|
||||
repo_targets_json = tuf.util.load_json_file(targetsmetapath)
|
||||
cur_timestamp_string = repo_targets_json['signed']['ts']
|
||||
cur_meta_timestamp = tuf.formats.parse_time(cur_timestamp_string)
|
||||
|
||||
new_timestamp_string = targets_json['signed']['ts']
|
||||
new_meta_timestamp = tuf.formats.parse_time(new_timestamp_string)
|
||||
|
||||
# Allowing equality makes testing/development easier.
|
||||
if cur_meta_timestamp > new_meta_timestamp:
|
||||
raise tuf.Error("Existing metadata timestamp (%s) is newer than "
|
||||
"the new metadata's timestamp (%s)" %
|
||||
(cur_timestamp_string, new_timestamp_string))
|
||||
else:
|
||||
logger.debug('Metadata timestamp is %s (replacing metdata with '
|
||||
'timestamp %s)' %
|
||||
(new_timestamp_string, cur_timestamp_string))
|
||||
|
||||
else:
|
||||
logger.warn("The old targets metadata file %s doesn't exist in "
|
||||
"the repo. Skipping timestamp check." %
|
||||
repotargetsmetapath)
|
||||
|
||||
# Ensure the metadata is not expired.
|
||||
expiration_string = repo_targets_json['signed']['expires']
|
||||
expiration_timestamp = tuf.formats.parse_time(expiration_string)
|
||||
if expiration_timestamp <= time.time():
|
||||
raise tuf.Error("Pushed metadata expired at %s" % expiration_string)
|
||||
else:
|
||||
logger.debug('Metadata will expire at %s' % expiration_string)
|
||||
|
||||
# This raises tuf.BadSignature if the check fails.
|
||||
status = tuf.sig.check_signatures(targets_json, keydb, role='targets')
|
||||
logger.debug('Signatures: %s' % status)
|
||||
|
||||
logger.info("Number of targets specified: %s" %
|
||||
len(targets_json['signed']['targets'].keys()))
|
||||
|
||||
for targetrelpath, targetinfo in targets_json['signed']['targets'].items():
|
||||
targetpath = os.path.join(pushpath, 'targets', targetrelpath)
|
||||
|
||||
# Check that the target was provided.
|
||||
if not os.path.exists(targetpath):
|
||||
raise tuf.Error('The specified target file was not provided: %s',
|
||||
targetrelpath)
|
||||
|
||||
# Check size.
|
||||
actualsize = os.path.getsize(targetpath)
|
||||
if actualsize != targetinfo['length']:
|
||||
raise tuf.Error('The size of target file %s is incorrect: ' +
|
||||
'was %s, expected %s' % (targetrelpath, actualsize,
|
||||
targetinfo['length']))
|
||||
else:
|
||||
logger.debug('Size of target %s is correct (%s bytes).' %
|
||||
(targetpath, actualsize))
|
||||
|
||||
# Check hashes.
|
||||
hashcount = len(targetinfo['hashes'].items())
|
||||
if hashcount == 0:
|
||||
raise tuf.Error('Empty hashes dictionary.')
|
||||
else:
|
||||
logger.debug('%s hashes to check.' % hashcount)
|
||||
for hashalg, hashval in targetinfo['hashes'].items():
|
||||
d_obj = tuf.hash.Digest(hashalg)
|
||||
d_obj.update_filename(targetpath)
|
||||
if d_obj.format() != hashval:
|
||||
raise tuf.Error('%s hash does not match: was %s, expected %s' %
|
||||
(hashalg, d_obj.format(), hashval))
|
||||
else:
|
||||
logger.debug('%s hash of target %s is correct (%s).' %
|
||||
(hashalg, targetpath, hashval))
|
||||
|
||||
# At this point, the targets metadata and all specified files have been
|
||||
# verified.
|
||||
|
||||
# Remove the files referenced by the old targets metadata as well as the
|
||||
# old targets metadata itself.
|
||||
_remove_old_files(repotargetsmetapath, pushname)
|
||||
|
||||
# Copy the new target files into place on the repository.
|
||||
for targetrelpath in targets_json['signed']['targets'].keys():
|
||||
srcpath = os.path.join(pushpath, 'targets', targetrelpath)
|
||||
destpath = os.path.join(TARGETSDIR, targetrelpath)
|
||||
logger.info("Adding target to repo: %s" % destpath)
|
||||
destdir = os.path.dirname(destpath)
|
||||
if not os.path.exists(destdir):
|
||||
os.mkdir(destdir)
|
||||
shutil.copy(srcpath, destpath)
|
||||
|
||||
# Copy the targets metadata into place on the repository.
|
||||
logger.info("Adding new targets metadata to repo: %s" %
|
||||
repotargetsmetapath)
|
||||
shutil.copy(targetsmetapath, repotargetsmetapath)
|
||||
|
||||
|
||||
def _remove_old_files(oldtargetsfile, pushname):
|
||||
"""Remove metadata and target files that will be replaced.
|
||||
|
||||
This does not take into account any targets that are the same between
|
||||
the old and new metadata. For simplicity, all old targets are removed
|
||||
and thus even targets that remained the same will need to be copied
|
||||
into place after this has been called.
|
||||
|
||||
This function currently assumes that the the metadata file is the
|
||||
top-level targets.txt file rather than a delegated metadata file.
|
||||
|
||||
Args:
|
||||
oldtargetsfile: The old targets metadata file that is to be
|
||||
replaced, along with all of its referenced targets.
|
||||
"""
|
||||
if not os.path.exists(oldtargetsfile):
|
||||
logger.warn("The old targets metadata file %s doesn't exist in "
|
||||
"the repo. Skipping file backup." % oldtargetsfile)
|
||||
return
|
||||
|
||||
backupdestdir = tempfile.mktemp(prefix=pushname, dir=BACKUPDIR)
|
||||
os.mkdir(backupdestdir)
|
||||
backuptargetsdir = os.path.join(backupdestdir, 'targets')
|
||||
os.mkdir(backuptargetsdir)
|
||||
|
||||
targets_json = tuf.util.load_json_file(oldtargetsfile)
|
||||
for targetrelpath in targets_json['signed']['targets'].keys():
|
||||
curtargetpath = os.path.join(TARGETSDIR, targetrelpath)
|
||||
baktargetpath = os.path.join(backuptargetsdir, targetrelpath)
|
||||
logger.info("Backing up target %s to %s" % (curtargetpath, baktargetpath))
|
||||
if os.path.exists(curtargetpath):
|
||||
mkdir_p(os.path.dirname(baktargetpath))
|
||||
os.rename(curtargetpath, baktargetpath)
|
||||
else:
|
||||
logger.warn("The old target %s doesn't exist in the repo." %
|
||||
curtargetpath)
|
||||
|
||||
baktargetsmetafile = os.path.join(backupdestdir, 'targets.txt')
|
||||
logger.info("Backing up old metadata %s to %s" % (oldtargetsfile,
|
||||
baktargetsmetafile))
|
||||
os.rename(oldtargetsfile, baktargetsmetafile)
|
||||
|
||||
|
||||
def mkdir_p(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError, err:
|
||||
if err.errno == errno.EEXIST:
|
||||
pass
|
||||
else: raise
|
||||
|
||||
|
||||
def _check_directories_exist():
|
||||
"""Check that the various defined directories exist."""
|
||||
# We don't check the PUSHROOTS here because we consider it non-fatal
|
||||
# if those are missing. A log message is issued if any of those are
|
||||
# missing.
|
||||
dirs_to_check = {'REPODIR':REPODIR, 'METADIR':METADIR,
|
||||
'TARGETSDIR':TARGETSDIR, 'BACKUPDIR':BACKUPDIR}
|
||||
for name, path in dirs_to_check.items():
|
||||
if not os.path.exists(path):
|
||||
logger.error("%s directory does not exist: %s" % (name, path))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_check_directories_exist()
|
||||
run()
|
||||
0
src/tuf/pushtools/transfer/__init__.py
Executable file
0
src/tuf/pushtools/transfer/__init__.py
Executable file
112
src/tuf/pushtools/transfer/scp.py
Executable file
112
src/tuf/pushtools/transfer/scp.py
Executable file
|
|
@ -0,0 +1,112 @@
|
|||
# Copyright 2010 The Update Framework. See LICENSE for licensing information.
|
||||
"""
|
||||
SCP transfer module for the developer push mechanism.
|
||||
|
||||
This will use scp to upload a push directory to the repository. The directory
|
||||
will be named with the current timestamp in the format XXXXXXXXXX.XX. The
|
||||
directory will contain a file named 'info' that provides information about
|
||||
the push, the signed metadata file, and a 'targets' directory that contains
|
||||
the targets specified in the metadata.
|
||||
|
||||
Use of this module requires the following section to be present in the push
|
||||
configuration file provided to push.py:
|
||||
|
||||
[scp]
|
||||
host = somehost
|
||||
user = someuser
|
||||
identity_file = optional_path_to_ssh_key
|
||||
remote_dir = ~/pushes
|
||||
|
||||
The remote_dir should correspond to a pushroot configured in the repository's
|
||||
receive.py script.
|
||||
|
||||
This transfer module will output to stdout the commands it runs and the output
|
||||
of those commands.
|
||||
|
||||
Example:
|
||||
|
||||
$ python pushtools/push.py push push.cfg test.txt
|
||||
Running command: scp -r /tmp/tmpc8PiXo somehost:~/test/pushes/1273704893.55
|
||||
info 100% 21 0.0KB/s 00:00
|
||||
targets.txt 100% 771 0.8KB/s 00:00
|
||||
test.txt 100% 5 0.0KB/s 00:00
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
|
||||
class TransferContext(object):
|
||||
|
||||
def __init__(self, config):
|
||||
self.host = config['host']
|
||||
self.user = config.get('user')
|
||||
self.identity_file = config.get('identity_file')
|
||||
self.remote_dir = config.get('remote_dir', '.')
|
||||
|
||||
def transfer(self, target_paths, metadata_path):
|
||||
"""
|
||||
Create a local temporary directory with an additional file used to
|
||||
communicate additional information to the repository. This directory
|
||||
will be transferred to the repository.
|
||||
"""
|
||||
|
||||
basecommand = ['scp']
|
||||
if self.identity_file:
|
||||
basecommand.extend(['-i', self.identity_file])
|
||||
|
||||
timestamp = time.time()
|
||||
dest = ""
|
||||
if self.user:
|
||||
dest += "%s@"
|
||||
dest += "%s:%s/%s" % (self.host, self.remote_dir, timestamp)
|
||||
|
||||
tempdir = tempfile.mkdtemp()
|
||||
try:
|
||||
# Make sure the temp directory is world-readable as the permissions
|
||||
# get carried over in the scp'ing.
|
||||
os.chmod(tempdir, 0755)
|
||||
|
||||
# Create a file that tells the repository the name of the targets
|
||||
# metadata file. For delegation, this will be the only way the
|
||||
# the repository knows the full role name.
|
||||
fp = open(os.path.join(tempdir, 'info'), 'w')
|
||||
fp.write("metadata=%s\n" % metadata_path)
|
||||
fp.close()
|
||||
|
||||
# Copy the metadata.
|
||||
basename = os.path.basename(metadata_path)
|
||||
shutil.copy(metadata_path, os.path.join(tempdir, basename))
|
||||
|
||||
# Create a directory that all target files will be put in before
|
||||
# being transferred.
|
||||
targetsdir = os.path.join(tempdir, 'targets')
|
||||
os.mkdir(targetsdir)
|
||||
|
||||
# This is quite inefficient for large files, but just copy all
|
||||
# targets into the correct directory structure.
|
||||
for path in target_paths:
|
||||
dirname = os.path.dirname(path)
|
||||
basename = os.path.basename(path)
|
||||
if dirname and not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
shutil.copy(path, os.path.join(targetsdir, basename))
|
||||
|
||||
# This will create the 'timestamp' directory on the remote host and
|
||||
# it will contain the info file and an empty targets directory.
|
||||
command = basecommand[:]
|
||||
command.append('-r') # recursive
|
||||
command.append(tempdir)
|
||||
command.append(dest)
|
||||
print "Running command: %s" % ' '.join(command)
|
||||
# Raises subprocess.CalledProcessError on failure.
|
||||
subprocess.check_call(command)
|
||||
|
||||
finally:
|
||||
shutil.rmtree(tempdir)
|
||||
|
||||
def finalize(self):
|
||||
pass
|
||||
0
src/tuf/repo/__init__.py
Executable file
0
src/tuf/repo/__init__.py
Executable file
485
src/tuf/repo/keystore.py
Executable file
485
src/tuf/repo/keystore.py
Executable file
|
|
@ -0,0 +1,485 @@
|
|||
"""
|
||||
<Program Name>
|
||||
keystore.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
March 28, 2012. Based on a previous version of this module by Geremy Condra.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Help store private keys in encrypted files and provide functions to load and
|
||||
save a keystore database. The database contains all of the keys needed to
|
||||
sign a repository's Metadata files, such as 'root.txt' and 'release.txt'.
|
||||
|
||||
Originally, this stored the keys in one file- we've changed that so that it
|
||||
instead encrypts them separately, naming them according to their keyid value.
|
||||
|
||||
This changes the semantics of the system considerably- first, the 'fname'
|
||||
passed in was originally treated as a filename. We now treat it as the name
|
||||
of a directory.
|
||||
|
||||
Secondly, it no longer makes sense to provide access to keys which do not
|
||||
match the given decryption key.
|
||||
|
||||
Thirdly, the semantics of adding keys has changed. It does not make sense to
|
||||
have only one key for the entire keystore, and as a result, we're requiring
|
||||
that the password be set at the point where the key is added.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import binascii
|
||||
import logging
|
||||
|
||||
import evpy.cipher
|
||||
|
||||
import tuf.rsa_key
|
||||
import tuf.util
|
||||
|
||||
# See 'log.py' to learn how logging is handled in TUF.
|
||||
logger = logging.getLogger('tuf.keystore')
|
||||
|
||||
json = tuf.util.import_json()
|
||||
|
||||
# The delimeter symbol used to separate the different sections
|
||||
# of encrypted files (i.e., salt, IV, ciphertext, passphrase).
|
||||
# This delimeter is arbitrarily chosen and should not occur in
|
||||
# the hexadecimal representations of the fields it is separating.
|
||||
_ENCRYPTION_DELIMETER = '@@@@'
|
||||
|
||||
# A password is set for each key added to the keystore.
|
||||
# The passwords dict has the form: {keyid: 'password', ...}
|
||||
_key_passwords = {}
|
||||
|
||||
# The keystore database, which has the form:
|
||||
# {keyid: key, ...}.
|
||||
_keystore = {}
|
||||
|
||||
|
||||
def add_rsakey(rsakey_dict, password, keyid=None):
|
||||
"""
|
||||
<Purpose>
|
||||
Add 'rsakey_dict' to the keystore database while ensuring only
|
||||
unique keys are added. If 'keyid' is provided, verify it is the
|
||||
correct keyid for 'rsakey_dict' and raise an exception otherwise.
|
||||
|
||||
<Arguments>
|
||||
rsakey_dict:
|
||||
A dictionary conformant to 'tuf.formats.RSAKEY_SCHEMA', which
|
||||
has the form:
|
||||
{'keytype': 'rsa',
|
||||
'keyid': keyid,
|
||||
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
||||
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
||||
|
||||
password:
|
||||
The object containing the password needed to encrypt and decrypt
|
||||
the key file (i.e., '<keyid>.key'). It must conform to
|
||||
'PASSWORD_SCHEMA'.
|
||||
|
||||
keyid:
|
||||
An object conformant to 'KEYID_SCHEMA'. It is used as an identifier
|
||||
for RSA keys. This particular keyid should be extracted by the caller
|
||||
from the file name used by the key file ('<keyid>.key') to ensure it
|
||||
was correctly named.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rsakey_dict' or 'keyid' has an incorrect format.
|
||||
|
||||
tuf.Error, if 'keyid' argument does not match the keyid for 'rsakey_dict'.
|
||||
|
||||
tuf.KeyAlreadyExistsError, if 'rsakey_dict' is found in the keystore.
|
||||
|
||||
<Side Effects>
|
||||
The '_keystore' and '_key_passwords' dictionaries are modified.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
# Does 'rsakey_dict' have the correct format?
|
||||
# This check will ensure 'rsakey_dict' has the appropriate number of objects
|
||||
# and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
|
||||
|
||||
# Does 'password' have the correct format?
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.PASSWORD_SCHEMA.check_match(password)
|
||||
|
||||
# If 'keyid' was passed as an argument, does it
|
||||
# have the correct format?
|
||||
if keyid:
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.KEYID_SCHEMA.check_match(keyid)
|
||||
|
||||
# Check if the keyid found in 'rsakey_dict' matches
|
||||
# the 'keyid' supplied as an argument.
|
||||
if keyid != rsakey_dict['keyid']:
|
||||
raise tuf.Error('Incorrect keyid '+rsakey_dict['keyid']+' expected '+keyid)
|
||||
|
||||
# Check if the keyid belonging to 'rsakey_dict' is not already
|
||||
# available in the key database.
|
||||
keyid = rsakey_dict['keyid']
|
||||
if keyid in _keystore:
|
||||
raise tuf.KeyAlreadyExistsError('keyid: '+keyid)
|
||||
|
||||
_key_passwords[keyid] = password
|
||||
_keystore[keyid] = rsakey_dict
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def load_keystore_from_keyfiles(directory_name, keyids, passwords):
|
||||
"""
|
||||
<Purpose>
|
||||
Populate the keystore database with the key files found in
|
||||
'directory_name'. Use the user-supplied passwords in 'passwords' to
|
||||
decrypt the key files. Each key file has a corresponding password.
|
||||
|
||||
<Arguments>
|
||||
directory_name:
|
||||
The name of the directory containing the key files ('<keyid>.key'),
|
||||
conformant to tuf.formats.RELPATH_SCHEMA.
|
||||
|
||||
keyids:
|
||||
A list containing the keyids of the signing keys to load.
|
||||
|
||||
passwords:
|
||||
A list containing the password objects to encrypt and decrypt
|
||||
the key files ('<keyid>.key').
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'directory_name' or 'passwords' has an incorrect
|
||||
format.
|
||||
|
||||
<Side Effects>
|
||||
The '_keystore' and '_key_passwords' dictionaries are modified.
|
||||
The key files found in 'directory_name' are read.
|
||||
|
||||
<Returns>
|
||||
A list containing the keyids of the loaded keys.
|
||||
|
||||
"""
|
||||
|
||||
# Does 'directory_name' have the correct format?
|
||||
# This check will ensure 'directory_name' has the appropriate number of
|
||||
# objects and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.RELPATH_SCHEMA.check_match(directory_name)
|
||||
|
||||
# Does 'keyids' have the correct format?
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.KEYIDS_SCHEMA.check_match(keyids)
|
||||
|
||||
# Does 'passwords' have the correct format?
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.PASSWORDS_SCHEMA.check_match(passwords)
|
||||
|
||||
# Keep a list of the keys loaded.
|
||||
loaded_keys = []
|
||||
|
||||
logger.info('Loading private key(s) from '+repr(directory_name))
|
||||
|
||||
# Make sure the directory exists.
|
||||
if not os.path.exists(directory_name):
|
||||
logger.info('...no such directory. Keystore cannot be loaded.')
|
||||
return
|
||||
|
||||
# Get the list of filenames with a '.key' extension from 'directory_name'.
|
||||
keypaths = []
|
||||
for filename in os.listdir(directory_name):
|
||||
if filename.endswith('.key'):
|
||||
keypaths.append(filename)
|
||||
|
||||
# Decrypt the keys we can from those stored in 'keypaths'.
|
||||
for keypath in keypaths:
|
||||
full_filepath = os.path.join(directory_name, keypath)
|
||||
raw_contents = open(full_filepath, 'rb').read()
|
||||
|
||||
# Try to decrypt the file using one of the passwords in 'passwords'.
|
||||
for password in passwords:
|
||||
try:
|
||||
json_data = _decrypt(raw_contents, password)
|
||||
except tuf.CryptoError:
|
||||
continue
|
||||
|
||||
try:
|
||||
keydata = tuf.util.load_json_string(json_data)
|
||||
except ValueError:
|
||||
# 'keydata' could not be decoded. This will be the case
|
||||
# if the encrypted file could not be decrypted (e.g.,
|
||||
# invalid password).
|
||||
continue
|
||||
|
||||
# Create the key based on its key type. RSA keys currently
|
||||
# supported.
|
||||
if keydata['keytype'] == 'rsa':
|
||||
# 'keydata' is stored in KEY_SCHEMA format. Call
|
||||
# create_from_metadata_format() to get the key in RSAKEY_SCHEMA
|
||||
# format, which is the format expected by 'add_rsakey()'.
|
||||
rsa_key = tuf.rsa_key.create_from_metadata_format(keydata)
|
||||
|
||||
# Ensure the keyid for 'rsa_key' is one of the keys specified in
|
||||
# 'keyids'. If not, do not load the key.
|
||||
if rsa_key['keyid'] not in keyids:
|
||||
continue
|
||||
|
||||
# Ensure the '.key' extension is removed (keypath[:-4]), as we only
|
||||
# need the basefilename containing the full keyid.
|
||||
add_rsakey(rsa_key, password, keyid=keypath[:-4])
|
||||
logger.info('Loaded key: '+rsa_key['keyid'])
|
||||
loaded_keys.append(rsa_key['keyid'])
|
||||
continue
|
||||
else:
|
||||
logger.warn(repr(full_filepath)+' contains an invalid key type.')
|
||||
continue
|
||||
|
||||
logger.info('Done.')
|
||||
return loaded_keys
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def save_keystore_to_keyfiles(directory_name):
|
||||
"""
|
||||
<Purpose>
|
||||
Save all the keys found in the keystore to separate files. The password
|
||||
for each key is stored when it is added to the keystore. Use these
|
||||
passwords to encrypt the key files and save them in encrypted form to
|
||||
'directory_name'.
|
||||
|
||||
<Arguments>
|
||||
directory_name:
|
||||
The name of the directory containing the key files ('<keyid>.key'),
|
||||
conformant to tuf.formats.RELPATH_SCHEMA.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError if 'directory_name is incorrectly formatted.
|
||||
|
||||
<Side Effects>
|
||||
'directory_name' created if it does not exist.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
# Does 'directory_name' have the correct format?
|
||||
# This check will ensure 'directory_name' has the appropriate number of
|
||||
# objects and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.RELPATH_SCHEMA.check_match(directory_name)
|
||||
|
||||
logger.info('Saving private key(s) to '+repr(directory_name))
|
||||
|
||||
# Make sure the directory exists, otherwise create it.
|
||||
if not os.path.exists(directory_name):
|
||||
logger.info('...no such directory. The directory will be created.')
|
||||
os.mkdir(directory_name)
|
||||
|
||||
# Iterate through the keystore keys and save them individually to a file.
|
||||
for keyid, key in _keystore.items():
|
||||
basefilename = os.path.join(directory_name, str(keyid)+'.key')
|
||||
file_object = open(basefilename, 'w')
|
||||
|
||||
# Determine the appropriate format to save the key based on its key type.
|
||||
if key['keytype'] == 'rsa':
|
||||
key_metadata_format = \
|
||||
tuf.rsa_key.create_in_metadata_format(key['keyval'], private=True)
|
||||
else:
|
||||
logger.warn('The keystore has a key with an unrecognized key type.')
|
||||
continue
|
||||
|
||||
# Encrypt 'key_metadata_format' and save it.
|
||||
encrypted_key = _encrypt(json.dumps(key_metadata_format), _key_passwords[keyid])
|
||||
file_object.write(encrypted_key)
|
||||
file_object.close()
|
||||
logger.info(repr(basefilename)+' saved.')
|
||||
|
||||
logger.info('Done.')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def clear_keystore():
|
||||
"""
|
||||
<Purpose>
|
||||
Clear the keystore and key passwords.
|
||||
|
||||
<Arguments>
|
||||
None.
|
||||
|
||||
<Exceptions>
|
||||
None.
|
||||
|
||||
<Side Effect>
|
||||
The keystore and password dicts are reset.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
_keystore.clear()
|
||||
_key_passwords.clear()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def change_password(keyid, old_password, new_password):
|
||||
"""
|
||||
<Purpose>
|
||||
Change the password for 'keyid'.
|
||||
|
||||
<Arguments>
|
||||
keyid:
|
||||
The keyid for the signing key.
|
||||
|
||||
old_password:
|
||||
The old password for the signing key to modify.
|
||||
|
||||
new_password:
|
||||
The new password to set for the signing key.
|
||||
|
||||
<Exceptions>
|
||||
tuf.UnknownKeyError, if 'keyid' is not found in the
|
||||
keystore.
|
||||
|
||||
tuf.BadPasswordError, if 'old_password' is invalid or
|
||||
'new_password' does not have to correct format.
|
||||
|
||||
<Side Effects>
|
||||
The old password for 'keyid' is changed to 'new_password'.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
# Check if 'keyid' is the keystore.
|
||||
if keyid not in _keystore or keyid not in _key_passwords:
|
||||
raise tuf.UnknownKeyError(keyid+' not recognized.')
|
||||
|
||||
# Check if the old password matches.
|
||||
if _key_passwords[keyid] != old_password:
|
||||
raise tuf.BadPasswordError('Old password invalid')
|
||||
|
||||
# If 'new_password' has the correct format, update '_key_passwords'.
|
||||
if tuf.formats.PASSWORD_SCHEMA.matches(new_password):
|
||||
_key_passwords[keyid] = new_password
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_key(keyid):
|
||||
"""
|
||||
<Purpose>
|
||||
Return the key for 'keyid'. If 'keyid' corresponds to an
|
||||
RSA key, the object returned would conform to
|
||||
'tuf.formats.RSAKEY_SCHEMA'. A different key type would return
|
||||
its corresponding key schema.
|
||||
|
||||
<Arguments>
|
||||
keyid:
|
||||
The key identifier.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'keyid' does not have the correct format.
|
||||
|
||||
tuf.UnknownKeyError, if 'keyid' is not found in the keystore.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
The key belonging to 'keyid' (e.g., RSA key).
|
||||
|
||||
"""
|
||||
|
||||
# Does 'keyid' have the correct format?
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.KEYID_SCHEMA.check_match(keyid)
|
||||
|
||||
try:
|
||||
key = _keystore[keyid]
|
||||
except KeyError:
|
||||
raise tuf.UnknownKeyError('The keyid was not found in the keystore')
|
||||
|
||||
return key
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _encrypt(key_data, password):
|
||||
"""
|
||||
Encrypt 'key_data' using the Advanced Encryption Standard algorithm.
|
||||
'password' is treated as the symmetric key, strengthened using SHA512.
|
||||
The key size is 192 bits and AES's mode of operation is set to CBC
|
||||
(Cipher-Block Chaining).
|
||||
|
||||
'key_data' is the JSON string representation of the key. In the case
|
||||
of RSA keys, this format would be 'tuf.formats.RSAKEY_SCHEMA':
|
||||
{'keytype': 'rsa',
|
||||
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
||||
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
||||
|
||||
tuf.CryptoError raised if the encryption fails.
|
||||
|
||||
"""
|
||||
|
||||
# Use AES192 to encrypt 'key_data'.
|
||||
try:
|
||||
salt, iv, ciphertext = evpy.cipher.encrypt(key_data, password)
|
||||
except evpy.cipher.CipherError:
|
||||
raise tuf.CryptoError
|
||||
|
||||
# Return the salt, initialization vector, and ciphertext as a single string.
|
||||
# These three values are delimited by '_ENCRYPTION_DELIMETER' to make
|
||||
# extraction easier. This delimeter is arbitrarily chosen and should not
|
||||
# occur in the hexadecimal representations of the fields it is separating.
|
||||
return binascii.hexlify(salt) + _ENCRYPTION_DELIMETER + \
|
||||
binascii.hexlify(iv) + _ENCRYPTION_DELIMETER + \
|
||||
binascii.hexlify(ciphertext)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _decrypt(key_data, password):
|
||||
"""
|
||||
The corresponding decryption routine for _encrypt().
|
||||
|
||||
tuf.CryptoError raised if the decryption fails.
|
||||
|
||||
"""
|
||||
|
||||
# Extract the salt, initialization vector, and ciphertext from 'key_data'.
|
||||
# These three values are delimited by '_ENCRYPTION_DELIMETER'.
|
||||
# This delimeter is arbitrarily chosen and should not occur in the
|
||||
# hexadecimal representations of the fields it is separating.
|
||||
salt, iv, ciphertext = key_data.split(_ENCRYPTION_DELIMETER)
|
||||
|
||||
# The following decryption routine assumes 'key_data' was encrypted
|
||||
# using AES192.
|
||||
try:
|
||||
key_plaintext = evpy.cipher.decrypt(binascii.unhexlify(salt),
|
||||
binascii.unhexlify(iv),
|
||||
binascii.unhexlify(ciphertext),
|
||||
password)
|
||||
except evpy.cipher.CipherError:
|
||||
raise tuf.CryptoError
|
||||
|
||||
return key_plaintext
|
||||
1298
src/tuf/repo/signercli.py
Executable file
1298
src/tuf/repo/signercli.py
Executable file
File diff suppressed because it is too large
Load diff
1143
src/tuf/repo/signerlib.py
Executable file
1143
src/tuf/repo/signerlib.py
Executable file
File diff suppressed because it is too large
Load diff
625
src/tuf/roledb.py
Executable file
625
src/tuf/roledb.py
Executable file
|
|
@ -0,0 +1,625 @@
|
|||
"""
|
||||
<Program Name>
|
||||
roledb.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
March 21, 2012. Based on a previous version of this module by Geremy Condra.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Represent a collection of roles and their organization. The caller may create
|
||||
a collection of roles from those found in the 'root.txt' metadata file by
|
||||
calling 'create_roledb_from_rootmeta()', or individually by adding roles with
|
||||
'add_role()'. There are many supplemental functions included here that yield
|
||||
useful information about the roles contained in the database, such as
|
||||
extracting all the parent rolenames for a specified rolename, deleting all the
|
||||
delegated roles, retrieving role paths, etc.
|
||||
|
||||
The role database is a dictionary conformant to 'tuf.formats.ROLEDICT_SCHEMA'
|
||||
and has the form:
|
||||
{'rolename': {'keyids': ['34345df32093bd12...'],
|
||||
'threshold': 1
|
||||
'paths': ['path/to/role.txt']}}
|
||||
|
||||
"""
|
||||
|
||||
import tuf.formats
|
||||
import logging
|
||||
|
||||
import tuf.log
|
||||
|
||||
# See 'tuf.log' to learn how logging is handled in TUF.
|
||||
logger = logging.getLogger('tuf.roledb')
|
||||
|
||||
# The role database.
|
||||
_roledb_dict = {}
|
||||
|
||||
|
||||
def create_roledb_from_root_metadata(root_metadata):
|
||||
"""
|
||||
<Purpose>
|
||||
Create a role database containing all of the unique roles found in
|
||||
'root_metadata'.
|
||||
|
||||
<Arguments>
|
||||
root_metadata:
|
||||
A dictionary conformant to 'tuf.formats.ROOT_SCHEMA'. The roles found
|
||||
in the 'roles' field of 'root_metadata' is needed by this function.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'root_metadata' does not have the correct object format.
|
||||
|
||||
tuf.Error, if one of the roles found in 'root_metadata' contains an invalid
|
||||
delegation (i.e., a nonexistent parent role).
|
||||
|
||||
<Side Effects>
|
||||
Calls add_role().
|
||||
|
||||
The old role database is replaced.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
# Does 'root_metadata' have the correct object format?
|
||||
# This check will ensure 'root_metadata' has the appropriate number of objects
|
||||
# and object types, and that all dict keys are properly named.
|
||||
# Raises tuf.FormatError.
|
||||
tuf.formats.ROOT_SCHEMA.check_match(root_metadata)
|
||||
|
||||
# Clear the role database.
|
||||
_roledb_dict.clear()
|
||||
|
||||
# Iterate through the roles found in 'root_metadata'
|
||||
# and add them to '_roledb_dict'. Duplicates are avoided.
|
||||
for rolename, roleinfo in root_metadata['roles'].items():
|
||||
try:
|
||||
add_role(rolename, roleinfo)
|
||||
# tuf.Error raised if the parent role of 'rolename' does not exist.
|
||||
except tuf.Error, e:
|
||||
logger.error(e)
|
||||
raise
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def add_role(rolename, roleinfo, require_parent=True):
|
||||
"""
|
||||
<Purpose>
|
||||
Add to the role database the 'roleinfo' associated with 'rolename'.
|
||||
|
||||
<Arguments>
|
||||
rolename:
|
||||
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
|
||||
(e.g., 'root', 'release', 'timestamp').
|
||||
|
||||
roleinfo:
|
||||
An object representing the role associated with 'rolename', conformant to
|
||||
ROLE_SCHEMA. 'roleinfo' has the form:
|
||||
{'keyids': ['34345df32093bd12...'],
|
||||
'threshold': 1}
|
||||
|
||||
The 'target' role has an additional 'paths' key. Its value is a list of
|
||||
strings representing the path of the target file(s).
|
||||
|
||||
require_parent:
|
||||
A boolean indicating whether to check for a delegating role. add_role()
|
||||
will raise an exception if this parent role does not exist.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rolename' or 'roleinfo' does not have the correct
|
||||
object format.
|
||||
|
||||
tuf.RoleAlreadyExistsError, if 'rolename' has already been added.
|
||||
|
||||
tuf.InvalidNameError, if 'rolename' is improperly formatted.
|
||||
|
||||
<Side Effects>
|
||||
The role database is modified.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
# Does 'rolename' have the correct object format?
|
||||
# This check will ensure 'rolename' has the appropriate number of objects
|
||||
# and object types, and that all dict keys are properly named.
|
||||
tuf.formats.ROLENAME_SCHEMA.check_match(rolename)
|
||||
|
||||
# Does 'roleinfo' have the correct object format?
|
||||
tuf.formats.ROLE_SCHEMA.check_match(roleinfo)
|
||||
|
||||
# Does 'require_parent' have the correct format?
|
||||
tuf.formats.TOGGLE_SCHEMA.check_match(require_parent)
|
||||
|
||||
# Raises tuf.InvalidNameError.
|
||||
_validate_rolename(rolename)
|
||||
|
||||
if rolename in _roledb_dict:
|
||||
raise tuf.RoleAlreadyExistsError('Role already exists: '+rolename)
|
||||
|
||||
# Make sure that the delegating role exists. This should be just a
|
||||
# sanity check and not a security measure.
|
||||
if require_parent and '/' in rolename:
|
||||
# Get parent role. 'a/b/c/d' --> 'a/b/c'.
|
||||
parent_role = '/'.join(rolename.split('/')[:-1])
|
||||
|
||||
if parent_role not in _roledb_dict:
|
||||
raise tuf.Error('Parent role does not exist: '+parent_role)
|
||||
|
||||
_roledb_dict[rolename] = roleinfo
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_parent_rolename(rolename):
|
||||
"""
|
||||
<Purpose>
|
||||
Return the name of the parent role for 'rolename'.
|
||||
Given the rolename 'a/b/c/d', return 'a/b/c'.
|
||||
Given 'a', return ''.
|
||||
|
||||
<Arguments>
|
||||
rolename:
|
||||
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
|
||||
(e.g., 'root', 'release', 'timestamp').
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rolename' does not have the correct object format.
|
||||
|
||||
tuf.UnknownRoleError, if 'rolename' cannot be found in the role database.
|
||||
|
||||
tuf.InvalidNameError, if 'rolename' is incorrectly formatted.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
A string representing the name of the parent role.
|
||||
|
||||
"""
|
||||
|
||||
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
|
||||
_check_rolename(rolename)
|
||||
|
||||
parts = rolename.split('/')
|
||||
parent_rolename = '/'.join(parts[:-1])
|
||||
|
||||
return parent_rolename
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_all_parent_roles(rolename):
|
||||
"""
|
||||
<Purpose>
|
||||
Return a list of roles that are parents of 'rolename'.
|
||||
Given the rolename 'a/b/c/d', return the list:
|
||||
['a', 'a/b', 'a/b/c'].
|
||||
|
||||
Given 'a', return ['a'].
|
||||
|
||||
<Arguments>
|
||||
rolename:
|
||||
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
|
||||
(e.g., 'root', 'release', 'timestamp').
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rolename' does not have the correct object format.
|
||||
|
||||
tuf.UnknownRoleError, if 'rolename' cannot be found in the role database.
|
||||
|
||||
tuf.InvalidNameError, if 'rolename' is improperly formatted.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
A list containing all the parent roles.
|
||||
|
||||
"""
|
||||
|
||||
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
|
||||
_check_rolename(rolename)
|
||||
|
||||
# List of parent roles returned.
|
||||
parent_roles = []
|
||||
|
||||
parts = rolename.split('/')
|
||||
|
||||
# Append the first role to the list.
|
||||
parent_roles.append(parts[0])
|
||||
|
||||
# The 'roles_added' string contains the roles already added. If 'a' and 'a/b'
|
||||
# have been added to 'parent_roles', 'roles_added' would contain 'a/b'
|
||||
roles_added = parts[0]
|
||||
|
||||
# Add each subsequent role to the previous string (with a '/' separator).
|
||||
# This only goes to -1 because we only want to return the parents (so we
|
||||
# ignore the last element).
|
||||
for next_role in parts[1:-1]:
|
||||
parent_roles.append(roles_added+'/'+next_role)
|
||||
roles_added = roles_added+'/'+next_role
|
||||
|
||||
return parent_roles
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def role_exists(rolename):
|
||||
"""
|
||||
<Purpose>
|
||||
Verify whether 'rolename' is stored in the role database.
|
||||
|
||||
<Arguments>
|
||||
rolename:
|
||||
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
|
||||
(e.g., 'root', 'release', 'timestamp').
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rolename' does not have the correct object format.
|
||||
|
||||
tuf.InvalidNameError, if 'rolename' is incorrectly formatted.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
Boolean. True if 'rolename' is found in the role database, False otherwise.
|
||||
|
||||
"""
|
||||
|
||||
# Raise tuf.FormatError, tuf.InvalidNameError.
|
||||
try:
|
||||
_check_rolename(rolename)
|
||||
except (tuf.FormatError, tuf.InvalidNameError):
|
||||
raise
|
||||
except tuf.UnknownRoleError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def remove_role(rolename):
|
||||
"""
|
||||
<Purpose>
|
||||
Remove 'rolename', including its delegations.
|
||||
|
||||
<Arguments>
|
||||
rolename:
|
||||
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
|
||||
(e.g., 'root', 'release', 'timestamp').
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rolename' does not have the correct object format.
|
||||
|
||||
tuf.UnknownRoleError, if 'rolename' cannot be found in the role database.
|
||||
|
||||
tuf.InvalidNameError, if 'rolename' is incorrectly formatted.
|
||||
|
||||
<Side Effects>
|
||||
A role, or roles, may be removed from the role database.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
|
||||
_check_rolename(rolename)
|
||||
|
||||
remove_delegated_roles(rolename)
|
||||
if rolename in _roledb_dict:
|
||||
del _roledb_dict[rolename]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def remove_delegated_roles(rolename):
|
||||
"""
|
||||
<Purpose>
|
||||
Remove a role's delegations (leaving the rest of the role alone).
|
||||
All levels of delegation are removed, not just the directly delegated roles.
|
||||
If 'rolename' is 'a/b/c' and the role database contains
|
||||
['a/b/c/d/e', 'a/b/c/d', 'a/b/c', 'a/b', 'a'], return
|
||||
['a/b/c', 'a/b', 'a'].
|
||||
|
||||
<Arguments>
|
||||
rolename:
|
||||
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
|
||||
(e.g., 'root', 'release', 'timestamp').
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rolename' does not have the correct object format.
|
||||
|
||||
tuf.UnknownRoleError, if 'rolename' cannot be found in the role database.
|
||||
|
||||
tuf.InvalidNameError, if 'rolename' is incorrectly formatted.
|
||||
|
||||
<Side Effects>
|
||||
Role(s) from the role database may be deleted.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
|
||||
_check_rolename(rolename)
|
||||
|
||||
for name in get_rolenames():
|
||||
if name.startswith(rolename) and name != rolename:
|
||||
del _roledb_dict[name]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_rolenames():
|
||||
"""
|
||||
<Purpose>
|
||||
Return a list of the rolenames found in the role database.
|
||||
|
||||
<Arguments>
|
||||
None.
|
||||
|
||||
<Exceptions>
|
||||
None.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
A list of rolenames.
|
||||
|
||||
"""
|
||||
|
||||
return _roledb_dict.keys()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_role_keyids(rolename):
|
||||
"""
|
||||
<Purpose>
|
||||
Return a list of the keyids associated with 'rolename'.
|
||||
Keyids are used as identifiers for keys (e.g., rsa key).
|
||||
A list of keyids are associated with each rolename.
|
||||
Signing a metadata file, such as 'root.txt' (Root role),
|
||||
involves signing or verifying the file with a list of
|
||||
keys identified by keyid.
|
||||
|
||||
<Arguments>
|
||||
rolename:
|
||||
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
|
||||
(e.g., 'root', 'release', 'timestamp').
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rolename' does not have the correct object format.
|
||||
|
||||
tuf.UnknownRoleError, if 'rolename' cannot be found in the role database.
|
||||
|
||||
tuf.InvalidNameError, if 'rolename' is incorrectly formatted.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
A list of keyids.
|
||||
|
||||
"""
|
||||
|
||||
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
|
||||
_check_rolename(rolename)
|
||||
|
||||
roleinfo = _roledb_dict[rolename]
|
||||
|
||||
return roleinfo['keyids']
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_role_threshold(rolename):
|
||||
"""
|
||||
<Purpose>
|
||||
Return the threshold value of the role associated with 'rolename'.
|
||||
|
||||
<Arguments>
|
||||
rolename:
|
||||
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
|
||||
(e.g., 'root', 'release', 'timestamp').
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rolename' does not have the correct object format.
|
||||
|
||||
tuf.UnknownRoleError, if 'rolename' cannot be found in in the role database.
|
||||
|
||||
tuf.InvalidNameError, if 'rolename' is incorrectly formatted.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
A threshold integer value.
|
||||
|
||||
"""
|
||||
|
||||
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
|
||||
_check_rolename(rolename)
|
||||
|
||||
roleinfo = _roledb_dict[rolename]
|
||||
|
||||
return roleinfo['threshold']
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_role_paths(rolename):
|
||||
"""
|
||||
<Purpose>
|
||||
Return the paths of the role associated with 'rolename'.
|
||||
|
||||
<Arguments>
|
||||
rolename:
|
||||
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
|
||||
(e.g., 'root', 'release', 'timestamp').
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rolename' does not have the correct object format.
|
||||
|
||||
tuf.UnknownRoleError, if 'rolename' cannot be found in the role database.
|
||||
|
||||
tuf.InvalidNameError, if 'rolename' is incorrectly formatted.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
A list of paths.
|
||||
|
||||
"""
|
||||
|
||||
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
|
||||
_check_rolename(rolename)
|
||||
|
||||
roleinfo = _roledb_dict[rolename]
|
||||
|
||||
# Paths won't exist for non-target roles.
|
||||
try:
|
||||
return roleinfo['paths']
|
||||
except KeyError:
|
||||
return list()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_delegated_rolenames(rolename):
|
||||
"""
|
||||
<Purpose>
|
||||
Return the delegations of a role. If 'rolename' is 'a/b/c'
|
||||
and the role database contains ['a/b/c/d', 'a/b/c/d/e', 'a/b/c'],
|
||||
return ['a/b/c/d', 'a/b/c/d/e']
|
||||
|
||||
<Arguments>
|
||||
rolename:
|
||||
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
|
||||
(e.g., 'root', 'release', 'timestamp').
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rolename' does not have the correct object format.
|
||||
|
||||
tuf.UnknownRoleError, if 'rolename' cannot be found in the role database.
|
||||
|
||||
tuf.InvalidNameError, if 'rolename' is incorrectly formatted.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
A list of rolenames.
|
||||
|
||||
"""
|
||||
|
||||
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
|
||||
_check_rolename(rolename)
|
||||
|
||||
# The list of delegated roles to be returned.
|
||||
delegated_roles = []
|
||||
|
||||
rolename_with_slash = rolename + '/'
|
||||
for name in get_rolenames():
|
||||
if name.startswith(rolename_with_slash) and name != rolename:
|
||||
delegated_roles.append(name)
|
||||
|
||||
return delegated_roles
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def clear_roledb():
|
||||
"""
|
||||
<Purpose>
|
||||
Reset the roledb database.
|
||||
|
||||
<Arguments>
|
||||
None.
|
||||
|
||||
<Exceptions>
|
||||
None.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
_roledb_dict.clear()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _check_rolename(rolename):
|
||||
"""
|
||||
Raise tuf.FormatError if 'rolename' does not match
|
||||
'tuf.formats.ROLENAME_SCHEMA', tuf.UnknownRoleError if 'rolename' is not
|
||||
found in the role database, or tuf.InvalidNameError if 'rolename' is
|
||||
not formatted correctly.
|
||||
|
||||
"""
|
||||
|
||||
# Does 'rolename' have the correct object format?
|
||||
# This check will ensure 'rolename' has the appropriate number of objects
|
||||
# and object types, and that all dict keys are properly named.
|
||||
tuf.formats.ROLENAME_SCHEMA.check_match(rolename)
|
||||
|
||||
# Raises tuf.InvalidNameError.
|
||||
_validate_rolename(rolename)
|
||||
|
||||
if rolename not in _roledb_dict:
|
||||
raise tuf.UnknownRoleError('Role name does not exist: '+rolename)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _validate_rolename(rolename):
|
||||
"""
|
||||
Raise tuf.InvalidNameError if 'rolename' is not formatted correctly.
|
||||
It is assumed 'rolename' has been checked against 'ROLENAME_SCHEMA'
|
||||
prior to calling this function.
|
||||
|
||||
"""
|
||||
|
||||
if rolename == '':
|
||||
raise tuf.InvalidNameError('Rolename must not be an empty string')
|
||||
|
||||
if rolename != rolename.strip():
|
||||
raise tuf.InvalidNameError(
|
||||
'Invalid rolename. Cannot start or end with whitespace: '+rolename)
|
||||
|
||||
if rolename.startswith('/') or rolename.endswith('/'):
|
||||
raise tuf.InvalidNameError(
|
||||
'Invalid rolename. Cannot start or end with "/": '+rolename)
|
||||
425
src/tuf/rsa_key.py
Executable file
425
src/tuf/rsa_key.py
Executable file
|
|
@ -0,0 +1,425 @@
|
|||
"""
|
||||
<Program Name>
|
||||
rsa_key.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
March 9, 2012. Based on a previous version of this module by Geremy Condra.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
The goal of this module is to support public-key cryptography using
|
||||
the RSA algorithm. The RSA-related functions provided include
|
||||
generate(), create_signature(), and verify_signature(). The 'evpy' package
|
||||
used by 'rsa_key.py' generates the actual RSA keys and the functions listed
|
||||
above can be viewed as an easy-to-use public interface. Additional functions
|
||||
contained here include create_in_metadata_format() and
|
||||
create_from_metadata_format(). These last two functions produce or use RSA
|
||||
keys compatible with the key structures listed in TUF Metadata files.
|
||||
The generate() function returns a dictionary containing all the information
|
||||
needed of RSA keys, such as public and private keys, keyIDs, and an iden-
|
||||
fier. create_signature() and verify_signature() are supplemental functions
|
||||
used for generating RSA 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 key object (specifically, the key
|
||||
object containing only the public key). See 'rsa_key.py' and the
|
||||
'_get_keyid()' function 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']).
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Required for hexadecimal conversions.
|
||||
import binascii
|
||||
|
||||
# Needed to generate, sign, and verify RSA keys.
|
||||
import evpy.signature
|
||||
import evpy.envelope
|
||||
|
||||
# Digest objects needed to generate hashes.
|
||||
import tuf.hash
|
||||
|
||||
# Perform object format-checking.
|
||||
import tuf.formats
|
||||
|
||||
|
||||
_KEY_ID_HASH_ALGORITHM = 'sha256'
|
||||
|
||||
# Recommended RSA key sizes: http://www.rsa.com/rsalabs/node.asp?id=2004
|
||||
# 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
|
||||
|
||||
|
||||
def generate(bits=_DEFAULT_RSA_KEY_BITS):
|
||||
"""
|
||||
<Purpose>
|
||||
Generate public and private RSA keys, with modulus length 'bits'.
|
||||
In addition, a keyid used as an identifier for RSA keys is generated.
|
||||
The object returned conforms to tuf.formats.RSAKEY_SCHEMA and as the form:
|
||||
{'keytype': 'rsa',
|
||||
'keyid': keyid,
|
||||
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
||||
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
||||
|
||||
The public and private keys are in PEM format and stored as strings.
|
||||
|
||||
<Arguments>
|
||||
bits:
|
||||
The key size, or key length, of the RSA key.
|
||||
|
||||
<Exceptions>
|
||||
tuf.CryptoError, if an exception occurs after calling evpy.envelope.keygen().
|
||||
|
||||
tuf.FormatError, if 'bits' does not contain the correct format.
|
||||
|
||||
<Side Effects>
|
||||
The RSA keys are generated by calling evpy.envelope.keygen().
|
||||
|
||||
<Returns>
|
||||
A dictionary containing the RSA keys and other identifying information.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Does 'bits' have the correct format?
|
||||
# This check will ensure 'bits' conforms to 'tuf.formats.RSAKEYBITS_SCHEMA'.
|
||||
# 'bits' must be an integer object, with a minimum value of 2048.
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.RSAKEYBITS_SCHEMA.check_match(bits)
|
||||
|
||||
# Begin building the RSA key dictionary.
|
||||
rsakey_dict = {}
|
||||
keytype = 'rsa'
|
||||
|
||||
# Generate the public and private keys. 'public_key' and 'private_key'
|
||||
# will both be strings containing RSA keys in PEM format.
|
||||
# The evpy.envelope module performs the actual key generation. The
|
||||
# evpy.envelope.keygen() function returns a (public, private) tuple.
|
||||
|
||||
try:
|
||||
public_key, private_key = evpy.envelope.keygen(bits, pem=True)
|
||||
except (EnvelopeError, KeygenError, MemoryError), e:
|
||||
raise tuf.CryptoError(e)
|
||||
|
||||
# Generate the keyid for the RSA key. 'key_value' corresponds to the
|
||||
# 'keyval' entry of the RSAKEY_SCHEMA dictionary.
|
||||
key_value = {'public': public_key,
|
||||
'private': ''}
|
||||
|
||||
keyid = _get_keyid(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_key
|
||||
|
||||
rsakey_dict['keytype'] = keytype
|
||||
rsakey_dict['keyid'] = keyid
|
||||
rsakey_dict['keyval'] = key_value
|
||||
|
||||
return rsakey_dict
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def create_in_metadata_format(key_value, private=False):
|
||||
"""
|
||||
<Purpose>
|
||||
Return a dictionary conformant to tuf.formats.KEY_SCHEMA.
|
||||
If 'private' is True, include the private key. The dictionary
|
||||
returned has the form:
|
||||
{'keytype': 'rsa',
|
||||
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
||||
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
||||
or
|
||||
|
||||
{'keytype': 'rsa',
|
||||
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
||||
'private': ''}} if 'private' is False.
|
||||
|
||||
The private and public keys are in PEM format.
|
||||
|
||||
RSA keys are stored in Metadata files (e.g., root.txt) in the format
|
||||
returned by this function.
|
||||
|
||||
<Arguments>
|
||||
key_value:
|
||||
A dictionary containing a private and public RSA key.
|
||||
'key_value' is of the form:
|
||||
|
||||
{'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
||||
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}},
|
||||
conformat to tuf.formats.KEYVAL_SCHEMA.
|
||||
|
||||
private:
|
||||
Indicates if the private key should be included in the
|
||||
returned dictionary.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'key_value' does not conform to
|
||||
tuf.formats.KEYVAL_SCHEMA.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
An KEY_SCHEMA dictionary.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Does 'key_value' have the correct format?
|
||||
# This check will ensure 'key_value' has the appropriate number
|
||||
# of objects and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.KEYVAL_SCHEMA.check_match(key_value)
|
||||
|
||||
if private and key_value['private']:
|
||||
return {'keytype': 'rsa', 'keyval': key_value}
|
||||
else:
|
||||
public_key_value = {'public': key_value['public'], 'private': ''}
|
||||
return {'keytype': 'rsa', 'keyval': public_key_value}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def create_from_metadata_format(key_metadata):
|
||||
"""
|
||||
<Purpose>
|
||||
Construct an RSA key dictionary (i.e., tuf.formats.RSAKEY_SCHEMA)
|
||||
from 'key_metadata'. The dict returned by this function has the exact
|
||||
format as the dict returned by generate(). It is of the form:
|
||||
|
||||
{'keytype': 'rsa',
|
||||
'keyid': keyid,
|
||||
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
||||
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
||||
|
||||
The public and private keys are in PEM format and stored as strings.
|
||||
|
||||
RSA key dictionaries in RSAKEY_SCHEMA format should be used by
|
||||
modules storing a collection of keys, such as a keydb and keystore.
|
||||
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 needs converting. Generate() creates an entirely
|
||||
new key and returns it in the format appropriate for keydb and keystore.
|
||||
|
||||
<Arguments>
|
||||
key_metadata:
|
||||
The RSA key dictionary as stored in Metadata files, conforming to
|
||||
tuf.formats.KEY_SCHEMA.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'key_metadata' does not conform to
|
||||
tuf.formats.KEY_SCHEMA.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
A dictionary containing the RSA keys and other identifying information.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# 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 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.KEY_SCHEMA.check_match(key_metadata)
|
||||
|
||||
# Construct the dictionary to be returned.
|
||||
rsakey_dict = {}
|
||||
keytype = 'rsa'
|
||||
key_value = key_metadata['keyval']
|
||||
|
||||
keyid = _get_keyid(key_value)
|
||||
|
||||
# We now have all the required key values.
|
||||
# Build 'rsakey_dict'.
|
||||
rsakey_dict['keytype'] = keytype
|
||||
rsakey_dict['keyid'] = keyid
|
||||
rsakey_dict['keyval'] = key_value
|
||||
|
||||
return rsakey_dict
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _get_keyid(key_value):
|
||||
"""Return the keyid for 'key_value'."""
|
||||
|
||||
# 'keyid' will be generated from an object conformant to KEY_SCHEMA,
|
||||
# which is the format Metadata files (e.g., root.txt) store keys.
|
||||
# 'create_in_metadata_format()' returns the object needed by _get_keyid().
|
||||
rsakey_meta = create_in_metadata_format(key_value, private=False)
|
||||
|
||||
# Convert the RSA key to JSON Canonical format suitable for adding
|
||||
# to digest objects.
|
||||
rsakey_update_data = tuf.formats.encode_canonical(rsakey_meta)
|
||||
|
||||
# Create a digest object and call update(), using the JSON
|
||||
# canonical format of 'rskey_meta' as the update data.
|
||||
digest_object = tuf.hash.digest(_KEY_ID_HASH_ALGORITHM)
|
||||
digest_object.update(rsakey_update_data)
|
||||
|
||||
# 'keyid' becomes the hexadecimal representation of the hash.
|
||||
keyid = digest_object.hexdigest()
|
||||
|
||||
return keyid
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def create_signature(rsakey_dict, data):
|
||||
"""
|
||||
<Purpose>
|
||||
Return a signature dictionary of the form:
|
||||
{'keyid': keyid, 'method': 'evp', 'sig': sig}.
|
||||
|
||||
The signing process will use the private key
|
||||
rsakey_dict['keyval']['private'] and 'data' to generate the signature.
|
||||
|
||||
<Arguments>
|
||||
rsakey_dict:
|
||||
A dictionary containing the RSA keys and other identifying information.
|
||||
'rsakey_dict' has the form:
|
||||
|
||||
{'keytype': 'rsa',
|
||||
'keyid': keyid,
|
||||
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
||||
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
||||
|
||||
The public and private keys are in PEM format and stored as strings.
|
||||
|
||||
data:
|
||||
Data object used by create_signature() to generate the signature.
|
||||
|
||||
<Exceptions>
|
||||
TypeError, if a private key is not defined for 'rsakey_dict'.
|
||||
|
||||
tuf.FormatError, if an incorrect format is found for the
|
||||
'rsakey_dict' object.
|
||||
|
||||
<Side Effects>
|
||||
evpy.signature.sign() called to perform the actual signing.
|
||||
|
||||
<Returns>
|
||||
A signature dictionary conformat to tuf.format.SIGNATURE_SCHEMA.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Does 'rsakey_dict' have the correct format?
|
||||
# This check will ensure 'rsakey_dict' has the appropriate number
|
||||
# of objects and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
|
||||
|
||||
# Signing the 'data' object requires a private key.
|
||||
# The 'evp' (i.e., evpy) signing method is the only method
|
||||
# currently supported.
|
||||
signature = {}
|
||||
private_key = rsakey_dict['keyval']['private']
|
||||
keyid = rsakey_dict['keyid']
|
||||
method = 'evp'
|
||||
|
||||
if private_key:
|
||||
sig = evpy.signature.sign(data, key=private_key)
|
||||
else:
|
||||
raise TypeError('The required private key is not defined for rsakey_dict.')
|
||||
|
||||
# Build the signature dictionary to be returned.
|
||||
# The hexadecimal representation of 'sig' is stored in the signature.
|
||||
signature['keyid'] = keyid
|
||||
signature['method'] = method
|
||||
signature['sig'] = binascii.hexlify(sig)
|
||||
|
||||
return signature
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def verify_signature(rsakey_dict, signature, data):
|
||||
"""
|
||||
<Purpose>
|
||||
Determine whether the private key belonging to 'rsakey_dict' produced
|
||||
'signature'. verify_signature() will use the public key found in
|
||||
'rsakey_dict', the 'method' and 'sig' objects contained in 'signature',
|
||||
and 'data' to complete the verification. Type-checking performed on both
|
||||
'rsakey_dict' and 'signature'.
|
||||
|
||||
<Arguments>
|
||||
rsakey_dict:
|
||||
A dictionary containing the RSA keys and other identifying information.
|
||||
'rsakey_dict' has the form:
|
||||
|
||||
{'keytype': 'rsa',
|
||||
'keyid': keyid,
|
||||
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
||||
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
||||
|
||||
The public and private keys are in PEM format and stored as strings.
|
||||
|
||||
signature:
|
||||
The signature dictionary produced by tuf.rsa_key.create_signature().
|
||||
'signature' has the form:
|
||||
{'keyid': keyid, 'method': 'method', 'sig': sig}. Conformant to
|
||||
tuf.formats.SIGNATURE_SCHEMA.
|
||||
|
||||
data:
|
||||
Data object used by tuf.rsa_key.create_signature() to generate
|
||||
'signature'. 'data' is needed here to verify the signature.
|
||||
|
||||
<Exceptions>
|
||||
tuf.UnknownMethodError. Raised if the signing method used by
|
||||
'signature' is not one supported by tuf.rsa_key.create_signature().
|
||||
|
||||
tuf.FormatError. Raised if either 'rsakey_dict'
|
||||
or 'signature' do not match their respective tuf.formats schema.
|
||||
'rsakey_dict' must conform to tuf.formats.RSAKEY_SCHEMA.
|
||||
'signature' must conform to tuf.formats.SIGNATURE_SCHEMA.
|
||||
|
||||
<Side Effects>
|
||||
evpy.signature_verify() called to do the actual verification.
|
||||
|
||||
<Returns>
|
||||
Boolean.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Does 'rsakey_dict' have the correct format?
|
||||
# This check will ensure 'rsakey_dict' has the appropriate number
|
||||
# of objects and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
|
||||
|
||||
# Does 'signature' have the correct format?
|
||||
tuf.formats.SIGNATURE_SCHEMA.check_match(signature)
|
||||
|
||||
# Using the public key belonging to 'rsakey_dict'
|
||||
# (i.e., rsakey_dict['keyval']['public']), verify whether 'signature'
|
||||
# was produced by rsakey_dict's corresponding private key
|
||||
# rsakey_dict['keyval']['private']. Before returning the Boolean result,
|
||||
# ensure 'evp' was used as the signing method.
|
||||
|
||||
method = signature['method']
|
||||
sig = signature['sig']
|
||||
public_key = rsakey_dict['keyval']['public']
|
||||
|
||||
if method != 'evp':
|
||||
raise tuf.UnknownMethodError(method)
|
||||
return evpy.signature.verify(data, binascii.unhexlify(sig), key=public_key)
|
||||
797
src/tuf/schema.py
Executable file
797
src/tuf/schema.py
Executable file
|
|
@ -0,0 +1,797 @@
|
|||
"""
|
||||
<Program Name>
|
||||
schema.py
|
||||
|
||||
<Author>
|
||||
Geremy Condra
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
Refactored April 30, 2012 (previously named checkjson.py). -Vlad
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
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'}
|
||||
>>> 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,
|
||||
tuf.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 'tuf.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'.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
import tuf
|
||||
|
||||
|
||||
class Schema:
|
||||
"""
|
||||
<Purpose>
|
||||
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):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' exception.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
self.check_match(object)
|
||||
except tuf.FormatError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def check_match(self, object):
|
||||
"""
|
||||
<Purpose>
|
||||
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,
|
||||
'tuf.FormatError' should be raised.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Any(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
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(): raises 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> 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):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> schema = String('Hi')
|
||||
>>> schema.matches('Hi')
|
||||
True
|
||||
>>> schema.matches('Not hi')
|
||||
False
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, string):
|
||||
self._string = string
|
||||
|
||||
|
||||
def check_match(self, object):
|
||||
if self._string != object:
|
||||
raise tuf.FormatError('Expected '+repr(self._string)+' got '+repr(object))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class AnyString(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> 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, basestring):
|
||||
raise tuf.FormatError('Expected a string but got '+repr(object))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class OneOf(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> 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):
|
||||
self._alternatives = alternatives
|
||||
|
||||
|
||||
def check_match(self, object):
|
||||
# Simply return as soon as we find a match.
|
||||
# Raise 'tuf.FormatError' if no matches are found.
|
||||
for alternative in self._alternatives:
|
||||
if alternative.matches(object):
|
||||
return
|
||||
raise tuf.FormatError('Object did not match a recognized alternative.')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class AllOf(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> schema = AllOf([Any(), AnyString(), String('a')])
|
||||
>>> schema.matches('b')
|
||||
False
|
||||
>>> schema.matches('a')
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, required_schemas):
|
||||
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):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> 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 tuf.FormatError('Got '+repr(object)+' instead of a boolean.')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ListOf(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> 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.maxint, list_name='list'):
|
||||
"""
|
||||
<Purpose>
|
||||
Create a new ListOf schema.
|
||||
|
||||
<Arguments>
|
||||
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.
|
||||
|
||||
"""
|
||||
|
||||
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 tuf.FormatError('Expected '+repr(self._list_name)+'; 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 tuf.FormatError, e:
|
||||
raise tuf.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 tuf.FormatError('Length of '+repr(self._list_name)+' out of range')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Integer(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> schema = Integer()
|
||||
>>> schema.matches(99)
|
||||
True
|
||||
>>> schema.matches(False)
|
||||
False
|
||||
>>> schema.matches(0L)
|
||||
True
|
||||
>>> 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= -sys.maxint, hi=sys.maxint):
|
||||
"""
|
||||
<Purpose>
|
||||
Create a new Integer schema.
|
||||
|
||||
<Arguments>
|
||||
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, (int, long)):
|
||||
# We need to check for bool as a special case, since bool
|
||||
# is for historical reasons a subtype of int.
|
||||
raise tuf.FormatError('Got '+repr(object)+' instead of an integer')
|
||||
|
||||
elif not (self._lo <= object <= self._hi):
|
||||
int_range = '['+repr(self._lo)+','+repr(self._hi)+']'
|
||||
raise tuf.FormatError(repr(object)+' not in range '+int_range)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class DictOf(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> 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):
|
||||
"""
|
||||
<Purpose>
|
||||
Create a new DictOf schema.
|
||||
|
||||
<Arguments>
|
||||
key_schema: The dictionary's key.
|
||||
value_schema: The dictionary's value.
|
||||
|
||||
"""
|
||||
|
||||
self._key_schema = key_schema
|
||||
self._value_schema = value_schema
|
||||
|
||||
|
||||
def check_match(self, object):
|
||||
if not isinstance(object, dict):
|
||||
raise tuf.FormatError('Expected a dict but got '+repr(object))
|
||||
|
||||
for key, value in object.items():
|
||||
self._key_schema.check_match(key)
|
||||
self._value_schema.check_match(value)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Optional(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> 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):
|
||||
self._schema = schema
|
||||
|
||||
|
||||
def check_match(self, object):
|
||||
self._schema.check_match(object)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Object(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> 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):
|
||||
"""
|
||||
<Purpose>
|
||||
Create a new Object schema.
|
||||
|
||||
<Arguments>
|
||||
object_name: A string identifier for the object argument.
|
||||
|
||||
A variable number of keyword arguments is accepted.
|
||||
|
||||
"""
|
||||
|
||||
self._object_name = object_name
|
||||
self._required = required.items()
|
||||
|
||||
|
||||
def check_match(self, object):
|
||||
if not isinstance(object, dict):
|
||||
raise tuf.FormatError('Wanted a '+repr(self._object_name)+' did not get a dict')
|
||||
|
||||
# (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 tuf.FormatError(message)
|
||||
# Check that 'object's schema matches Object()'s schema for this
|
||||
# particular 'key'.
|
||||
else:
|
||||
try:
|
||||
schema.check_match(item)
|
||||
except tuf.FormatError, e:
|
||||
raise tuf.FormatError(str(e)+' in '+self._object_name+'.'+key)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Struct(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> 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'):
|
||||
"""
|
||||
<Purpose>
|
||||
Create a new Struct schema.
|
||||
|
||||
<Arguments>
|
||||
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.
|
||||
|
||||
"""
|
||||
|
||||
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 tuf.FormatError('Expected '+repr(self._struct_name)+'; got '+repr(object))
|
||||
elif len(object) < self._min:
|
||||
raise tuf.FormatError('Too few fields in '+self._struct_name)
|
||||
elif len(object) > len(self._sub_schemas) and not self._allow_more:
|
||||
raise tuf.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):
|
||||
"""
|
||||
<Purpose>
|
||||
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 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> 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):
|
||||
"""
|
||||
<Purpose>
|
||||
Create a new regular expression schema.
|
||||
|
||||
<Arguments>
|
||||
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 re_object is None:
|
||||
if pattern is None:
|
||||
error = 'Cannot compare against an unset regular expression'
|
||||
raise tuf.FormatError(error)
|
||||
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, basestring) or not self._re_object.match(object):
|
||||
raise tuf.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()
|
||||
313
src/tuf/sig.py
Executable file
313
src/tuf/sig.py
Executable file
|
|
@ -0,0 +1,313 @@
|
|||
"""
|
||||
<Program Name>
|
||||
sig.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
February 28, 2012. Based on a previous version by Geremy Condra.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Survivable key compromise is one feature of a secure update system
|
||||
incorporated into TUF's design. Responsibility separation through
|
||||
the use of multiple roles, multi-signature trust, and explicit and
|
||||
implicit key revocation are some of the mechanisms employed towards
|
||||
this goal of survivability. These mechanisms can all be seen in
|
||||
play by the functions available in this module.
|
||||
|
||||
The signed metadata files utilized by TUF to download target files
|
||||
securely are used and represented here as the 'signable' object.
|
||||
More precisely, the signature structures contained within these metadata
|
||||
files are packaged into 'signable' dictionaries. This module makes it
|
||||
possible to capture the states of these signatures by organizing the
|
||||
keys into different categories. As keys are added and removed, the
|
||||
system must securely and efficiently verify the status of these signatures.
|
||||
For instance, a bunch of keys have recently expired. How many valid keys
|
||||
are now available to the Release role? This question can be answered by
|
||||
get_signature_status(), which will return a full 'status report' of these
|
||||
'signable' dicts. This module also provides a convenient verify() function
|
||||
that will determine if a role still has a sufficient number of valid keys.
|
||||
If a caller needs to update the signatures of a 'signable' object, there
|
||||
is also a function for that.
|
||||
|
||||
"""
|
||||
|
||||
import tuf.formats
|
||||
import tuf.keydb
|
||||
import tuf.roledb
|
||||
|
||||
|
||||
def get_signature_status(signable, role=None):
|
||||
"""
|
||||
<Purpose>
|
||||
Return a dictionary representing the status of the signatures listed
|
||||
in 'signable'. Given an object conformant to SIGNABLE_SCHEMA, a set
|
||||
of public keys in 'tuf.keydb', a set of roles in 'tuf.roledb',
|
||||
and a role, the status of these signatures can be determined. This
|
||||
method will iterate through the signatures in 'signable' and enumerate
|
||||
all the keys that are valid, invalid, unrecognized, unauthorized, or
|
||||
generated using an unknown method.
|
||||
|
||||
<Arguments>
|
||||
signable:
|
||||
A dictionary containing a list of signatures and a 'signed' identifier.
|
||||
signable = {'signed': 'signer',
|
||||
'signatures': [{'keyid': keyid,
|
||||
'method': 'evp',
|
||||
'sig': sig}]}
|
||||
Conformant to tuf.formats.SIGNABLE_SCHEMA.
|
||||
|
||||
role:
|
||||
TUF role (e.g., 'root', 'targets', 'release').
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'signable' does not have the correct format.
|
||||
|
||||
tuf.UnknownRoleError, if 'role' is not recognized.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
A dictionary representing the status of the signatures in 'signable'.
|
||||
Conformant to tuf.formats.SIGNATURESTATUS_SCHEMA.
|
||||
|
||||
"""
|
||||
|
||||
# Does 'signable' have the correct format?
|
||||
# This check will ensure 'signable' has the appropriate number of objects
|
||||
# and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.SIGNABLE_SCHEMA.check_match(signable)
|
||||
|
||||
# The signature status dictionary returned.
|
||||
signature_status = {}
|
||||
|
||||
# The fields of the signature_status dict. A description of each field:
|
||||
# good_sigs = keys confirmed to have produced 'sig' and 'method' using
|
||||
# 'signed' and that are associated with 'role'; bad_sigs = negation of
|
||||
# good_sigs; unknown_sigs = keys not found in the 'keydb' database;
|
||||
# untrusted_sigs = keys that are not in the list of keyids associated
|
||||
# with 'role'; unknown_method_sigs = keys found to have used an
|
||||
# unsupported method of generating signatures.
|
||||
good_sigs = []
|
||||
bad_sigs = []
|
||||
unknown_sigs = []
|
||||
untrusted_sigs = []
|
||||
unknown_method_sigs = []
|
||||
|
||||
# Extract the relevant fields from 'signable' that will allow us to identify
|
||||
# the different classes of keys (i.e., good_sigs, bad_sigs, etc.).
|
||||
signed = signable['signed']
|
||||
signatures = signable['signatures']
|
||||
|
||||
# 'signed' needed in canonical JSON format.
|
||||
data = tuf.formats.encode_canonical(signed)
|
||||
|
||||
# Iterate through the signatures and enumerate the signature_status fields.
|
||||
# (i.e., good_sigs, bad_sigs, etc.).
|
||||
for signature in signatures:
|
||||
sig = signature['sig']
|
||||
keyid = signature['keyid']
|
||||
method = signature['method']
|
||||
|
||||
# Identify unrecognized key.
|
||||
try:
|
||||
key = tuf.keydb.get_key(keyid)
|
||||
except tuf.UnknownKeyError:
|
||||
unknown_sigs.append(keyid)
|
||||
continue
|
||||
|
||||
# Identify key using an unknown key signing method.
|
||||
try:
|
||||
valid_sig = tuf.rsa_key.verify_signature(key, signature, data)
|
||||
except tuf.UnknownMethodError:
|
||||
unknown_method_sigs.append(keyid)
|
||||
continue
|
||||
|
||||
# We are now dealing with a valid key.
|
||||
if valid_sig:
|
||||
if role is not None:
|
||||
try:
|
||||
# Identify unauthorized key.
|
||||
if keyid not in tuf.roledb.get_role_keyids(role):
|
||||
untrusted_sigs.append(keyid)
|
||||
continue
|
||||
# Unknown role, re-raise exception.
|
||||
except tuf.UnknownRoleError:
|
||||
raise
|
||||
# Identify good/authorized key.
|
||||
good_sigs.append(keyid)
|
||||
else:
|
||||
# Identify bad key.
|
||||
bad_sigs.append(keyid)
|
||||
|
||||
# Retrieve the threshold value for 'role'. Raise tuf.UnknownRoleError
|
||||
# if we were given an invalid role.
|
||||
if role is not None:
|
||||
try:
|
||||
threshold = tuf.roledb.get_role_threshold(role)
|
||||
except tuf.UnknownRoleError:
|
||||
raise
|
||||
else:
|
||||
threshold = 0
|
||||
|
||||
# Build the signature_status dict.
|
||||
signature_status['threshold'] = threshold
|
||||
signature_status['good_sigs'] = good_sigs
|
||||
signature_status['bad_sigs'] = bad_sigs
|
||||
signature_status['unknown_sigs'] = unknown_sigs
|
||||
signature_status['untrusted_sigs'] = untrusted_sigs
|
||||
signature_status['unknown_method_sigs'] = unknown_method_sigs
|
||||
|
||||
return signature_status
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def verify(signable, role):
|
||||
"""
|
||||
<Purpose>
|
||||
Verify whether the authorized signatures of 'signable' meet the minimum
|
||||
required by 'role'. Authorized signatures are those with valid keys
|
||||
associated with 'role'. 'signable' must conform to SIGNABLE_SCHEMA
|
||||
and 'role' must not equal 'None' or be less than zero.
|
||||
|
||||
<Arguments>
|
||||
signable:
|
||||
A dictionary containing a list of signatures and a 'signed' identifier.
|
||||
signable = {'signed':, 'signatures': [{'keyid':, 'method':, 'sig':}]}
|
||||
|
||||
role:
|
||||
TUF role (e.g., 'root', 'targets', 'release').
|
||||
|
||||
<Exceptions>
|
||||
tuf.UnknownRoleError, if 'role' is not recognized.
|
||||
|
||||
tuf.FormatError, if 'signable' is not formatted correctly.
|
||||
|
||||
tuf.Error, if an invalid threshold is encountered.
|
||||
|
||||
<Side Effects>
|
||||
tuf.sig.get_signature_status() called. Any exceptions thrown by
|
||||
get_signature_status() will be caught here and re-raised.
|
||||
|
||||
<Returns>
|
||||
Boolean. True if the number of good signatures >= the role's threshold,
|
||||
False otherwise.
|
||||
|
||||
"""
|
||||
|
||||
# Retrieve the signature status. tuf.sig.get_signature_status() raises
|
||||
# tuf.UnknownRoleError
|
||||
# tuf.FormatError
|
||||
status = get_signature_status(signable, role)
|
||||
|
||||
# Retrieve the role's threshold and the authorized keys of 'status'
|
||||
threshold = status['threshold']
|
||||
good_sigs = status['good_sigs']
|
||||
|
||||
# Does 'status' have the required threshold of signatures?
|
||||
# First check for invalid threshold values before returning result.
|
||||
if threshold is None or threshold <= 0:
|
||||
raise tuf.Error("Invalid threshold: "+str(threshold))
|
||||
|
||||
return len(good_sigs) >= threshold
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def may_need_new_keys(signature_status):
|
||||
"""
|
||||
<Purpose>
|
||||
Return true iff downloading a new set of keys might tip this
|
||||
signature status over to valid. This is determined by checking
|
||||
if either the number of unknown or untrused keys is > 0.
|
||||
|
||||
<Arguments>
|
||||
signature_status:
|
||||
The dictionary returned by tuf.sig.get_signature_status().
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'signature_status does not have the correct format.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
Boolean.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Does 'signature_status' have the correct format?
|
||||
# This check will ensure 'signature_status' has the appropriate number
|
||||
# of objects and object types, and that all dict keys are properly named.
|
||||
# Raise 'tuf.FormatError' if the check fails.
|
||||
tuf.formats.SIGNATURESTATUS_SCHEMA.check_match(signature_status)
|
||||
|
||||
unknown = signature_status['unknown_sigs']
|
||||
untrusted = signature_status['untrusted_sigs']
|
||||
|
||||
return len(unknown) or len(untrusted)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def generate_rsa_signature(signed, rsakey_dict):
|
||||
"""
|
||||
<Purpose>
|
||||
Generate a new signature dict presumably to be added to the 'signatures'
|
||||
field of 'signable'. The 'signable' dict is of the form:
|
||||
|
||||
{'signed': 'signer',
|
||||
'signatures': [{'keyid': keyid,
|
||||
'method': 'evp',
|
||||
'sig': sig}]}
|
||||
|
||||
The 'signed' argument is needed here for the signing process.
|
||||
The 'rsakey_dict' argument is used to generate 'keyid', 'method', and 'sig'.
|
||||
|
||||
The caller should ensure the returned signature is not already in
|
||||
'signable'.
|
||||
|
||||
<Arguments>
|
||||
signed:
|
||||
The data used by 'tuf.rsa_key.create_signature()' to generate signatures.
|
||||
It is stored in the 'signed' field of 'signable'.
|
||||
|
||||
rsakey_dict:
|
||||
The RSA key, a tuf.formats.RSAKEY_SCHEMA dictionary.
|
||||
Used here to produce 'keyid', 'method', and 'sig'.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if 'rsakey_dict' does not have the correct format.
|
||||
|
||||
TypeError, if a private key is not defined for 'rsakey_dict'.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
Signature dictionary conformant to tuf.formats.SIGNATURE_SCHEMA.
|
||||
Has the form:
|
||||
{'keyid': keyid, 'method': 'evp', 'sig': sig}
|
||||
|
||||
"""
|
||||
|
||||
# We need 'signed' in canonical JSON format to generate
|
||||
# the 'method' and 'sig' fields of the signature.
|
||||
signed = tuf.formats.encode_canonical(signed)
|
||||
|
||||
# Generate the RSA signature.
|
||||
# Raises tuf.FormatError and TypeError.
|
||||
signature = tuf.rsa_key.create_signature(rsakey_dict, signed)
|
||||
|
||||
return signature
|
||||
0
src/tuf/tests/__init__.py
Executable file
0
src/tuf/tests/__init__.py
Executable file
354
src/tuf/tests/repository_setup.py
Executable file
354
src/tuf/tests/repository_setup.py
Executable file
|
|
@ -0,0 +1,354 @@
|
|||
"""
|
||||
<Program Name>
|
||||
repository_setup.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
|
||||
<Started>
|
||||
October 15, 2012
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
To provide a quick repository structure to be used in conjunction with
|
||||
test modules like test_updater.py for instance.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import tuf.rsa_key as rsa_key
|
||||
import tuf.repo.keystore as keystore
|
||||
import tuf.repo.signerlib as signerlib
|
||||
import tuf.repo.signercli as signercli
|
||||
import tuf.tests.unittest_toolbox as unittest_toolbox
|
||||
|
||||
|
||||
# Populating 'rsa_keystore' and 'rsa_passwords' dictionaries.
|
||||
# We will need them in creating keystore directory.
|
||||
unittest_toolbox.Modified_TestCase.bind_keys_to_roles()
|
||||
|
||||
|
||||
# Role:keyids dictionary.
|
||||
role_keyids = {}
|
||||
for role in unittest_toolbox.Modified_TestCase.semi_roledict.keys():
|
||||
role_keyids[role] = unittest_toolbox.Modified_TestCase.semi_roledict[role]['keyids']
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _create_keystore(keystore_directory):
|
||||
"""
|
||||
<Purpose>
|
||||
Populate 'keystore_directory' with keys (.key files).
|
||||
"""
|
||||
|
||||
_rsa_keystore = unittest_toolbox.Modified_TestCase.rsa_keystore
|
||||
_rsa_passwords = unittest_toolbox.Modified_TestCase.rsa_passwords
|
||||
if not _rsa_keystore or not _rsa_passwords:
|
||||
msg = 'Populate \'rsa_keystore\' and \'rsa_passwords\''+\
|
||||
' before invoking this method.'
|
||||
sys.exit(msg)
|
||||
|
||||
keystore._keystore = _rsa_keystore
|
||||
keystore._key_passwords = _rsa_passwords
|
||||
keystore.save_keystore_to_keyfiles(keystore_directory)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def build_server_repository(server_repository_dir, targets_dir):
|
||||
"""
|
||||
<Purpose>
|
||||
'build_server_repository' builds a complete repository based on target
|
||||
files provided in the 'targets_dir'. Delegated roles are included.
|
||||
"""
|
||||
|
||||
server_metadata_dir = os.path.join(server_repository_dir, 'metadata')
|
||||
keystore_dir = os.path.join(server_repository_dir, 'keystore')
|
||||
|
||||
# Remove 'server_metadata_dir' and 'keystore_dir' if they already exist.
|
||||
if os.path.exists(server_metadata_dir):
|
||||
shutil.rmtree(server_metadata_dir)
|
||||
if os.path.exists(keystore_dir):
|
||||
shutil.rmtree(keystore_dir)
|
||||
|
||||
# Make metadata directory inside server repository dir.
|
||||
os.mkdir(server_metadata_dir)
|
||||
|
||||
# Make a keystore directory inside server's repository and populate it.
|
||||
os.mkdir(keystore_dir)
|
||||
_create_keystore(keystore_dir)
|
||||
|
||||
# Build config file.
|
||||
build_config = signerlib.build_config_file
|
||||
top_level_role_info = unittest_toolbox.Modified_TestCase.top_level_role_info
|
||||
config_filepath = build_config(server_repository_dir, 365, top_level_role_info)
|
||||
|
||||
|
||||
# BUILD ROLE FILES.
|
||||
# Build root file.
|
||||
signerlib.build_root_file(config_filepath, role_keyids['root'],
|
||||
server_metadata_dir)
|
||||
|
||||
# Build targets file.
|
||||
signerlib.build_targets_file(targets_dir, role_keyids['targets'],
|
||||
server_metadata_dir)
|
||||
|
||||
# MAKE DELEGATIONS.
|
||||
# We will need to patch a few signercli prompts.
|
||||
# Specifically, signercli.make_delegations() asks user input for:
|
||||
# metadata directory, delegated targets directory, parent role,
|
||||
# passwords for parent role's keyids, delegated role's name, and
|
||||
# the keyid to be assigned to the delegated role. Take a look at
|
||||
# signercli's make_delegation() to gain bit more insight in what is
|
||||
# happening.
|
||||
|
||||
# 'load_key' is a reference to the 'load_keystore_from_keyfiles function'.
|
||||
load_keys = keystore.load_keystore_from_keyfiles
|
||||
|
||||
# Setup first level delegated role.
|
||||
delegated_level1 = os.path.join(targets_dir, 'delegated_level1')
|
||||
delegated_targets_dir = delegated_level1
|
||||
parent_role = 'targets'
|
||||
delegated_role_name = 'delegated_role1'
|
||||
signing_keyids = role_keyids['targets/delegated_role1']
|
||||
|
||||
|
||||
# Patching the 'signercli' prompts.
|
||||
|
||||
# Mock method for signercli._get_metadata_directory().
|
||||
def _mock_get_metadata_directory():
|
||||
return server_metadata_dir
|
||||
|
||||
# Mock method for signercli._prompt().
|
||||
def _mock_prompt(msg, junk):
|
||||
if msg.startswith('\nNOTE: The directory entered'):
|
||||
return delegated_targets_dir
|
||||
elif msg.startswith('\nChoose and enter the parent'):
|
||||
return parent_role
|
||||
elif msg.endswith('\nEnter the delegated role\'s name: '):
|
||||
return delegated_role_name
|
||||
else:
|
||||
error_msg = ('Prompt: '+'\''+msg+'\''+
|
||||
' did not match any predefined mock prompts.')
|
||||
sys.exit(error_msg)
|
||||
|
||||
# Mock method for signercli._get_password().
|
||||
def _mock_get_password(msg):
|
||||
for keyid in unittest_toolbox.Modified_TestCase.rsa_keyids:
|
||||
if msg.endswith('('+keyid+'): '):
|
||||
return unittest_toolbox.Modified_TestCase.rsa_passwords[keyid]
|
||||
|
||||
|
||||
# Method to patch signercli._get_keyids()
|
||||
def _mock_get_keyids(junk):
|
||||
if signing_keyids:
|
||||
for keyid in signing_keyids:
|
||||
password = unittest_toolbox.Modified_TestCase.rsa_passwords[keyid]
|
||||
# Load the keyfile.
|
||||
load_keys(keystore_dir, [keyid], [password])
|
||||
return signing_keyids
|
||||
|
||||
|
||||
# Patch signercli._get_metadata_directory().
|
||||
signercli._get_metadata_directory = _mock_get_metadata_directory
|
||||
|
||||
# Patch signercli._prompt().
|
||||
signercli._prompt = _mock_prompt
|
||||
|
||||
# Patch signercli._get_password().
|
||||
signercli._get_password = _mock_get_password
|
||||
|
||||
# Patch signercli._get_keyids().
|
||||
signercli._get_keyids = _mock_get_keyids
|
||||
|
||||
# Clear kestore's dictionaries, by detaching them from unittest_toolbox's
|
||||
# dictionaries.
|
||||
keystore._keystore = {}
|
||||
keystore._key_passwords = {}
|
||||
|
||||
# Make first level delegation.
|
||||
signercli.make_delegation(keystore_dir)
|
||||
|
||||
# Setup second level delegated role.
|
||||
delegated_level2 = os.path.join(delegated_level1, 'delegated_level2')
|
||||
delegated_targets_dir = delegated_level2
|
||||
parent_role = 'targets/delegated_role1'
|
||||
delegated_role_name = 'delegated_role2'
|
||||
signing_keyids = role_keyids['targets/delegated_role1/delegated_role2']
|
||||
|
||||
# Clear kestore's dictionaries.
|
||||
keystore.clear_keystore()
|
||||
|
||||
# Make second level delegation.
|
||||
signercli.make_delegation(keystore_dir)
|
||||
|
||||
|
||||
keystore._keystore = unittest_toolbox.Modified_TestCase.rsa_keystore
|
||||
keystore._key_passwords = unittest_toolbox.Modified_TestCase.rsa_passwords
|
||||
|
||||
# Build release file.
|
||||
signerlib.build_release_file(role_keyids['release'], server_metadata_dir)
|
||||
|
||||
# Build timestamp file.
|
||||
signerlib.build_timestamp_file(role_keyids['timestamp'], server_metadata_dir)
|
||||
|
||||
keystore._keystore = {}
|
||||
keystore._key_passwords = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Create a complete server and client repositories.
|
||||
def create_repositories():
|
||||
"""
|
||||
Main directories have the following structure:
|
||||
|
||||
main_repository
|
||||
|
|
||||
------------------
|
||||
| |
|
||||
client_repository_dir server_repository_dir
|
||||
|
||||
|
||||
|
||||
client_repository
|
||||
|
|
||||
metadata
|
||||
|
|
||||
----------------
|
||||
| |
|
||||
previous current
|
||||
|
||||
|
||||
server_repository
|
||||
|
|
||||
----------------------------
|
||||
| | |
|
||||
metadata targets keystore
|
||||
|
|
||||
delegation_level1
|
||||
|
|
||||
delegation_level2
|
||||
|
||||
|
||||
|
||||
NOTE: Do not forget to remove the directory using remove_all_repositories
|
||||
after the tests.
|
||||
|
||||
<Return>
|
||||
A dictionary of all repositories, with the following keys:
|
||||
(main_repository, client_repository, server_repository)
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Make a temporary general repository directory.
|
||||
repository_dir = tempfile.mkdtemp()
|
||||
|
||||
|
||||
# Make server repository and client repository directories.
|
||||
server_repository_dir = os.path.join(repository_dir, 'server_repository')
|
||||
client_repository_dir = os.path.join(repository_dir, 'client_repository')
|
||||
os.mkdir(server_repository_dir)
|
||||
os.mkdir(client_repository_dir)
|
||||
|
||||
|
||||
# Make metadata directory inside client repository dir.
|
||||
client_metadata_dir = os.path.join(client_repository_dir, 'metadata')
|
||||
os.mkdir(client_metadata_dir)
|
||||
|
||||
# Create a 'targets' directory.
|
||||
targets = os.path.join(server_repository_dir, 'targets')
|
||||
delegated_level1 = os.path.join(targets, 'delegated_level1')
|
||||
delegated_level2 = os.path.join(delegated_level1, 'delegated_level2')
|
||||
os.makedirs(delegated_level2)
|
||||
|
||||
# Populate the project directory with files.
|
||||
file_path_1 = tempfile.mkstemp(suffix='.txt', dir=targets)
|
||||
file_path_2 = tempfile.mkstemp(suffix='.txt', dir=targets)
|
||||
file_path_3 = tempfile.mkstemp(suffix='.txt', dir=delegated_level1)
|
||||
file_path_4 = tempfile.mkstemp(suffix='.txt', dir=delegated_level2)
|
||||
|
||||
def data():
|
||||
return 'Stored data: '+unittest_toolbox.Modified_TestCase.random_string()
|
||||
|
||||
file_1 = open(file_path_1[1], 'wb')
|
||||
file_1.write(data())
|
||||
file_1.close()
|
||||
file_2 = open(file_path_2[1], 'wb')
|
||||
file_2.write(data())
|
||||
file_2.close()
|
||||
file_3 = open(file_path_3[1], 'wb')
|
||||
file_3.write(data())
|
||||
file_3.close()
|
||||
file_4 = open(file_path_4[1], 'wb')
|
||||
file_4.write(data())
|
||||
file_4.close()
|
||||
|
||||
|
||||
# Build server's repository.
|
||||
build_server_repository(server_repository_dir, targets)
|
||||
|
||||
# Build client's repository.
|
||||
client_repository_include_all_role_files(repository_dir)
|
||||
|
||||
repositories = {'main_repository': repository_dir,
|
||||
'client_repository': client_repository_dir,
|
||||
'server_repository': server_repository_dir,
|
||||
'targets_directory': targets}
|
||||
|
||||
return repositories
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# client_repository_include_all_role_files() copies all of the metadata file.
|
||||
def client_repository_include_all_role_files(repository_dir):
|
||||
if repository_dir is None:
|
||||
msg = ('Please provide main repository directory where client '+
|
||||
'repository is located.')
|
||||
sys.exit(msg)
|
||||
|
||||
# Destination directories.
|
||||
current_dir = os.path.join(repository_dir, 'client_repository', 'metadata',
|
||||
'current')
|
||||
previous_dir = os.path.join(repository_dir, 'client_repository', 'metadata',
|
||||
'previous')
|
||||
# Source directory.
|
||||
metadata_files = os.path.join(repository_dir, 'server_repository',
|
||||
'metadata')
|
||||
|
||||
# Copy the whole source directory to destination directories.
|
||||
shutil.copytree(metadata_files, current_dir)
|
||||
shutil.copytree(metadata_files, previous_dir)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Supply the main repository directory.
|
||||
def remove_all_repositories(repository_directory):
|
||||
# Check if 'repository_directory' is an existing directory.
|
||||
if os.path.isdir(repository_directory):
|
||||
shutil.rmtree(repository_directory)
|
||||
else:
|
||||
print '\nInvalid repository directory.'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
repos = create_repositories()
|
||||
remove_all_repositories(repos['main_repository'])
|
||||
176
src/tuf/tests/test_download.py
Executable file
176
src/tuf/tests/test_download.py
Executable file
|
|
@ -0,0 +1,176 @@
|
|||
"""
|
||||
<Program>
|
||||
test_download.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
|
||||
<Started>
|
||||
March 26, 2012.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Test download.py module.
|
||||
|
||||
NOTE: launch test_download_server.py before running.
|
||||
"""
|
||||
|
||||
import tuf
|
||||
import tuf.download
|
||||
import tuf.tests.unittest_toolbox as unittest_toolbox
|
||||
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
import hashlib
|
||||
import urllib2
|
||||
|
||||
|
||||
TARGET = 'test_target_file.txt' # Remove this, use temp file instead.
|
||||
"""
|
||||
server_script = '.....'
|
||||
temp_dir = unittest_toolbox.make_temp_directory()
|
||||
current_dir = os.getcwd()
|
||||
target = unittest_toolbox.make_temp_data_file(directory=current_dir)
|
||||
server = unittest_toolbox.make_temp_data_file(directory=current_dir,
|
||||
data=server_script)
|
||||
"""
|
||||
PORT = 8080
|
||||
EXC = (tuf.FormatError, tuf.DownloadError, tuf.BadHashError,
|
||||
urllib2.URLError, urllib2.HTTPError)
|
||||
|
||||
|
||||
# Unit tests
|
||||
class TestDownload_url_to_tempfileobj(unittest.TestCase):
|
||||
def setUp(self):
|
||||
'''
|
||||
<Initialized Variables>
|
||||
url:
|
||||
A url string that composed of localhost, port # and target name.
|
||||
target_file:
|
||||
A string of an entire target file.
|
||||
target_length:
|
||||
Integer value representing the length of the target file.
|
||||
target_hash:
|
||||
A dictionary consisting of the hash algorithm as a key and a hexdigest
|
||||
as its value.
|
||||
digest:
|
||||
A hexadecimal hash value.
|
||||
|
||||
'''
|
||||
|
||||
if not os.path.isfile(TARGET):
|
||||
self._fileobject = open(TARGET, 'w')
|
||||
data = 'file containing data'
|
||||
self._fileobject.write(data)
|
||||
self._fileobject.close()
|
||||
|
||||
else:
|
||||
msg = '\'TARGET\': '+TARGET+'. File already exists! Try renaming TARGET.'
|
||||
raise tuf.Error, msg
|
||||
|
||||
self.url = "http://localhost:"+str(PORT)+"/"+TARGET
|
||||
|
||||
self._fileobject = open(TARGET, 'r')
|
||||
self.target_file = self._fileobject.read()
|
||||
self.target_length = len(self.target_file)
|
||||
|
||||
# Computing hash of the target file.
|
||||
# md5 algorithm is used here, any algorithm can be used. If changed to some
|
||||
# other algorithm don't forget to edit the key in 'self.target_hash'
|
||||
# dictionary.
|
||||
self.d = hashlib.md5()
|
||||
self.d.update(self.target_file)
|
||||
self.digest = self.d.hexdigest()
|
||||
self.target_hash = {"md5":self.digest}
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
self._fileobject.close()
|
||||
os.remove(TARGET)
|
||||
|
||||
def testNormal(self):
|
||||
# temp_file is a 'file-like' object
|
||||
# I took measurement of performance when using 'auto_flush = False' vs.
|
||||
# 'auto_flush = True' in the download_url_to_file(). No change was observed.
|
||||
star_cpu = time.clock()
|
||||
star_real = time.time()
|
||||
_temp_file = tuf.download.download_url_to_tempfileobj(self.url,
|
||||
self.target_hash,
|
||||
self.target_length)
|
||||
end_cpu = time.clock()
|
||||
end_real = time.time()
|
||||
print "Performance cpu time: "+str(end_cpu - star_cpu)
|
||||
print "Performance real time: "+str(end_real - star_real)
|
||||
_temp_file.seek(0)
|
||||
data = _temp_file.read()
|
||||
_temp_file.close_temp_file()
|
||||
self.assertEqual(data, self.target_file)
|
||||
|
||||
|
||||
def testWrongLength_lessthenactual(self):
|
||||
wronglength = self.target_length - 1
|
||||
self.assertRaises(tuf.DownloadError,
|
||||
tuf.download.download_url_to_tempfileobj,
|
||||
self.url, self.target_hash, wronglength)
|
||||
|
||||
|
||||
def testWrongLength(self):
|
||||
wronglength = self.target_length + 1
|
||||
self.assertRaises(tuf.DownloadError,
|
||||
tuf.download.download_url_to_tempfileobj,
|
||||
self.url, self.target_hash, wronglength)
|
||||
|
||||
|
||||
def testWrongHash(self):
|
||||
wronghash = {"md5":"fffffffffffffff"}
|
||||
self.assertRaises(tuf.DownloadError,
|
||||
tuf.download.download_url_to_tempfileobj,
|
||||
self.url, wronghash, self.target_length)
|
||||
|
||||
|
||||
def testEmptyArgs_url(self):
|
||||
url = None
|
||||
self.assertRaises(EXC, tuf.download.download_url_to_tempfileobj,
|
||||
url, self.target_hash, self.target_length)
|
||||
|
||||
|
||||
def testEmptyArgs_hashes(self):
|
||||
_hash = None
|
||||
self.assertRaises(EXC, tuf.download.download_url_to_tempfileobj,
|
||||
self.url, _hash, self.target_length)
|
||||
|
||||
|
||||
def testEmptyArgs_length(self):
|
||||
length = None
|
||||
self.assertRaises(EXC, tuf.download.download_url_to_tempfileobj,
|
||||
self.url, self.target_hash,length)
|
||||
|
||||
|
||||
def testWrongFile(self):
|
||||
url = self.url = "http://localhost:"+str(PORT)+"/"+"non_existing.sh"
|
||||
self.assertRaises(EXC, tuf.download.download_url_to_tempfileobj,
|
||||
url, self.target_hash, self.target_length)
|
||||
|
||||
|
||||
def testWrongPort(self):
|
||||
wrongPort = 8081
|
||||
url = self.url = "http://localhost:"+str(wrongPort)+"/"+TARGET
|
||||
self.assertRaises(EXC, tuf.download.download_url_to_tempfileobj,
|
||||
url, self.target_hash, self.target_length)
|
||||
|
||||
|
||||
# This function takes time to complete.
|
||||
def testWrongURL(self):
|
||||
url = self.url = "http://192.168.12.12:"+str(PORT)+"/"+"non_existing.sh"
|
||||
self.assertRaises(EXC, tuf.download.download_url_to_tempfileobj,
|
||||
url, self.target_hash, self.target_length)
|
||||
|
||||
|
||||
|
||||
# Run the unittests.
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
37
src/tuf/tests/test_download_server.py
Executable file
37
src/tuf/tests/test_download_server.py
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
"""
|
||||
<Program>
|
||||
test_download_server.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
|
||||
<Started>
|
||||
February 15, 2012
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
This is a basic server that was designed to be used in conjunction with
|
||||
test_download.py to test download.py module.
|
||||
|
||||
"""
|
||||
|
||||
# Server serves files in the current directory.
|
||||
|
||||
import SimpleHTTPServer
|
||||
import SocketServer
|
||||
|
||||
PORT = 8080
|
||||
|
||||
Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
|
||||
httpd = SocketServer.TCPServer(("", PORT), Handler)
|
||||
|
||||
print "serving at port", PORT
|
||||
httpd.serve_forever(poll_interval=0.5)
|
||||
|
||||
|
||||
# Instead you can run the following command:
|
||||
# $python -m SimpleHTTPServer 8080
|
||||
# http://docs.python.org/library/simplehttpserver.html#module-SimpleHTTPServer
|
||||
|
||||
219
src/tuf/tests/test_hash.py
Executable file
219
src/tuf/tests/test_hash.py
Executable file
|
|
@ -0,0 +1,219 @@
|
|||
"""
|
||||
<Program Name>
|
||||
test_hash.py
|
||||
|
||||
<Authors>
|
||||
Geremy Condra
|
||||
Vladimir Diaz
|
||||
vladimir.v.diaz AT gmail
|
||||
|
||||
<Started>
|
||||
March 1, 2012. Based on a previous version of this module.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Unit tests for hash.py.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import StringIO
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import tuf.hash
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def _run_with_all_hash_libraries(self, test_func):
|
||||
test_func('hashlib')
|
||||
test_func('pycrypto')
|
||||
|
||||
|
||||
def test_md5_update(self):
|
||||
self._run_with_all_hash_libraries(self._do_md5_update)
|
||||
|
||||
|
||||
def _do_md5_update(self, library):
|
||||
digest_object = tuf.hash.digest('md5', library)
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'd41d8cd98f00b204e9800998ecf8427e')
|
||||
digest_object.update('a')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'0cc175b9c0f1b6a831c399e269772661')
|
||||
digest_object.update(u'bbb')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'f034e93091235adbb5d2781908e2b313')
|
||||
digest_object.update('')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'f034e93091235adbb5d2781908e2b313')
|
||||
|
||||
|
||||
def test_sha1_update(self):
|
||||
self._run_with_all_hash_libraries(self._do_sha1_update)
|
||||
|
||||
|
||||
def _do_sha1_update(self, library):
|
||||
digest_object = tuf.hash.digest('sha1', library)
|
||||
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'da39a3ee5e6b4b0d3255bfef95601890afd80709')
|
||||
digest_object.update('a')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'86f7e437faa5a7fce15d1ddcb9eaeaea377667b8')
|
||||
digest_object.update(u'bbb')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'd7bfa42fc62b697bf6cf1cda9af1fb7f40a27817')
|
||||
digest_object.update('')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'd7bfa42fc62b697bf6cf1cda9af1fb7f40a27817')
|
||||
|
||||
|
||||
def test_sha224_update(self):
|
||||
self._run_with_all_hash_libraries(self._do_sha224_update)
|
||||
|
||||
|
||||
def _do_sha224_update(self, library):
|
||||
digest_object = tuf.hash.digest('sha224', library)
|
||||
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f')
|
||||
digest_object.update('a')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5')
|
||||
digest_object.update(u'bbb')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'ab1342f31c2a6f242d9a3cefb503fb49465c95eb255c16ad791d688c')
|
||||
digest_object.update('')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'ab1342f31c2a6f242d9a3cefb503fb49465c95eb255c16ad791d688c')
|
||||
|
||||
|
||||
def test_sha256_update(self):
|
||||
self._run_with_all_hash_libraries(self._do_sha256_update)
|
||||
|
||||
|
||||
def _do_sha256_update(self, library):
|
||||
digest_object = tuf.hash.digest('sha256', library)
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')
|
||||
digest_object.update('a')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb')
|
||||
digest_object.update(u'bbb')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'01d162a5c95d4698c0a3e766ae80d85994b549b877ed275803725f43dadc83bd')
|
||||
digest_object.update('')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'01d162a5c95d4698c0a3e766ae80d85994b549b877ed275803725f43dadc83bd')
|
||||
|
||||
|
||||
def test_sha384_update(self):
|
||||
self._run_with_all_hash_libraries(self._do_sha384_update)
|
||||
|
||||
|
||||
def _do_sha384_update(self, library):
|
||||
digest_object = tuf.hash.digest('sha384', library)
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe'
|
||||
'76f65fbd51ad2f14898b95b')
|
||||
digest_object.update('a')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d'
|
||||
'57bc35efae0b5afd3145f31')
|
||||
digest_object.update(u'bbb')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'f2c1438e9cc1d24bebbf3b88e60adc169db0c5c459d02054ec131438bf20ebee5ca88c17c'
|
||||
'b5f1a824fcccf8d2b20b0a9')
|
||||
digest_object.update('')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'f2c1438e9cc1d24bebbf3b88e60adc169db0c5c459d02054ec131438bf20ebee5ca88c17c'
|
||||
'b5f1a824fcccf8d2b20b0a9')
|
||||
|
||||
|
||||
def test_sha512_update(self):
|
||||
self._run_with_all_hash_libraries(self._do_sha512_update)
|
||||
|
||||
|
||||
def _do_sha512_update(self, library):
|
||||
digest_object = tuf.hash.digest('sha512', library)
|
||||
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5'
|
||||
'd85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e')
|
||||
digest_object.update('a')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652'
|
||||
'bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75')
|
||||
digest_object.update(u'bbb')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'09ade82ae3c5d54f8375f348563a372106488adef16a74b63b5591849f740bff55ceab22e'
|
||||
'117b4b09349b860f8a644adb32a9ea542abdecb80bf625160604251')
|
||||
digest_object.update('')
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'09ade82ae3c5d54f8375f348563a372106488adef16a74b63b5591849f740bff55ceab22e'
|
||||
'117b4b09349b860f8a644adb32a9ea542abdecb80bf625160604251')
|
||||
|
||||
|
||||
def test_unsupported_algorithm(self):
|
||||
self._run_with_all_hash_libraries(self._do_unsupported_algorithm)
|
||||
|
||||
|
||||
def _do_unsupported_algorithm(self, library):
|
||||
self.assertRaises(tuf.UnsupportedAlgorithmError, tuf.hash.digest, 'bogus')
|
||||
|
||||
|
||||
def test_digest_size(self):
|
||||
self._run_with_all_hash_libraries(self._do_digest_size)
|
||||
|
||||
|
||||
def _do_digest_size(self, library):
|
||||
self.assertEqual(16, tuf.hash.digest('md5', library).digest_size)
|
||||
self.assertEqual(20, tuf.hash.digest('sha1', library).digest_size)
|
||||
self.assertEqual(28, tuf.hash.digest('sha224', library).digest_size)
|
||||
self.assertEqual(32, tuf.hash.digest('sha256', library).digest_size)
|
||||
self.assertEqual(48, tuf.hash.digest('sha384', library).digest_size)
|
||||
self.assertEqual(64, tuf.hash.digest('sha512', library).digest_size)
|
||||
|
||||
|
||||
def test_update_filename(self):
|
||||
self._run_with_all_hash_libraries(self._do_update_filename)
|
||||
|
||||
|
||||
def _do_update_filename(self, library):
|
||||
data = 'abcdefgh' * 4096
|
||||
fd, filename = tempfile.mkstemp()
|
||||
try:
|
||||
os.write(fd, data)
|
||||
os.close(fd)
|
||||
for algorithm in ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']:
|
||||
digest_object_truth = tuf.hash.digest(algorithm, library)
|
||||
digest_object_truth.update(data)
|
||||
digest_object = tuf.hash.digest_filename(filename, algorithm, library)
|
||||
self.assertEqual(digest_object_truth.digest(), digest_object.digest())
|
||||
finally:
|
||||
os.remove(filename)
|
||||
|
||||
|
||||
def test_update_file_obj(self):
|
||||
self._run_with_all_hash_libraries(self._do_update_file_obj)
|
||||
|
||||
|
||||
def _do_update_file_obj(self, library):
|
||||
data = 'abcdefgh' * 4096
|
||||
file_obj = StringIO.StringIO()
|
||||
file_obj.write(data)
|
||||
for algorithm in ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']:
|
||||
digest_object_truth = tuf.hash.digest(algorithm, library)
|
||||
digest_object_truth.update(data)
|
||||
digest_object = tuf.hash.digest_fileobject(file_obj, algorithm, library)
|
||||
# Note: we don't seek because the update_file_obj call is supposed
|
||||
# to always seek to the beginning.
|
||||
self.assertEqual(digest_object_truth.digest(), digest_object.digest())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
327
src/tuf/tests/test_keystore.py
Executable file
327
src/tuf/tests/test_keystore.py
Executable file
|
|
@ -0,0 +1,327 @@
|
|||
"""
|
||||
<Program Name>
|
||||
test_keystore.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
|
||||
<Started>
|
||||
April 27, 2012.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Unit test for keystore.py.
|
||||
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import shutil
|
||||
import os
|
||||
|
||||
import tuf.repo.keystore
|
||||
import tuf.rsa_key
|
||||
import tuf.formats
|
||||
import tuf.util
|
||||
|
||||
# We'll need json module for testing '_encrypt()' and '_decrypt()'
|
||||
# internal function.
|
||||
json = tuf.util.import_json()
|
||||
|
||||
# Creating a directory string in current directory.
|
||||
_CURRENT_DIR = os.getcwd()
|
||||
_DIR = os.path.join(_CURRENT_DIR, 'test_keystore')
|
||||
|
||||
# Check if directory '_DIR' exists.
|
||||
if os.path.exists(_DIR):
|
||||
msg = ('\''+_DIR+'\' directory already exists,'+
|
||||
' please change '+'\'_DIR\''+' to something else.')
|
||||
raise tuf.Error(msg)
|
||||
|
||||
KEYSTORE = tuf.repo.keystore
|
||||
RSAKEYS = []
|
||||
PASSWDS = []
|
||||
temp_keys_info = []
|
||||
temp_keys_vals = []
|
||||
|
||||
print 'Generating keys...'
|
||||
for i in range(3):
|
||||
# Populating the original 'RSAKEYS' and 'PASSWDS' lists.
|
||||
RSAKEYS.append(tuf.rsa_key.generate())
|
||||
PASSWDS.append('passwd_'+str(i))
|
||||
|
||||
# Saving original copies of 'RSAKEYS' and 'PASSWDS' to temp variables
|
||||
# in order to repopulate them at the start of every test.
|
||||
temp_keys_info.append(RSAKEYS[i].values())
|
||||
temp_keys_vals.append(RSAKEYS[i]['keyval'].values())
|
||||
|
||||
temp_passwds=list(PASSWDS)
|
||||
print 'Done.'
|
||||
|
||||
|
||||
|
||||
class TestKeystore(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Returning 'RSAKEY' and 'PASSWDS' to original state.
|
||||
for i in range(len(temp_keys_info)):
|
||||
RSAKEYS[i]['keytype'] = temp_keys_info[i][0]
|
||||
RSAKEYS[i]['keyid'] = temp_keys_info[i][1]
|
||||
RSAKEYS[i]['keyval'] = temp_keys_info[i][2]
|
||||
RSAKEYS[i]['keyval']['public'] = temp_keys_vals[i][0]
|
||||
RSAKEYS[i]['keyval']['private'] = temp_keys_vals[i][1]
|
||||
PASSWDS[i] = temp_passwds[i]
|
||||
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
# Empty keystore's databases.
|
||||
KEYSTORE.clear_keystore()
|
||||
|
||||
# Check if directory '_DIR' exists, remove it if it does.
|
||||
if os.path.exists(_DIR):
|
||||
shutil.rmtree(_DIR)
|
||||
|
||||
|
||||
|
||||
def test_clear_keystore(self):
|
||||
# Populate KEYSTORE's internal databases '_keystore' and '_key_passwords'.
|
||||
for i in range(3):
|
||||
KEYSTORE.add_rsakey(RSAKEYS[i], PASSWDS[i], RSAKEYS[i]['keyid'])
|
||||
|
||||
# Verify KEYSTORE's internal databases ARE NOT EMPTY.
|
||||
self.assertTrue(len(KEYSTORE._keystore) > 0)
|
||||
self.assertTrue(len(KEYSTORE._key_passwords) > 0)
|
||||
|
||||
# Clear KEYSTORE's internal databases.
|
||||
KEYSTORE.clear_keystore()
|
||||
|
||||
# Verify KEYSTORE's internal databases ARE EMPTY.
|
||||
self.assertFalse(len(KEYSTORE._keystore) > 0)
|
||||
self.assertFalse(len(KEYSTORE._key_passwords) > 0)
|
||||
|
||||
|
||||
|
||||
def test_add_rsakey(self):
|
||||
# Passing 2 arguments to the function and verifying that the internal
|
||||
# databases have been modified.
|
||||
KEYSTORE.add_rsakey(RSAKEYS[0], PASSWDS[0])
|
||||
|
||||
self.assertEqual(RSAKEYS[0], KEYSTORE._keystore[RSAKEYS[0]['keyid']],
|
||||
'Adding an rsa key dict was unsuccessful.')
|
||||
|
||||
self.assertEqual(PASSWDS[0],
|
||||
KEYSTORE._key_passwords[RSAKEYS[0]['keyid']],
|
||||
'Adding a password pertaining to \'_keyid\' was unsuccessful.')
|
||||
|
||||
# Passing three arguments to the function, i.e. including the 'keyid'.
|
||||
KEYSTORE.add_rsakey(RSAKEYS[1], PASSWDS[1], RSAKEYS[1]['keyid'])
|
||||
|
||||
self.assertEqual(RSAKEYS[1],
|
||||
KEYSTORE._keystore[RSAKEYS[1]['keyid']],
|
||||
'Adding an rsa key dict was unsuccessful.')
|
||||
|
||||
self.assertEqual(PASSWDS[1],
|
||||
KEYSTORE._key_passwords[RSAKEYS[1]['keyid']],
|
||||
'Adding a password pertaining to \'_keyid\' was unsuccessful.')
|
||||
|
||||
# Passing a keyid that does not match the keyid in 'rsakey_dict'.
|
||||
_keyid = 'somedifferentkey123456789'
|
||||
self.assertRaises(tuf.Error, KEYSTORE.add_rsakey, RSAKEYS[2],
|
||||
PASSWDS[2], _keyid)
|
||||
|
||||
# Passing an existing 'rsakey_dict' object.
|
||||
self.assertRaises(tuf.KeyAlreadyExistsError, KEYSTORE.add_rsakey,
|
||||
RSAKEYS[1], PASSWDS[1], RSAKEYS[1]['keyid'])
|
||||
|
||||
# Passing an 'rsakey_dict' that does not conform to the 'RSAKEY_SCHEMA'.
|
||||
del RSAKEYS[2]['keytype']
|
||||
|
||||
self.assertRaises(tuf.FormatError, KEYSTORE.add_rsakey,
|
||||
RSAKEYS[2], PASSWDS[2], RSAKEYS[2]['keyid'])
|
||||
|
||||
|
||||
|
||||
def test_save_keystore_to_keyfiles(self):
|
||||
# Extract and store keyids in '_keyids' list.
|
||||
keyids = []
|
||||
|
||||
# Populate KEYSTORE's internal databases '_keystore' and '_key_passwords'.
|
||||
for i in range(3):
|
||||
KEYSTORE.add_rsakey(RSAKEYS[i], PASSWDS[i], RSAKEYS[i]['keyid'])
|
||||
keyids.append(RSAKEYS[i]['keyid'])
|
||||
|
||||
# Check if directory '_DIR' exists, remove it if it does.
|
||||
if os.path.exists(_DIR):
|
||||
shutil.rmtree(_DIR)
|
||||
|
||||
KEYSTORE.save_keystore_to_keyfiles(_DIR)
|
||||
|
||||
# Check if directory '_DIR' has been created.
|
||||
self.assertTrue(os.path.exists(_DIR), 'Creating directory failed.')
|
||||
|
||||
# Check if all of the key files where created and that they are not empty.
|
||||
for keyid in keyids:
|
||||
key_file = os.path.join(_DIR, str(keyid)+'.key')
|
||||
# Checks if key file has been created.
|
||||
self.assertTrue(os.path.exists(key_file), 'Key file does not exits.')
|
||||
|
||||
file_stats = os.stat(key_file)
|
||||
# Checks if key file is not empty.
|
||||
self.assertTrue(file_stats.st_size > 0)
|
||||
|
||||
# Passing an invalid 'directory_name' argument - an integer value.
|
||||
self.assertRaises(tuf.FormatError, KEYSTORE.save_keystore_to_keyfiles, 222)
|
||||
|
||||
|
||||
|
||||
def test_load_keystore_from_keyfiles(self):
|
||||
keyids = []
|
||||
# Check if '_DIR' directory exists, if not - create it.
|
||||
if not os.path.exists(_DIR):
|
||||
# Populate KEYSTORE's internal databases.
|
||||
for i in range(3):
|
||||
KEYSTORE.add_rsakey(RSAKEYS[i], PASSWDS[i], RSAKEYS[i]['keyid'])
|
||||
keyids.append(RSAKEYS[i]['keyid'])
|
||||
|
||||
# Create the key files.
|
||||
KEYSTORE.save_keystore_to_keyfiles(_DIR)
|
||||
|
||||
# Clearing internal databases.
|
||||
KEYSTORE.clear_keystore()
|
||||
|
||||
# Test normal conditions where two valid arguments are passed.
|
||||
loaded_keys = KEYSTORE.load_keystore_from_keyfiles(_DIR, keyids, PASSWDS)
|
||||
|
||||
# Loaded keys should all be contained in 'keyids'.
|
||||
loaded_keys_set = set(loaded_keys)
|
||||
keyids_set = set(keyids)
|
||||
intersect = keyids_set.intersection(loaded_keys_set)
|
||||
self.assertEquals(len(intersect), len(keyids))
|
||||
|
||||
for i in range(3):
|
||||
self.assertEqual(RSAKEYS[i], KEYSTORE._keystore[RSAKEYS[i]['keyid']])
|
||||
|
||||
# Clearing internal databases.
|
||||
KEYSTORE.clear_keystore()
|
||||
|
||||
_invalid_dir = os.path.join(_CURRENT_DIR, 'invalid_directory')
|
||||
|
||||
# Passing an invalid 'directory_name' argument - a directory that
|
||||
# does not exist. AS EXPECTED, THIS CALL SHOULDN'T RAISE ANY ERRORS.
|
||||
KEYSTORE.load_keystore_from_keyfiles(_invalid_dir, keyids, PASSWDS)
|
||||
|
||||
# The keystore should not have loaded any keys.
|
||||
self.assertEqual(0, len(KEYSTORE._keystore))
|
||||
self.assertEqual(0, len(KEYSTORE._key_passwords))
|
||||
|
||||
# Passing nonexistent 'keyids'.
|
||||
# AS EXPECTED, THIS CALL SHOULDN'T RAISE ANY ERRORS.
|
||||
invalid_keyids = ['333', '333', '333']
|
||||
KEYSTORE.load_keystore_from_keyfiles(_DIR, invalid_keyids, PASSWDS)
|
||||
|
||||
# The keystore should not have loaded any keys.
|
||||
self.assertEqual(0, len(KEYSTORE._keystore))
|
||||
self.assertEqual(0, len(KEYSTORE._key_passwords))
|
||||
|
||||
# Passing an invalid 'directory_name' argument - an integer value.
|
||||
self.assertRaises(tuf.FormatError, KEYSTORE.load_keystore_from_keyfiles,
|
||||
333, keyids, PASSWDS)
|
||||
|
||||
# Passing an invalid 'passwords' argument - a string value.
|
||||
self.assertRaises(tuf.FormatError, KEYSTORE.load_keystore_from_keyfiles,
|
||||
_DIR, keyids, '333')
|
||||
|
||||
# Passing an invalid 'passwords' argument - an integer value.
|
||||
self.assertRaises(tuf.FormatError, KEYSTORE.load_keystore_from_keyfiles,
|
||||
_DIR, keyids, 333)
|
||||
|
||||
# Passing an invalid 'keyids' argument - a string value.
|
||||
self.assertRaises(tuf.FormatError, KEYSTORE.load_keystore_from_keyfiles,
|
||||
_DIR, '333', PASSWDS)
|
||||
|
||||
# Passing an invalid 'keyids' argument - an integer value.
|
||||
self.assertRaises(tuf.FormatError, KEYSTORE.load_keystore_from_keyfiles,
|
||||
_DIR, 333, PASSWDS)
|
||||
|
||||
|
||||
|
||||
def test_change_password(self):
|
||||
# Populate KEYSTORE's internal databases.
|
||||
for i in range(2):
|
||||
KEYSTORE.add_rsakey(RSAKEYS[i], PASSWDS[i], RSAKEYS[i]['keyid'])
|
||||
|
||||
# Create a new password.
|
||||
new_passwd = 'changed_password'
|
||||
|
||||
# Change a password - normal case.
|
||||
KEYSTORE.change_password(RSAKEYS[0]['keyid'], PASSWDS[0], new_passwd)
|
||||
|
||||
# Check if password was changed.
|
||||
self.assertNotEqual(KEYSTORE._key_passwords[RSAKEYS[0]['keyid']],
|
||||
PASSWDS[0])
|
||||
self.assertEqual(KEYSTORE._key_passwords[RSAKEYS[0]['keyid']],
|
||||
new_passwd)
|
||||
|
||||
# Passing an invalid keyid i.e. RSAKEY[2] that was not loaded into
|
||||
# the '_keystore'.
|
||||
self.assertRaises(tuf.UnknownKeyError, KEYSTORE.change_password,
|
||||
RSAKEYS[2]['keyid'], PASSWDS[1], new_passwd)
|
||||
|
||||
# Passing an incorrect old password.
|
||||
self.assertRaises(tuf.BadPasswordError, KEYSTORE.change_password,
|
||||
RSAKEYS[1]['keyid'], PASSWDS[2], new_passwd)
|
||||
|
||||
|
||||
|
||||
def test_get_key(self):
|
||||
# Populate KEYSTORE's internal databases.
|
||||
for i in range(2):
|
||||
KEYSTORE.add_rsakey(RSAKEYS[i], PASSWDS[i], RSAKEYS[i]['keyid'])
|
||||
|
||||
# Get a key - normal case.
|
||||
self.assertEqual(KEYSTORE.get_key(RSAKEYS[0]['keyid']), RSAKEYS[0])
|
||||
|
||||
# Passing an invalid keyid.
|
||||
self.assertRaises(tuf.UnknownKeyError,
|
||||
KEYSTORE.get_key, RSAKEYS[2]['keyid'])
|
||||
|
||||
# Passing an invalid keyid format.
|
||||
self.assertRaises(tuf.FormatError, KEYSTORE.get_key, 123)
|
||||
|
||||
|
||||
|
||||
def test_internal_encrypt(self):
|
||||
# Test for valid arguments to '_encrypt()' and a valid return type.
|
||||
encrypted_key = KEYSTORE._encrypt(json.dumps(RSAKEYS[0]), PASSWDS[0])
|
||||
self.assertEqual(type(encrypted_key), str)
|
||||
|
||||
# Test for invalid arguments to _encrypt().
|
||||
self.assertRaises(tuf.CryptoError, KEYSTORE._encrypt, '', PASSWDS[0])
|
||||
self.assertRaises(tuf.CryptoError, KEYSTORE._encrypt,
|
||||
json.dumps(RSAKEYS[0]), '')
|
||||
|
||||
|
||||
|
||||
def test_internal_decrypt(self):
|
||||
del RSAKEYS[0]['keyid']
|
||||
tuf.formats.KEY_SCHEMA.check_match(RSAKEYS[0])
|
||||
|
||||
# Getting a valid encrypted key using '_encrypt()'.
|
||||
encrypted_key = KEYSTORE._encrypt(json.dumps(RSAKEYS[0]), PASSWDS[0])
|
||||
|
||||
# Decrypting and decoding (using json's loads()) an encrypted file.
|
||||
tuf.util.load_json_string(KEYSTORE._decrypt(encrypted_key, PASSWDS[0]))
|
||||
|
||||
self.assertEqual(RSAKEYS[0], tuf.util.load_json_string(
|
||||
KEYSTORE._decrypt(encrypted_key, PASSWDS[0])))
|
||||
|
||||
# Passing an invalid password to try to decrypt the file.
|
||||
self.assertRaises(tuf.CryptoError, KEYSTORE._decrypt,
|
||||
encrypted_key, PASSWDS[1])
|
||||
|
||||
|
||||
|
||||
# Run the unit tests.
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestKeystore)
|
||||
unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
122
src/tuf/tests/test_mirrors.py
Executable file
122
src/tuf/tests/test_mirrors.py
Executable file
|
|
@ -0,0 +1,122 @@
|
|||
"""
|
||||
<Program>
|
||||
test_mirrors.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
|
||||
<Started>
|
||||
March 26, 2012
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Test mirrors.py module.
|
||||
|
||||
"""
|
||||
|
||||
import tuf
|
||||
import tuf.formats as formats
|
||||
import tuf.mirrors as mirrors
|
||||
import copy
|
||||
import unittest
|
||||
|
||||
# Unit tests
|
||||
class TestMirrors(unittest.TestCase):
|
||||
mirrors = {'mirror1': {'url_prefix' : 'http://mirror1.com',
|
||||
'metadata_path' : 'metadata',
|
||||
'targets_path' : 'targets',
|
||||
'confined_target_paths' : ['targets/target1.py',
|
||||
'targets/target2.py']},
|
||||
'mirror2': {'url_prefix' : 'http://mirror2.com',
|
||||
'metadata_path' : 'metadata',
|
||||
'targets_path' : 'targets',
|
||||
'confined_target_paths' : ['targets/target3.py',
|
||||
'targets/target4.py']},
|
||||
'mirror3': {'url_prefix' : 'http://mirror3.com',
|
||||
'metadata_path' : 'metadata',
|
||||
'targets_path' : 'targets',
|
||||
'confined_target_paths' : ['targets/target1.py',
|
||||
'targets/target2.py']}}
|
||||
|
||||
|
||||
|
||||
# Testing if wrong formats are being detected.
|
||||
def testFormatErrors(self):
|
||||
|
||||
# Checking if all the formats are correct.
|
||||
self.assertTrue(formats.MIRRORDICT_SCHEMA.matches(TestMirrors.mirrors))
|
||||
|
||||
|
||||
file_path = 1234
|
||||
file_type = 'meta'
|
||||
self.assertRaises(tuf.FormatError, mirrors.get_list_of_mirrors,
|
||||
file_type, file_path, self.mirrors)
|
||||
|
||||
file_path = []
|
||||
self.assertRaises(tuf.FormatError, mirrors.get_list_of_mirrors,
|
||||
file_type, file_path, TestMirrors.mirrors)
|
||||
|
||||
file_path = {}
|
||||
self.assertRaises(tuf.FormatError, mirrors.get_list_of_mirrors,
|
||||
file_type, file_path, TestMirrors.mirrors)
|
||||
|
||||
file_type = 1234
|
||||
file_path = 'meta2.txt'
|
||||
self.assertRaises(tuf.FormatError, mirrors.get_list_of_mirrors,
|
||||
file_type, file_path, TestMirrors.mirrors)
|
||||
|
||||
file_type = 'blah'
|
||||
self.assertEqual(mirrors.get_list_of_mirrors(file_type, file_path,
|
||||
TestMirrors.mirrors),
|
||||
[])
|
||||
|
||||
|
||||
"""
|
||||
def testNormalCases(self):
|
||||
file_path = 'root.txt'
|
||||
file_type = 'meta'
|
||||
result = mirrors.get_list_of_mirrors(file_type, file_path,
|
||||
TestMirrors.mirrors)
|
||||
print result
|
||||
expected_output = ['http://mirror1.com/metadata/root.txt',
|
||||
'http://mirror2.com/metadata/root.txt',
|
||||
'http://mirror3.com/metadata/root.txt']
|
||||
self.assertEqual(expected_output, result, 'Expected output did not match')
|
||||
|
||||
|
||||
file_path = 'target1.py'
|
||||
file_type = 'target'
|
||||
result = mirrors.get_list_of_mirrors(file_type, file_path,
|
||||
TestMirrors.mirrors)
|
||||
print result
|
||||
expected_output = ['http://mirror1.com/targets/target1.py',
|
||||
'http://mirror3.com/targets/target1.py']
|
||||
self.assertEqual(expected_output, result, 'Expected output did not match')
|
||||
|
||||
|
||||
file_path = 'target4.py'
|
||||
file_type = 'targets'
|
||||
result = mirrors.get_list_of_mirrors(file_type, file_path,
|
||||
TestMirrors.mirrors)
|
||||
print result
|
||||
expected_output = ['http://mirror2.com/targets/target4.py']
|
||||
self.assertEqual(expected_output, result, 'Expected output did not match')
|
||||
"""
|
||||
|
||||
"""
|
||||
def testNonExistingPath(self):
|
||||
file_path = 'tArgetz.py'
|
||||
file_type = 'target'
|
||||
empty_list = mirrors.get_list_of_mirrors(file_type,
|
||||
file_path,
|
||||
self.mirrors)
|
||||
msg = 'List returned on wrong \'file_path\' '+'should be empty.'
|
||||
self.assertEqual([],empty_list, msg)
|
||||
"""
|
||||
|
||||
|
||||
# Run the unittests
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestMirrors)
|
||||
unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
175
src/tuf/tests/test_quickstart.py
Executable file
175
src/tuf/tests/test_quickstart.py
Executable file
|
|
@ -0,0 +1,175 @@
|
|||
"""
|
||||
<Program Name>
|
||||
test_quickstart.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
|
||||
<Started>
|
||||
September 6, 2012
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
To test quickstart.py for expected/unexpected input by the user, verifying
|
||||
that all unexpected input is caught and an exception is raised.
|
||||
|
||||
Given that all message prompts don't change - this will work pretty well
|
||||
for running quickstart without having to manually enter input to prompts
|
||||
every time you want to run quickstart.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
import quickstart
|
||||
import tuf.util
|
||||
import tuf.tests.unittest_toolbox
|
||||
|
||||
unit_tbox = tuf.tests.unittest_toolbox.Modified_TestCase
|
||||
|
||||
|
||||
class TestQuickstart(unit_tbox):
|
||||
def test_1_get_password(self):
|
||||
# A quick test of _get_password.
|
||||
password = self.random_string()
|
||||
def _mock_getpass(junk1, junk2, pw = password):
|
||||
return pw
|
||||
# Monkey patch getpass.getpass().
|
||||
quickstart.getpass.getpass = _mock_getpass
|
||||
# Run _get_password().
|
||||
self.assertEqual(quickstart._get_password(), password)
|
||||
|
||||
|
||||
def test_2_build_repository(self):
|
||||
|
||||
# SETUP
|
||||
|
||||
# Create the project directories.
|
||||
repo_dir = os.path.join(os.getcwd(), 'repository')
|
||||
keystore_dir = os.path.join(os.getcwd(), 'keystore')
|
||||
client_dir = os.path.join(os.getcwd(), 'client')
|
||||
|
||||
proj_files = self.make_temp_directory_with_data_files()
|
||||
proj_dir = os.path.join(proj_files[0], 'targets')
|
||||
|
||||
input_dict = {'expiration':'12/12/2013',
|
||||
'root':{'threshold':1, 'password':'pass'},
|
||||
'targets':{'threshold':1, 'password':'pass'},
|
||||
'release':{'threshold':1, 'password':'pass'},
|
||||
'timestamp':{'threshold':1, 'password':'pass'}}
|
||||
|
||||
|
||||
def _mock_prompt(message, junk=str, input_parameters=input_dict):
|
||||
if message.startswith('\nWhen would you like your '+
|
||||
'certificates to expire?'):
|
||||
return input_parameters['expiration']
|
||||
for role in self.role_list: # role_list=['root', 'targets', ...]
|
||||
if message.startswith('\nEnter the desired threshold '+
|
||||
'for the role '+repr(role)):
|
||||
return input_parameters[role]['threshold']
|
||||
elif message.startswith('Enter a password for '+repr(role)):
|
||||
for threshold in range(input_parameters[role]['threshold']):
|
||||
if message.endswith(repr(role)+' ('+str(threshold+1)+'): '):
|
||||
return input_parameters[role]['password']
|
||||
print 'Cannot recognize message: '+message
|
||||
|
||||
# Monkey patching quickstart's _prompt() and _get_password.
|
||||
quickstart._prompt = _mock_prompt
|
||||
quickstart._get_password = _mock_prompt
|
||||
|
||||
|
||||
def _remove_repository_directories(repo_dir, keystore_dir, client_dir):
|
||||
"""
|
||||
quickstart.py creates the 'client', 'keystore', and 'repository'
|
||||
directories in the current working directory. Remove these
|
||||
directories after every quickstart.build_repository() call.
|
||||
"""
|
||||
|
||||
try:
|
||||
shutil.rmtree(repo_dir)
|
||||
shutil.rmtree(keystore_dir)
|
||||
shutil.rmtree(client_dir)
|
||||
except OSError, e:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# TESTS
|
||||
|
||||
# TEST: various input parameters.
|
||||
# Supplying bogus expiration.
|
||||
input_dict['expiration'] = '5/8/2011'
|
||||
self.assertRaises(tuf.RepositoryError, quickstart.build_repository,
|
||||
proj_dir)
|
||||
input_dict['expiration'] = self.random_string() # random string
|
||||
self.assertRaises(tuf.RepositoryError, quickstart.build_repository,
|
||||
proj_dir)
|
||||
_remove_repository_directories(repo_dir, keystore_dir, client_dir)
|
||||
|
||||
# Restore expiration.
|
||||
input_dict['expiration'] = '10/10/2013'
|
||||
|
||||
# Supplying bogus 'root' threshold. Doing this for all roles slows
|
||||
# the test significantly.
|
||||
input_dict['root']['threshold'] = self.random_string()
|
||||
self.assertRaises(tuf.RepositoryError, quickstart.build_repository,
|
||||
proj_dir)
|
||||
_remove_repository_directories(repo_dir, keystore_dir, client_dir)
|
||||
|
||||
input_dict['root']['threshold'] = 0
|
||||
self.assertRaises(tuf.RepositoryError, quickstart.build_repository,
|
||||
proj_dir)
|
||||
_remove_repository_directories(repo_dir, keystore_dir, client_dir)
|
||||
|
||||
# Restore keystore directory.
|
||||
input_dict['root']['threshold'] = 1
|
||||
|
||||
|
||||
# TEST: normal case.
|
||||
try:
|
||||
quickstart.build_repository(proj_dir)
|
||||
except Exception, e:
|
||||
raise
|
||||
|
||||
# Verify the existence of metadata, target, and keystore files.
|
||||
meta_dir = os.path.join(repo_dir, 'metadata')
|
||||
targets_dir = os.path.join(repo_dir, 'targets')
|
||||
client_current_meta_dir = os.path.join(client_dir, 'metadata', 'current')
|
||||
client_previous_meta_dir = os.path.join(client_dir, 'metadata', 'previous')
|
||||
target_files = os.listdir(targets_dir)
|
||||
|
||||
# Verify repository, keystore, metadata, and targets directories.
|
||||
self.assertTrue(os.path.exists(repo_dir))
|
||||
self.assertTrue(os.path.exists(keystore_dir))
|
||||
self.assertTrue(os.path.exists(meta_dir))
|
||||
self.assertTrue(os.path.exists(targets_dir))
|
||||
self.assertTrue(os.path.exists(client_current_meta_dir))
|
||||
self.assertTrue(os.path.exists(client_previous_meta_dir))
|
||||
|
||||
# Verify that target_files exist.
|
||||
self.assertTrue(target_files)
|
||||
|
||||
for role in self.role_list:
|
||||
meta_file = role+'.txt'
|
||||
# Verify metadata file for a 'role'.
|
||||
self.assertTrue(os.path.isfile(os.path.join(meta_dir, meta_file)))
|
||||
# Get the metadata.
|
||||
signable = tuf.util.load_json_file(os.path.join(meta_dir, meta_file))
|
||||
for signature in range(len(signable['signatures'])):
|
||||
# Extract a keyid.
|
||||
keyid = signable['signatures'][signature]['keyid']
|
||||
key_file = os.path.join(keystore_dir, keyid+'.key')
|
||||
# Verify existence of a key for the keyid that belong to the 'role'.
|
||||
self.assertTrue(os.path.isfile(key_file))
|
||||
|
||||
_remove_repository_directories(repo_dir, keystore_dir, client_dir)
|
||||
|
||||
|
||||
|
||||
# Run the unit tests.
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestQuickstart)
|
||||
unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
185
src/tuf/tests/test_rsa_key.py
Executable file
185
src/tuf/tests/test_rsa_key.py
Executable file
|
|
@ -0,0 +1,185 @@
|
|||
"""
|
||||
<Program Name>
|
||||
test_rsa_key.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
|
||||
<Started>
|
||||
April 24, 2012.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Test cases for rsa_key.py.
|
||||
|
||||
<Notes>
|
||||
I'm using 'global rsakey_dict' - there is no harm in doing so since
|
||||
in order to modify the global variable in any method, python requires
|
||||
explicit indication to modify i.e. declaring 'global' in each method
|
||||
that modifies the global variable 'rsakey_dict'.
|
||||
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
import tuf.formats
|
||||
import tuf.rsa_key
|
||||
|
||||
|
||||
RSA_KEY = tuf.rsa_key
|
||||
FORMAT_ERROR_MSG = 'tuf.FormatError was raised! Check object\'s format.'
|
||||
DATA = 'SOME DATA REQUIRING AUTHENTICITY.'
|
||||
|
||||
|
||||
rsakey_dict = RSA_KEY.generate()
|
||||
temp_key_info_vals = rsakey_dict.values()
|
||||
temp_key_vals = rsakey_dict['keyval'].values()
|
||||
|
||||
class TestRSA_KEY(unittest.TestCase):
|
||||
def setUp(self):
|
||||
rsakey_dict['keytype']=temp_key_info_vals[0]
|
||||
rsakey_dict['keyid']=temp_key_info_vals[1]
|
||||
rsakey_dict['keyval']=temp_key_info_vals[2]
|
||||
rsakey_dict['keyval']['public']=temp_key_vals[0]
|
||||
rsakey_dict['keyval']['private']=temp_key_vals[1]
|
||||
|
||||
|
||||
def test_generate(self):
|
||||
_rsakey_dict = RSA_KEY.generate()
|
||||
|
||||
# Check if the format of the object returned by generate() corresponds
|
||||
# to RSAKEY_SCHEMA format.
|
||||
self.assertEqual(None, tuf.formats.RSAKEY_SCHEMA.check_match(_rsakey_dict),
|
||||
FORMAT_ERROR_MSG)
|
||||
|
||||
# Passing a bit value that is <2048 to generate() - should raise
|
||||
# 'tuf.FormatError'.
|
||||
self.assertRaises(tuf.FormatError, RSA_KEY.generate, 555)
|
||||
|
||||
# Passing a string instead of integer for a bit value.
|
||||
self.assertRaises(tuf.FormatError, RSA_KEY.generate, 'bits')
|
||||
|
||||
# NOTE if random bit value >=2048 (not 4096) is passed generate(bits)
|
||||
# does not raise any errors and returns a valid key.
|
||||
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(RSA_KEY.generate(2048)))
|
||||
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(RSA_KEY.generate(4096)))
|
||||
|
||||
def test_create_in_metadata_format(self):
|
||||
key_value = rsakey_dict['keyval']
|
||||
key_meta = RSA_KEY.create_in_metadata_format(key_value)
|
||||
|
||||
# Check if the format of the object returned by this function corresponds
|
||||
# to KEY_SCHEMA format.
|
||||
self.assertEqual(None,
|
||||
tuf.formats.KEY_SCHEMA.check_match(key_meta),
|
||||
FORMAT_ERROR_MSG)
|
||||
key_meta = RSA_KEY.create_in_metadata_format(key_value, private=True)
|
||||
|
||||
# Check if the format of the object returned by this function corresponds
|
||||
# to KEY_SCHEMA format.
|
||||
self.assertEqual(None, tuf.formats.KEY_SCHEMA.check_match(key_meta),
|
||||
FORMAT_ERROR_MSG)
|
||||
|
||||
# Supplying a 'bad' key_value.
|
||||
del key_value['public']
|
||||
self.assertRaises(tuf.FormatError, RSA_KEY.create_in_metadata_format,
|
||||
key_value)
|
||||
|
||||
|
||||
def test_create_from_metadata_format(self):
|
||||
# Reconfiguring rsakey_dict to conform to KEY_SCHEMA
|
||||
# i.e. {keytype: 'rsa', keyval: {public: pub_key, private: priv_key}}
|
||||
#keyid = rsakey_dict['keyid']
|
||||
del rsakey_dict['keyid']
|
||||
|
||||
rsakey_dict_from_meta = RSA_KEY.create_from_metadata_format(rsakey_dict)
|
||||
|
||||
# Check if the format of the object returned by this function corresponds
|
||||
# to RSAKEY_SCHEMA format.
|
||||
self.assertEqual(None,
|
||||
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict_from_meta),
|
||||
FORMAT_ERROR_MSG)
|
||||
|
||||
# Supplying a wrong number of arguments.
|
||||
self.assertRaises(TypeError, RSA_KEY.create_from_metadata_format)
|
||||
args = (rsakey_dict, rsakey_dict)
|
||||
self.assertRaises(TypeError, RSA_KEY.create_from_metadata_format, *args)
|
||||
|
||||
# Supplying a malformed argument to the function - should get FormatError
|
||||
del rsakey_dict['keyval']
|
||||
self.assertRaises(tuf.FormatError, RSA_KEY.create_from_metadata_format,
|
||||
rsakey_dict)
|
||||
|
||||
|
||||
def test_helper_get_keyid(self):
|
||||
key_value = rsakey_dict['keyval']
|
||||
|
||||
# Check format of 'key_value'.
|
||||
self.assertEqual(None, tuf.formats.KEYVAL_SCHEMA.check_match(key_value),
|
||||
FORMAT_ERROR_MSG)
|
||||
|
||||
keyid = RSA_KEY._get_keyid(key_value)
|
||||
|
||||
# Check format of 'keyid' - the output of '_get_keyid()' function.
|
||||
self.assertEqual(None, tuf.formats.KEYID_SCHEMA.check_match(keyid),
|
||||
FORMAT_ERROR_MSG)
|
||||
|
||||
|
||||
def test_createsignature(self):
|
||||
# Creating a signature for 'DATA'.
|
||||
signature = RSA_KEY.create_signature(rsakey_dict, DATA)
|
||||
|
||||
# Check format of output.
|
||||
self.assertEqual(None,
|
||||
tuf.formats.SIGNATURE_SCHEMA.check_match(signature),
|
||||
FORMAT_ERROR_MSG)
|
||||
|
||||
# Removing private key from 'rsakey_dict' - should raise a TypeError.
|
||||
rsakey_dict['keyval']['private'] = ''
|
||||
|
||||
args = (rsakey_dict, DATA)
|
||||
self.assertRaises(TypeError, RSA_KEY.create_signature, *args)
|
||||
|
||||
# Supplying an incorrect number of arguments.
|
||||
self.assertRaises(TypeError, RSA_KEY.create_signature)
|
||||
|
||||
|
||||
def test_verify_signature(self):
|
||||
# Creating a signature 'signature' of 'DATA' to be verified.
|
||||
signature = RSA_KEY.create_signature(rsakey_dict, DATA)
|
||||
|
||||
# Verifying the 'signature' of 'DATA'.
|
||||
verified = RSA_KEY.verify_signature(rsakey_dict, signature, DATA)
|
||||
self.assertTrue(verified, "Incorrect signature.")
|
||||
|
||||
# Testing an invalid 'signature'. Same 'signature' is passed, with
|
||||
# 'DATA' different than the original 'DATA' that was used
|
||||
# in creating the 'signature'. Function should return 'False'.
|
||||
|
||||
# Modifying 'DATA'.
|
||||
_DATA = '1111'+DATA+'1111'
|
||||
|
||||
# Verifying the 'signature' of modified '_DATA'.
|
||||
verified = RSA_KEY.verify_signature(rsakey_dict, signature, _DATA)
|
||||
self.assertFalse(verified,
|
||||
'Returned \'True\' on an incorrect signature.')
|
||||
|
||||
# Modifying 'signature' to pass an incorrect method since only 'evp'
|
||||
# is accepted.
|
||||
signature['method'] = 'not_evp'
|
||||
|
||||
args = (rsakey_dict, signature, DATA)
|
||||
self.assertRaises(tuf.UnknownMethodError, RSA_KEY.verify_signature, *args)
|
||||
|
||||
# Passing incorrect number of arguments.
|
||||
self.assertRaises(TypeError,RSA_KEY.verify_signature)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Run the unit tests.
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestRSA_KEY)
|
||||
unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue