Initial commit.

This commit is contained in:
Kon 2013-01-31 13:54:15 -05:00
commit 0fb43a5e36
106 changed files with 23846 additions and 0 deletions

30
src/MANIFEST Executable file
View 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
View file

200
src/evpy/cipher.py Executable file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

2561
src/simplejson/_speedups.c Executable file

File diff suppressed because it is too large Load diff

421
src/simplejson/decoder.py Executable file
View 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

Binary file not shown.

501
src/simplejson/encoder.py Executable file
View 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. &amp;) 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

Binary file not shown.

119
src/simplejson/ordered_dict.py Executable file
View 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
View 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

Binary file not shown.

39
src/simplejson/tool.py Executable file
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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)
"""

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View 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

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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.
===============================================================================

View file

@ -0,0 +1 @@
print 'hello world!'

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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.
===============================================================================

View file

@ -0,0 +1 @@
delegated target 1

View file

@ -0,0 +1 @@
delegated target 2

1162
src/tuf/formats.py Executable file

File diff suppressed because it is too large Load diff

299
src/tuf/hash.py Executable file
View 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
View 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
View 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
View 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

View file

View 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
View 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()

View 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()

View file

112
src/tuf/pushtools/transfer/scp.py Executable file
View 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
View file

485
src/tuf/repo/keystore.py Executable file
View 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

File diff suppressed because it is too large Load diff

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
View 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
View 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
View 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
View 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
View file

354
src/tuf/tests/repository_setup.py Executable file
View 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
View 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()

View 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
View 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
View 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
View 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
View 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
View 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