Merge and resolve conflicts

This commit is contained in:
vladdd 2013-10-02 12:36:04 -04:00
commit 100e83136f
23 changed files with 2093 additions and 148 deletions

0
ed25519/__init__.py Executable file
View file

13
ed25519/checkparams.py Normal file
View file

@ -0,0 +1,13 @@
from ed25519 import *
assert b >= 10
assert 8 * len(H("hash input")) == 2 * b
assert expmod(2,q-1,q) == 1
assert q % 4 == 1
assert expmod(2,l-1,l) == 1
assert l >= 2**(b-4)
assert l <= 2**(b-3)
assert expmod(d,(q-1)/2,q) == q-1
assert expmod(I,2,q) == q-1
assert isoncurve(B)
assert scalarmult(B,l) == [0,1]

104
ed25519/ed25519.py Executable file
View file

@ -0,0 +1,104 @@
import hashlib
b = 256
q = 2**255 - 19
l = 2**252 + 27742317777372353535851937790883648493
def H(m):
return hashlib.sha512(m).digest()
def expmod(b,e,m):
if e == 0: return 1
t = expmod(b,e/2,m)**2 % m
if e & 1: t = (t*b) % m
return t
def inv(x):
return expmod(x,q-2,q)
d = -121665 * inv(121666)
I = expmod(2,(q-1)/4,q)
def xrecover(y):
xx = (y*y-1) * inv(d*y*y+1)
x = expmod(xx,(q+3)/8,q)
if (x*x - xx) % q != 0: x = (x*I) % q
if x % 2 != 0: x = q-x
return x
By = 4 * inv(5)
Bx = xrecover(By)
B = [Bx % q,By % q]
def edwards(P,Q):
x1 = P[0]
y1 = P[1]
x2 = Q[0]
y2 = Q[1]
x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2)
y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2)
return [x3 % q,y3 % q]
def scalarmult(P,e):
if e == 0: return [0,1]
Q = scalarmult(P,e/2)
Q = edwards(Q,Q)
if e & 1: Q = edwards(Q,P)
return Q
def encodeint(y):
bits = [(y >> i) & 1 for i in range(b)]
return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)])
def encodepoint(P):
x = P[0]
y = P[1]
bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)])
def bit(h,i):
return (ord(h[i/8]) >> (i%8)) & 1
def publickey(sk):
h = H(sk)
a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
A = scalarmult(B,a)
return encodepoint(A)
def Hint(m):
h = H(m)
return sum(2**i * bit(h,i) for i in range(2*b))
def signature(m,sk,pk):
h = H(sk)
a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
r = Hint(''.join([h[i] for i in range(b/8,b/4)]) + m)
R = scalarmult(B,r)
S = (r + Hint(encodepoint(R) + pk + m) * a) % l
return encodepoint(R) + encodeint(S)
def isoncurve(P):
x = P[0]
y = P[1]
return (-x*x + y*y - 1 - d*x*x*y*y) % q == 0
def decodeint(s):
return sum(2**i * bit(s,i) for i in range(0,b))
def decodepoint(s):
y = sum(2**i * bit(s,i) for i in range(0,b-1))
x = xrecover(y)
if x & 1 != bit(s,b-1): x = q-x
P = [x,y]
if not isoncurve(P): raise Exception("decoding point that is not on curve")
return P
def checkvalid(s,m,pk):
if len(s) != b/4: raise Exception("signature length is wrong")
if len(pk) != b/8: raise Exception("public-key length is wrong")
R = decodepoint(s[0:b/8])
A = decodepoint(pk)
S = decodeint(s[b/8:b/4])
h = Hint(encodepoint(R) + pk + m)
if scalarmult(B,S) != edwards(R,scalarmult(A,h)):
raise Exception("signature does not pass verification")

1024
ed25519/sign.input Normal file

File diff suppressed because it is too large Load diff

39
ed25519/sign.py Normal file
View file

@ -0,0 +1,39 @@
import sys
import binascii
import ed25519
# examples of inputs: see sign.input
# should produce no output: python sign.py < sign.input
# warning: currently 37 seconds/line on a fast machine
# fields on each input line: sk, pk, m, sm
# each field hex
# each field colon-terminated
# sk includes pk at end
# sm includes m at end
while 1:
line = sys.stdin.readline()
if not line: break
x = line.split(':')
sk = binascii.unhexlify(x[0][0:64])
pk = ed25519.publickey(sk)
m = binascii.unhexlify(x[2])
s = ed25519.signature(m,sk,pk)
ed25519.checkvalid(s,m,pk)
forgedsuccess = 0
try:
if len(m) == 0:
forgedm = "x"
else:
forgedmlen = len(m)
forgedm = ''.join([chr(ord(m[i])+(i==forgedmlen-1)) for i in range(forgedmlen)])
ed25519.checkvalid(s,forgedm,pk)
forgedsuccess = 1
except:
pass
assert not forgedsuccess
assert x[0] == binascii.hexlify(sk + pk)
assert x[1] == binascii.hexlify(pk)
assert x[3] == binascii.hexlify(s + m)

View file

@ -1,4 +1,5 @@
"""
<Program Name>
test_arbitrary_package_attack.py
@ -29,6 +30,13 @@
"""
# Help with Python 3 compatability, where the print statement is a function, an
# implicit relative import is invalid, and the '/' operator performs true
# division. Example: print 'hello world' raises a 'SyntaxError' exception.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import os
import urllib
@ -130,24 +138,24 @@ def test_arbitrary_package_attack(using_tuf=False):
print 'Attempting arbitrary package attack without TUF:'
print('Attempting arbitrary package attack without TUF:')
try:
test_arbitrary_package_attack(using_tuf=False)
except ArbitraryPackageAlert, error:
print error
print(error)
else:
print 'Extraneous dependency attack failed.'
print
print('Extraneous dependency attack failed.')
print()
print 'Attempting arbitrary package attack with TUF:'
print('Attempting arbitrary package attack with TUF:')
try:
test_arbitrary_package_attack(using_tuf=True)
except ArbitraryPackageAlert, error:
print error
print(error)
else:
print 'Extraneous dependency attack failed.'
print
print('Extraneous dependency attack failed.')
print()

View file

@ -17,10 +17,6 @@
Ensure that TUF meets expectations about target delegations.
"""
import os
import time
import tempfile

View file

@ -17,21 +17,26 @@
Simulate an endless data attack. A simple client update vs. client
update implementing TUF.
Note: The interposition provided by 'tuf.interposition' is used to intercept
all calls made by urllib/urillib2 to certain hostname specified in
the interposition configuration file. Look up interposition.py for more
information and illustration of a sample contents of the interposition
configuration file. Interposition was meant to make TUF integration with an
existing software updater an easy process. This allows for more flexibility
to the existing software updater. However, if you are planning to solely use
TUF there should be no need for interposition, all necessary calls will be
generated from within TUF.
Note: The interposition provided by 'tuf.interposition' is used to intercept
all calls made by urllib/urillib2 to certain hostname specified in
the interposition configuration file. Look up interposition.py for more
information and illustration of a sample contents of the interposition
configuration file. Interposition was meant to make TUF integration with an
existing software updater an easy process. This allows for more flexibility
to the existing software updater. However, if you are planning to solely use
TUF there should be no need for interposition, all necessary calls will be
generated from within TUF.
Note: There is no difference between 'updates' and 'target' files.
There is no difference between 'updates' and 'target' files.
"""
# Help with Python 3 compatability, where the print statement is a function, an
# implicit relative import is invalid, and the '/' operator performs true
# division. Example: print 'hello world' raises a 'SyntaxError' exception.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import os
import urllib
@ -182,6 +187,3 @@ def test_endless_data_attack(using_tuf=False, TIMESTAMP=False):
print(str(error))
else:
print('Endless data attack did not work on download with TUF!')

View file

@ -36,6 +36,12 @@
"""
# Help with Python 3 compatability, where the print statement is a function, an
# implicit relative import is invalid, and the '/' operator performs true
# division. Example: print 'hello world' raises a 'SyntaxError' exception.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import os
import urllib
@ -84,7 +90,6 @@ def test_extraneous_dependency_attack(using_tuf=False):
ERROR_MSG = 'Extraneous Dependency Attack was Successful!'
try:
# Setup.
root_repo, url, server_proc, keyids = util_test_tools.init_repo(using_tuf)
@ -168,22 +173,22 @@ def test_extraneous_dependency_attack(using_tuf=False):
print 'Attempting extraneous dependency attack without TUF:'
print('Attempting extraneous dependency attack without TUF:')
try:
test_extraneous_dependency_attack(using_tuf=False)
except ExtraneousDependencyAlert, error:
print error
print(error)
else:
print 'Extraneous dependency attack failed.'
print
print('Extraneous dependency attack failed.')
print()
print 'Attempting extraneous dependency attack with TUF:'
print('Attempting extraneous dependency attack with TUF:')
try:
test_extraneous_dependency_attack(using_tuf=True)
except ExtraneousDependencyAlert, error:
print error
print(error)
else:
print 'Extraneous dependency attack failed.'
print
print('Extraneous dependency attack failed.')
print()

View file

@ -19,6 +19,13 @@
"""
# Help with Python 3 compatability, where the print statement is a function, an
# implicit relative import is invalid, and the '/' operator performs true
# division. Example: print 'hello world' raises a 'SyntaxError' exception.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import os
import sys
import time
@ -101,7 +108,7 @@ def test_indefinite_freeze_attack(using_tuf=False):
downloaded_file = os.path.join(downloads, file_basename)
if using_tuf:
print 'TUF ...'
print('TUF ...')
# Update TUF metadata before attacker modifies anything.
util_test_tools.tuf_refresh_repo(root_repo, keyids)
@ -123,8 +130,8 @@ def test_indefinite_freeze_attack(using_tuf=False):
try:
_download(url_to_repo, downloaded_file, using_tuf)
except:
print 'Initial download failed! It may be because your machine is '+ \
'busy. Try again later.'
print('Initial download failed! It may be because your machine is '+ \
'busy. Try again later.')
else:
# Expire timestamp.
time.sleep(EXPIRATION)
@ -133,7 +140,7 @@ def test_indefinite_freeze_attack(using_tuf=False):
try:
_download(url_to_repo, downloaded_file, using_tuf)
except tuf.ExpiredMetadataError, error:
print 'Caught an expiration error!'
print('Caught an expiration error!')
else:
raise IndefiniteFreezeAttackAlert(ERROR_MSG)
finally:
@ -146,10 +153,10 @@ def test_indefinite_freeze_attack(using_tuf=False):
try:
test_indefinite_freeze_attack(using_tuf=False)
except IndefiniteFreezeAttackAlert, error:
print error
print(error)
try:
test_indefinite_freeze_attack(using_tuf=True)
except IndefiniteFreezeAttackAlert, error:
print error
print(error)

View file

@ -33,6 +33,12 @@
"""
# Help with Python 3 compatability, where the print statement is a function, an
# implicit relative import is invalid, and the '/' operator performs true
# division. Example: print 'hello world' raises a 'SyntaxError' exception.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import os
import shutil
@ -99,7 +105,7 @@ def test_mix_and_match_attack(using_tuf=False):
if using_tuf:
print 'TUF ...'
print('TUF ...')
tuf_repo = os.path.join(root_repo, 'tuf_repo')
tuf_targets = os.path.join(tuf_repo, 'targets')
metadata_dir = os.path.join(tuf_repo, 'metadata')
@ -170,7 +176,7 @@ def test_mix_and_match_attack(using_tuf=False):
except tuf.NoWorkingMirrorError as errors:
for mirror_url, mirror_error in errors.mirror_errors.iteritems():
if type(mirror_error) == tuf.BadHashError:
print 'Caught a Bad Hash Error!'
print('Caught a Bad Hash Error!')
# Check whether the attack succeeded by inspecting the content of the
# update. The update should contain 'Test NOT A'.
@ -189,10 +195,10 @@ def test_mix_and_match_attack(using_tuf=False):
try:
test_mix_and_match_attack(using_tuf=False)
except MixAndMatchAttackAlert, error:
print error
print(error)
try:
test_mix_and_match_attack(using_tuf=True)
except MixAndMatchAttackAlert, error:
print error
print(error)

View file

@ -21,20 +21,27 @@
software that is older than that which the client previously knew to be
available.
NOTE: The interposition provided by 'tuf.interposition' is used to intercept
all calls made by urllib/urillib2 to certain hostname specified in
the interposition configuration file. Look up interposition.py for more
information and illustration of a sample contents of the interposition
configuration file. Interposition was meant to make TUF integration with an
existing software updater an easy process. This allows for more flexibility
to the existing software updater. However, if you are planning to solely use
TUF there should be no need for interposition, all necessary calls will be
generated from within TUF.
NOTE: The interposition provided by 'tuf.interposition' is used to intercept
all calls made by urllib/urillib2 to certain hostname specified in
the interposition configuration file. Look up interposition.py for more
information and illustration of a sample contents of the interposition
configuration file. Interposition was meant to make TUF integration with an
existing software updater an easy process. This allows for more flexibility
to the existing software updater. However, if you are planning to solely use
TUF there should be no need for interposition, all necessary calls will be
generated from within TUF.
Note: There is no difference between 'updates' and 'target' files.
There is no difference between 'updates' and 'target' files.
"""
# Help with Python 3 compatability, where the print statement is a function, an
# implicit relative import is invalid, and the '/' operator performs true
# division. Example: print 'hello world' raises a 'SyntaxError' exception.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import os
import shutil
import urllib
@ -48,9 +55,6 @@ class TestSetupError(Exception): pass
class ReplayAttackAlert(Exception): pass
def _download(url, filename, using_tuf=False):
if using_tuf:
tuf.interposition.urllib_tuf.urlretrieve(url, filename)
@ -198,8 +202,3 @@ def test_replay_attack(using_tuf=False):
print('Download without TUF failed due to: '+str(exception))
else:
print('Download without TUF did NOT fail due to replayed metadata attack!')

View file

@ -21,23 +21,26 @@
being aware of interference with receiving updates by responding to client
requests so slowly that automated updates never complete.
NOTE: Currently TUF does not protect against slow retrieval attacks.
NOTE: The interposition provided by 'tuf.interposition' is used to intercept
all calls made by urllib/urillib2 to certain network locations specified in
the interposition configuration file. Look up interposition.py for more
information and illustration of a sample contents of the interposition
configuration file. Interposition was meant to make TUF integration with an
existing software updater an easy process. This allows for more flexibility
to the existing software updater. However, if you are planning to solely use
TUF there should be no need for interposition, all necessary calls will be
generated from within TUF.
NOTE: The interposition provided by 'tuf.interposition' is used to intercept
all calls made by urllib/urillib2 to certain network locations specified in
the interposition configuration file. Look up interposition.py for more
information and illustration of a sample contents of the interposition
configuration file. Interposition was meant to make TUF integration with an
existing software updater an easy process. This allows for more flexibility
to the existing software updater. However, if you are planning to solely use
TUF there should be no need for interposition, all necessary calls will be
generated from within TUF.
Note: There is no difference between 'updates' and 'target' files.
There is no difference between 'updates' and 'target' files.
"""
# Help with Python 3 compatability, where the print statement is a function, an
# implicit relative import is invalid, and the '/' operator performs true
# division. Example: print 'hello world' raises a 'SyntaxError' exception.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
from multiprocessing import Process
import os
@ -48,7 +51,6 @@
import tuf
import urllib
import tuf.interposition
import tuf.tests.util_test_tools as util_test_tools
@ -107,7 +109,6 @@ def test_slow_retrieval_attack(using_tuf=False, mode=None):
url_to_file = url+'reg_repo/'+file_basename
downloaded_file = os.path.join(downloads, file_basename)
if using_tuf:
tuf_repo = os.path.join(root_repo, 'tuf_repo')

View file

@ -36,6 +36,8 @@
# internal function.
json = tuf.util.import_json()
tuf.repo.keystore._PBKDF2_ITERATIONS = 1000
# Creating a directory string in current directory.
_CURRENT_DIR = os.getcwd()
_DIR = os.path.join(_CURRENT_DIR, 'test_keystore')
@ -297,8 +299,10 @@ def test_get_key(self):
def test_internal_encrypt(self):
# Test for valid arguments to '_encrypt()' and a valid return type.
salt = Crypto.Random.new().read(16)
iterations = tuf.repo.keystore._PBKDF2_ITERATIONS
derived_key = Crypto.Protocol.KDF.PBKDF2(PASSWDS[0], salt)
derived_key_information = {'salt': salt, 'derived_key': derived_key}
derived_key_information = {'salt': salt, 'derived_key': derived_key,
'iterations': iterations}
encrypted_key = KEYSTORE._encrypt(json.dumps(RSAKEYS[0]),
derived_key_information)
self.assertEqual(type(encrypted_key), str)
@ -310,8 +314,11 @@ def test_internal_decrypt(self):
tuf.formats.KEY_SCHEMA.check_match(RSAKEYS[0])
salt = Crypto.Random.new().read(16)
salt, derived_key = tuf.repo.keystore._generate_derived_key(PASSWDS[0], salt)
derived_key_information = {'salt': salt, 'derived_key': derived_key}
salt, iterations, derived_key = \
tuf.repo.keystore._generate_derived_key(PASSWDS[0], salt)
derived_key_information = {'salt': salt,
'iterations': iterations,
'derived_key': derived_key}
# Getting a valid encrypted key using '_encrypt()'.
encrypted_key = KEYSTORE._encrypt(json.dumps(RSAKEYS[0]),

View file

@ -176,6 +176,7 @@ def __str__(self):
class UnknownMethodError(CryptoError):
"""Indicate that a user-specified cryptograpthic method is unknown."""
pass
@ -311,8 +312,3 @@ def __str__(self):
all_errors += '\n '+str(mirror_netloc)+': '+str(mirror_error)
return all_errors

View file

@ -54,4 +54,14 @@
# The time (in seconds) we ignore a server with a slow initial retrieval speed.
SLOW_START_GRACE_PERIOD = 30 #seconds
# The current "good enough" number of PBKDF2 passphrase iterations.
# We recommend that important keys, such as root, be kept offline.
# 'tuf.conf.PBKDF2_ITERATIONS' should increase as CPU speeds increase, set here
# at 100,000 iterations by default (in 2013). The repository maintainer may opt
# to modify the default setting according to their security needs and
# computational restrictions. A strong user password is still important.
# Modifying the number of iterations will result in a new derived key+PBDKF2
# combination if the key is loaded and re-saved, overriding any previous
# iteration setting used by the old '<keyid>.key'.
# https://en.wikipedia.org/wiki/PBKDF2
PBKDF2_ITERATIONS = 100000

618
tuf/ed25519_key.py Executable file
View file

@ -0,0 +1,618 @@
"""
<Program Name>
ed25519_key.py
<Author>
Vladimir Diaz <vladimir.v.diaz@gmail.com>
<Started>
September 24, 2013.
<Copyright>
See LICENSE for licensing information.
<Purpose>
The goal of this module is to support ed25519 signatures. ed25519 is an
elliptic-curve public key signature scheme, its main strength being small
signatures (64 bytes) and small public keys (32 bytes).
http://ed25519.cr.yp.to/
'tuf/ed25519_key.py' calls 'ed25519/ed25519.py', which is the pure Python
implementation of ed25519 provided by the author:
http://ed25519.cr.yp.to/software.html
Optionally, ed25519 cryptographic operations may be executed by PyNaCl, which
provides Python bindings to the NaCl library and is much faster than the pure
python implementation. PyNaCl relies on the C library, libsodium.
https://github.com/dstufft/pynacl
https://github.com/jedisct1/libsodium
http://nacl.cr.yp.to/
The ed25519-related functions included here are generate(), create_signature()
and verify_signature(). The 'ed25519' and PyNaCl (i.e., 'nacl') modules used
by ed25519_key.py generate the actual ed25519 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
ed25519 keys compatible with the key structures listed in TUF Metadata files.
The generate() function returns a dictionary containing all the information
needed of ed25519 keys, such as public/private keys and a keyID identifier.
create_signature() and verify_signature() are supplemental functions used for
generating ed25519 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). Review 'ed25519_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., ed25519_key_dict['keyid']).
"""
# Help with Python 3 compatability, where the print statement is a function, an
# implicit relative import is invalid, and the '/' operator performs true
# division. Example: print 'hello world' raises a 'SyntaxError' exception.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
# Required for hexadecimal conversions. Signatures and public/private keys are
# hexlified.
import binascii
# Generate OS-specific randomness (os.urandom) suitable for cryptographic use.
# http://docs.python.org/2/library/os.html#miscellaneous-functions
import os
# Import the python implementation of the ed25519 algorithm that is provided by
# the author. Note: This implementation is very slow and does not include
# protection against side-channel attacks according to the author. Verifying
# signatures can take approximately 9 seconds on a intel core 2 duo @
# 2.2 ghz x 2). Optionally, the PyNaCl module may be used to speed up ed25519
# cryptographic operations.
# http://ed25519.cr.yp.to/software.html
try:
import nacl.signing
import nacl.encoding
except ImportError:
pass
# The pure Python implementation of ed25519.
import ed25519.ed25519
import tuf
# Digest objects needed to generate hashes.
import tuf.hash
# Perform object format-checking.
import tuf.formats
# The default hash algorithm to use when generating KeyIDs.
_KEY_ID_HASH_ALGORITHM = 'sha256'
# Supported ed25519 signing methods. 'ed25519-python' is the pure Python
# implementation signing method. 'ed25519-pynacl' (i.e., 'nacl' module) is the
# (libsodium+Python bindings) implementation signing method.
_SUPPORTED_ED25519_SIGNING_METHODS = ['ed25519-python', 'ed25519-pynacl']
def generate(use_pynacl=False):
"""
<Purpose>
Generate an ed25519 seed key ('sk') and public key ('pk').
In addition, a keyid used as an identifier for ed25519 keys is generated.
The object returned conforms to 'tuf.formats.ED25519KEY_SCHEMA' and has the
form:
{'keytype': 'ed25519',
'keyid': keyid,
'keyval': {'public': '876f5584a9db99b8546c0d8608d6...',
'private': 'bf7336055c7638276efe9afe039...'}}
The public and private keys are strings. An ed25519 seed key is a random
32-byte value and public key 32 bytes, although both are hexlified.
>>> ed25519_key = generate()
>>> tuf.formats.ED25519KEY_SCHEMA.matches(ed25519_key)
True
>>> len(ed25519_key['keyval']['public'])
64
>>> len(ed25519_key['keyval']['private'])
64
>>> ed25519_key_pynacl = generate(use_pynacl=True)
>>> tuf.formats.ED25519KEY_SCHEMA.matches(ed25519_key_pynacl)
True
>>> len(ed25519_key_pynacl['keyval']['public'])
64
>>> len(ed25519_key_pynacl['keyval']['private'])
64
<Arguments>
use_pynacl:
True, if the ed25519 keys should be generated with PyNaCl. False, if the
keys should be generated with the pure Python implementation of ed25519
(much slower).
<Exceptions>
NotImplementedError, if a randomness source is not found.
<Side Effects>
The ed25519 keys are generated by first creating a random 32-byte value
'sk' with os.urandom() and then calling ed25519's ed25519.25519.publickey(sk)
or PyNaCl's nacl.signing.SigningKey().
<Returns>
A dictionary containing the ed25519 keys and other identifying information.
Conforms to 'tuf.formats.ED25519KEY_SCHEMA'.
"""
# Begin building the ed25519 key dictionary.
ed25519_key_dict = {}
keytype = 'ed25519'
# Generate ed25519's seed key by calling os.urandom(). The random bytes
# returned should be suitable for cryptographic use and is OS-specific.
# Raise 'NotImplementedError' if a randomness source is not found.
# ed25519 seed keys are fixed at 32 bytes (256-bit keys).
# http://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
seed = os.urandom(32)
public = None
if use_pynacl:
# Generate the public key. PyNaCl (i.e., 'nacl' module) performs
# the actual key generation.
nacl_key = nacl.signing.SigningKey(seed)
public = str(nacl_key.verify_key)
# Use the pure Python implementation of ed25519.
else:
public = ed25519.ed25519.publickey(seed)
# Generate the keyid for the ed25519 key dict. 'key_value' corresponds to the
# 'keyval' entry of the 'ED25519KEY_SCHEMA' dictionary. The seed (private)
# key information is not included in the generation of the 'keyid' identifier.
key_value = {'public': binascii.hexlify(public),
'private': ''}
keyid = _get_keyid(key_value)
# Build the 'ed25519_key_dict' dictionary. Update 'key_value' with the
# ed25519 seed key prior to adding 'key_value' to 'ed25519_key_dict'.
key_value['private'] = binascii.hexlify(seed)
ed25519_key_dict['keytype'] = keytype
ed25519_key_dict['keyid'] = keyid
ed25519_key_dict['keyval'] = key_value
return ed25519_key_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': 'ed25519',
'keyval': {'public': '876f5584a9db99b8546c0d8608d6...',
'private': 'bf7336055c7638276efe9afe039...'}}
or if 'private' is False:
{'keytype': 'ed25519',
'keyval': {'public': '876f5584a9db99b8546c0d8608d6...',
'private': ''}}
The private and public keys are 32 bytes, although hexlified.
ed25519 keys are stored in Metadata files (e.g., root.txt) in the format
returned by this function.
>>> ed25519_key = generate()
>>> key_val = ed25519_key['keyval']
>>> ed25519_metadata = create_in_metadata_format(key_val, private=True)
>>> tuf.formats.KEY_SCHEMA.matches(ed25519_metadata)
True
<Arguments>
key_value:
A dictionary containing a seed and public ed25519 key.
'key_value' is of the form:
{'public': '876f5584a9db99b8546c0d8608d6...',
'private': 'bf7336055c7638276efe9afe039...'}
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>
A '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 is True and len(key_value['private']):
return {'keytype': 'ed25519', 'keyval': key_value}
else:
public_key_value = {'public': key_value['public'], 'private': ''}
return {'keytype': 'ed25519', 'keyval': public_key_value}
def create_from_metadata_format(key_metadata):
"""
<Purpose>
Construct an ed25519 key dictionary (i.e., tuf.formats.ED25519KEY_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': 'ed25519',
'keyid': keyid,
'keyval': {'public': '876f5584a9db99b8546c0d8608d6...',
'private': 'bf7336055c7638276efe9afe039...'}}
The public and private keys are 32-byte strings, although hexlified.
ed25519 key dictionaries in 'ED25519KEY_SCHEMA' format should be used by
modules storing a collection of keys, such as a keydb keystore.
ed25519 keys as stored in metadata files use a different format, so this
function should be called if an ed25519 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.py' and
'keystore.py'.
>>> ed25519_key = generate()
>>> key_val = ed25519_key['keyval']
>>> ed25519_metadata = create_in_metadata_format(key_val, private=True)
>>> ed25519_key_2 = create_from_metadata_format(ed25519_metadata)
>>> tuf.formats.ED25519KEY_SCHEMA.matches(ed25519_key_2)
True
>>> ed25519_key == ed25519_key_2
True
<Arguments>
key_metadata:
The ed25519 key dictionary as stored in Metadata files, conforming to
'tuf.formats.KEY_SCHEMA'. It has the form:
{'keytype': 'ed25519',
'keyval': {'public': '876f5584a9db99b8546c0d8608d6...',
'private': 'bf7336055c7638276efe9afe039...'}}
<Exceptions>
tuf.FormatError, if 'key_metadata' does not conform to
'tuf.formats.KEY_SCHEMA'.
<Side Effects>
None.
<Returns>
A dictionary containing the ed25519 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.
ed25519_key_dict = {}
keytype = 'ed25519'
key_value = key_metadata['keyval']
# Convert 'key_value' to 'tuf.formats.KEY_SCHEMA' and generate its hash
# The hash is in hexdigest form. _get_keyid() ensures the private key
# information is not included.
keyid = _get_keyid(key_value)
# We now have all the required key values. Build 'ed25519_key_dict'.
ed25519_key_dict['keytype'] = keytype
ed25519_key_dict['keyid'] = keyid
ed25519_key_dict['keyval'] = key_value
return ed25519_key_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().
ed25519_key_meta = create_in_metadata_format(key_value, private=False)
# Convert the ed25519 key to JSON Canonical format suitable for adding
# to digest objects.
ed25519_key_update_data = tuf.formats.encode_canonical(ed25519_key_meta)
# Create a digest object and call update(), using the JSON
# canonical format of 'ed25519_key_meta' as the update data.
digest_object = tuf.hash.digest(_KEY_ID_HASH_ALGORITHM)
digest_object.update(ed25519_key_update_data)
# 'keyid' becomes the hexadecimal representation of the hash.
keyid = digest_object.hexdigest()
return keyid
def create_signature(ed25519_key_dict, data, use_pynacl=False):
"""
<Purpose>
Return a signature dictionary of the form:
{'keyid': 'a0469d9491e3c0b42dd41fe3455359dbacb3306b6e8fb59...',
'method': 'ed25519-python',
'sig': '4b3829671b2c6b90034518a918d2447c722474c878c2431dd...'}
Note: 'method' may also be 'ed25519-pynacl', if the signature was created
by the 'nacl' module.
The signing process will use the public and seed key
ed25519_key_dict['keyval']['private'],
ed25519_key_dict['keyval']['public']
and 'data' to generate the signature.
>>> ed25519_key_dict = generate()
>>> data = 'The quick brown fox jumps over the lazy dog.'
>>> signature = create_signature(ed25519_key_dict, data)
>>> tuf.formats.SIGNATURE_SCHEMA.matches(signature)
True
>>> len(signature['sig'])
128
>>> signature_pynacl = create_signature(ed25519_key_dict, data, True)
>>> tuf.formats.SIGNATURE_SCHEMA.matches(signature_pynacl)
True
>>> len(signature_pynacl['sig'])
128
<Arguments>
ed25519_key_dict:
A dictionary containing the ed25519 keys and other identifying information.
'ed25519_key_dict' has the form:
{'keytype': 'ed25519',
'keyid': keyid,
'keyval': {'public': '876f5584a9db99b8546c0d8608d6...',
'private': 'bf7336055c7638276efe9afe039...'}}
The public and private keys are 32-byte strings, although hexlified.
data:
Data object used by create_signature() to generate the signature.
use_pynacl:
True, if the ed25519 signature should be generated with PyNaCl. False,
if the signature should be generated with the pure Python implementation
of ed25519 (much slower).
<Exceptions>
TypeError, if a private key is not defined for 'ed25519_key_dict'.
tuf.FormatError, if an incorrect format is found for 'ed25519_key_dict'.
tuf.CryptoError, if a signature cannot be created.
<Side Effects>
ed25519.ed25519.signature() or nacl.signing.SigningKey.sign() called to
generate the actual signature.
<Returns>
A signature dictionary conformat to 'tuf.format.SIGNATURE_SCHEMA'.
ed25519 signatures are 64 bytes, however, the hexlified signature is
stored in the dictionary returned.
"""
# Does 'ed25519_key_dict' have the correct format?
# This check will ensure 'ed25519_key_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.ED25519KEY_SCHEMA.check_match(ed25519_key_dict)
# Signing the 'data' object requires a seed and public key.
# 'ed25519.ed25519.py' generates the actual 64-byte signature in pure Python.
# nacl.signing.SigningKey.sign() generates the signature if 'use_pynacl'
# is True.
signature = {}
private_key = ed25519_key_dict['keyval']['private']
public_key = ed25519_key_dict['keyval']['public']
private_key = binascii.unhexlify(private_key)
public_key = binascii.unhexlify(public_key)
keyid = ed25519_key_dict['keyid']
method = None
sig = None
# Verify the signature, but only if the private key has been set. The private
# key is a NULL string if unset. Although it may be clearer to explicit check
# that 'private_key' is not '', we can/should check for a value and not
# compare identities with the 'is' keyword.
if len(private_key):
if use_pynacl:
method = 'ed25519-pynacl'
try:
nacl_key = nacl.signing.SigningKey(private_key)
nacl_sig = nacl_key.sign(data)
sig = nacl_sig.signature
except (ValueError, nacl.signing.CryptoError):
message = 'An "ed25519-pynacl" signature could not be created.'
raise tuf.CryptoError(message)
# Generate an "ed25519-python" (i.e., pure python implementation) signature.
else:
# ed25519.ed25519.signature() requires both the seed and public keys.
# It calculates the SHA512 of the seed key, which is 32 bytes.
method = 'ed25519-python'
try:
sig = ed25519.ed25519.signature(data, private_key, public_key)
except Exception, e:
message = 'An "ed25519-python" signature could not be generated.'
raise tuf.CryptoError(message)
# Raise an exception since the private key is not defined.
else:
message = 'The required private key is not defined for "ed25519_key_dict".'
raise TypeError(message)
# 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(ed25519_key_dict, signature, data, use_pynacl=False):
"""
<Purpose>
Determine whether the seed key belonging to 'ed25519_key_dict' produced
'signature'. verify_signature() will use the public key found in
'ed25519_key_dict', the 'method' and 'sig' objects contained in 'signature',
and 'data' to complete the verification. Type-checking performed on both
'ed25519_key_dict' and 'signature'.
>>> ed25519_key_dict = generate()
>>> data = 'The quick brown fox jumps over the lazy dog.'
>>> signature = create_signature(ed25519_key_dict, data)
>>> verify_signature(ed25519_key_dict, signature, data)
True
>>> verify_signature(ed25519_key_dict, signature, data, True)
True
>>> bad_data = 'The sly brown fox jumps over the lazy dog.'
>>> bad_signature = create_signature(ed25519_key_dict, bad_data)
>>> verify_signature(ed25519_key_dict, bad_signature, data, True)
False
<Arguments>
ed25519_key_dict:
A dictionary containing the ed25519 keys and other identifying
information. 'ed25519_key_dict' has the form:
{'keytype': 'ed25519',
'keyid': 'a0469d9491e3c0b42dd41fe3455359dbacb3306b6e8fb59...',
'keyval': {'public': '876f5584a9db99b8546c0d8608d6...',
'private': 'bf7336055c7638276efe9afe039...'}}
The public and private keys are 32-byte strings, although hexlified.
signature:
The signature dictionary produced by tuf.ed25519_key.create_signature().
'signature' has the form:
{'keyid': 'a0469d9491e3c0b42dd41fe3455359dbacb3306b6e8fb59...',
'method': 'ed25519-python',
'sig': '4b3829671b2c6b90034518a918d2447c722474c878c2431dd...'}
Conformant to 'tuf.formats.SIGNATURE_SCHEMA'.
data:
Data object used by tuf.ed25519_key.create_signature() to generate
'signature'. 'data' is needed here to verify the signature.
use_pynacl:
True, if the ed25519 signature should be verified with PyNaCl. False,
if the signature should be verified with the pure Python implementation
of ed25519 (much slower).
<Exceptions>
tuf.UnknownMethodError. Raised if the signing method used by
'signature' is not one supported by tuf.ed25519_key.create_signature().
tuf.FormatError. Raised if either 'ed25519_key_dict'
or 'signature' do not match their respective tuf.formats schema.
'ed25519_key_dict' must conform to 'tuf.formats.ED25519KEY_SCHEMA'.
'signature' must conform to 'tuf.formats.SIGNATURE_SCHEMA'.
<Side Effects>
ed25519.ed25519.checkvalid() called to do the actual verification.
nacl.signing.VerifyKey.verify() called if 'use_pynacl' is True.
<Returns>
Boolean. True if the signature is valid, False otherwise.
"""
# Does 'ed25519_key_dict' have the correct format?
# This check will ensure 'ed25519_key_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.ED25519KEY_SCHEMA.check_match(ed25519_key_dict)
# Does 'signature' have the correct format?
tuf.formats.SIGNATURE_SCHEMA.check_match(signature)
# Using the public key belonging to 'ed25519_key_dict'
# (i.e., ed25519_key_dict['keyval']['public']), verify whether 'signature'
# was produced by ed25519_key_dict's corresponding seed key
# ed25519_key_dict['keyval']['private']. Before returning the Boolean result,
# ensure 'ed25519-python' or 'ed25519-pynacl' was used as the signing method.
method = signature['method']
sig = signature['sig']
sig = binascii.unhexlify(sig)
public = ed25519_key_dict['keyval']['public']
public = binascii.unhexlify(public)
valid_signature = False
if method in _SUPPORTED_ED25519_SIGNING_METHODS:
if use_pynacl:
try:
nacl_verify_key = nacl.signing.VerifyKey(public)
nacl_message = nacl_verify_key.verify(data, sig)
if nacl_message == data:
valid_signature = True
except nacl.signing.BadSignatureError:
pass
# Verify signature with 'ed25519-python' (i.e., pure Python implementation).
else:
try:
ed25519.ed25519.checkvalid(sig, data, public)
valid_signature = True
# The pure Python implementation raises 'Exception' if 'signature' is
# invalid.
except Exception, e:
pass
else:
message = 'Unsupported ed25519 signing method: '+repr(method)+'.\n'+ \
'Supported methods: '+repr(_SUPPORTED_ED25519_SIGNING_METHODS)+'.'
raise tuf.UnknownMethodError(message)
return valid_signature
if __name__ == '__main__':
# The interactive sessions of the documentation strings can
# be tested by running 'ed25519_key.py' as a standalone module.
# python -B ed25519_key.py
import doctest
doctest.testmod()

View file

@ -168,6 +168,13 @@
keyid=KEYID_SCHEMA,
keyval=KEYVAL_SCHEMA)
# An ed25519 key.
ED25519KEY_SCHEMA = SCHEMA.Object(
object_name='ed25519key',
keytype=SCHEMA.String('ed25519'),
keyid=KEYID_SCHEMA,
keyval=KEYVAL_SCHEMA)
# Info that describes both metadata and target files.
# This schema allows the storage of multiple hashes for the same file
# (e.g., sha256 and sha512 may be computed for the same file and stored).

View file

@ -72,9 +72,8 @@
_FORMAT_STRING = '[%(asctime)s UTC] [%(name)s] [%(levelname)s]'+\
'[%(funcName)s:%(lineno)s@%(filename)s] %(message)s'
# Ask all Formatter instances to talk GMT.
# http://docs.python.org/2/library/logging.html#logging.Formatter.formatException
# http://docs.python.org/2/library/logging.html#logging.Formatter.formatTime
logging.Formatter.converter = time.gmtime
formatter = logging.Formatter(_FORMAT_STRING)
@ -144,6 +143,7 @@ def filter(self, record):
# original exception traceback. The exc_info is explained here:
# http://docs.python.org/2/library/sys.html#sys.exc_info
exc_type, exc_value, exc_traceback = record.exc_info
# Simply set the class name as the exception text.
record.exc_text = exc_type.__name__
@ -242,6 +242,9 @@ def set_console_log_level(log_level=_DEFAULT_CONSOLE_LOG_LEVEL):
# Raise 'tuf.FormatError' if there is a mismatch.
tuf.formats.LENGTH_SCHEMA.check_match(log_level)
# Assign to the global console_handler object.
global console_handler
if console_handler is not None:
console_handler.setLevel(log_level)
else:
@ -250,6 +253,8 @@ def set_console_log_level(log_level=_DEFAULT_CONSOLE_LOG_LEVEL):
def add_console_handler(log_level=_DEFAULT_CONSOLE_LOG_LEVEL):
"""
<Purpose>
@ -271,13 +276,14 @@ def add_console_handler(log_level=_DEFAULT_CONSOLE_LOG_LEVEL):
None.
"""
# Assign to the global console_handler object.
global console_handler
# Does 'log_level' have the correct format?
# Raise 'tuf.FormatError' if there is a mismatch.
tuf.formats.LENGTH_SCHEMA.check_match(log_level)
# Assign to the global console_handler object.
global console_handler
if not console_handler:
# Set the console handler for the logger. The built-in console handler will
# log messages to 'sys.stderr' and capture 'log_level' messages.
@ -299,7 +305,26 @@ def add_console_handler(log_level=_DEFAULT_CONSOLE_LOG_LEVEL):
def remove_console_handler():
# Assign to the global console_handler object.
"""
<Purpose>
Remove the console handler from the logger in 'log.py', if previously added.
<Arguments>
None.
<Exceptions>
None.
<Side Effects>
A handler belonging to the console is removed from the 'log.py' logger
and the console handler is marked as unset.
<Returns>
None.
"""
# Assign to the global 'console_handler' object.
global console_handler
if console_handler:
@ -309,8 +334,3 @@ def remove_console_handler():
logger.debug('Removed a console handler.')
else:
logger.warn('We do not have a console handler.')

View file

@ -61,14 +61,16 @@
import Crypto.Random
# The mode of operation is presently set to CTR (CounTeR Mode) for symmetric
# block encryption (AES-256). PyCrypto provides a callable stateful block
# counter that can update successive blocks when needed. The initial random
# block (IV) can be set to begin the process of incrementing the 128-bit blocks
# and allowing the AES algorithm to perform cipher block operations on them.
# block encryption (AES-256, where the symmetric key is 256 bits). PyCrypto
# provides a callable stateful block counter that can update successive blocks
# when needed. The initial random block, or initialization vector (IV), can
# be set to begin the process of incrementing the 128-bit blocks and allowing
# the AES algorithm to perform cipher block operations on them.
import Crypto.Util.Counter
import tuf.rsa_key
import tuf.util
import tuf.conf
# See 'log.py' to learn how logging is handled in TUF.
@ -77,7 +79,7 @@
json = tuf.util.import_json()
# The delimiter symbol used to separate the different sections
# of encrypted files (i.e., salt, IV, ciphertext, passphrase).
# of encrypted files (i.e., salt, iterations, hmac, IV, ciphertext).
# This delimiter is arbitrarily chosen and should not occur in
# the hexadecimal representations of the fields it is separating.
_ENCRYPTION_DELIMITER = '@@@@'
@ -86,23 +88,35 @@
_AES_KEY_SIZE = 32
# Default salt size, in bytes. A 128-bit salt (i.e., a random sequence of data
# to protect against dictionary attacks) is generated for PBKDF2.
# to protect against attacks that use precomputed rainbow tables to crack
# password hashes) is generated for PBKDF2.
_SALT_SIZE = 16
# Default PBKDF2 passphrase iterations. The current (2013) "good enough" number
# Default PBKDF2 passphrase iterations. The current "good enough" number
# of passphrase iterations. We recommend that important keys, such as root,
# be kept offline. Are we going overboard with respect to our use case?
# http://security.stackexchange.com/questions/3959/recommended-of-iterations-when-using-pkbdf2-sha256
_PBKDF2_ITERATIONS = 100000
# be kept offline. 'tuf.conf.PBKDF2_ITERATIONS' should increase as CPU
# speeds increase, set here at 100,000 iterations by default (in 2013).
# Repository maintainers may opt to modify the default setting according to
# their security needs and computational restrictions. A strong user password
# is still important. Modifying the number of iterations will result in a new
# derived key+PBDKF2 combination if the key is loaded and re-saved, overriding
# any previous iteration setting used by the old '<keyid>.key'.
# https://en.wikipedia.org/wiki/PBKDF2
_PBKDF2_ITERATIONS = tuf.conf.PBKDF2_ITERATIONS
# A user password is read and a derived key generated. The derived key and
# salt returned by the key derivation function (PBKDF2) is saved in
# '_derived_keys', which has the form:
# {keyid: {'salt': ..., 'derived_key': ...}}
# A user password is read and a derived key generated. The derived key returned
# by the key derivation function (PBKDF2) is saved in '_derived_keys', along
# with the salt and iterations used, which has the form:
# {keyid: {'salt': '\x9b\x90\xf1g\xb9li\x04\x8d\x10h\xb5T\xaa\xc1',
# 'iterations': 10000,
# 'derived_key': '\xda\xed\xf2\xe0\x8f\x03\xeb\xde!\xc4RJ'},
# keyid2: ...}
_derived_keys = {}
# The keystore database, which has the form:
# {keyid: key, keyid2: key2, ...}
# {keyid: key,
# keyid2: key2,
# ...}
_keystore = {}
@ -171,8 +185,8 @@ def add_rsakey(rsakey_dict, password, keyid=None):
'Expected: '+repr(keyid)
raise tuf.Error(message)
# Check if the keyid belonging to 'rsakey_dict' is not already
# available in the key database.
# Check if the keyid belonging to 'rsakey_dict' is not already available in
# the key database.
keyid = rsakey_dict['keyid']
if keyid in _keystore:
message = 'Keyid: '+repr(keyid)+' already exists.'
@ -181,8 +195,10 @@ def add_rsakey(rsakey_dict, password, keyid=None):
# The '_derived_keys' dictionary does not store the user's password. A key
# derivation function is applied to 'password' prior to storing it in
# _derived_key and may then be used as a symmetric key.
salt, derived_key = _generate_derived_key(password)
_derived_keys[keyid] = {'salt': salt, 'derived_key': derived_key}
salt, iterations, derived_key = _generate_derived_key(password)
_derived_keys[keyid] = {'salt': salt,
'derived_key': derived_key,
'iterations': iterations}
_keystore[keyid] = rsakey_dict
@ -249,15 +265,15 @@ def load_keystore_from_keyfiles(directory_name, keyids, passwords):
keyfilename = keyid+'.key'
full_filepath = os.path.join(directory_name, keyfilename)
raw_contents = open(full_filepath, 'rb').read()
except:
logger.warn('Could not find key '+repr(full_filepath)+'.')
except (OSError, IOError), e:
logger.warn('Could not load key file: '+repr(full_filepath)+'.')
else:
# Try to decrypt the file using one of the passwords in 'passwords'.
for password in passwords:
try:
json_data = _decrypt(raw_contents, password)
except:
logger.warn(repr(full_filepath)+' contains an invalid key.')
except tuf.CryptoError, e:
logger.warn(repr(full_filepath)+' could not be decrypted.')
continue
try:
@ -447,16 +463,22 @@ def change_password(keyid, old_password, new_password):
# stores derived keys instead of user passwords, according to the
# key derivation function used by _generate_derived_key().
salt = _derived_keys[keyid]['salt']
junk, old_derived_key = _generate_derived_key(old_password, salt)
iterations = _derived_keys[keyid]['iterations']
# Discard the old "salt" and "iterations" values, as we only need the old
# derived key.
junk_old_salt, junk_old_iterations, old_derived_key = \
_generate_derived_key(old_password, salt, iterations)
if _derived_keys[keyid]['derived_key'] != old_derived_key:
message = 'Old password invalid.'
raise tuf.BadPasswordError(message)
# Update '_derived_keys[keyid]' with the new derived key and salt.
salt, new_derived_key = _generate_derived_key(new_password)
salt, iterations, new_derived_key = _generate_derived_key(new_password)
_derived_keys[keyid] = {}
_derived_keys[keyid]['salt'] = salt
_derived_keys[keyid]['derived_key'] = new_derived_key
_derived_keys[keyid]['iterations'] = iterations
@ -502,7 +524,7 @@ def get_key(keyid):
def _generate_derived_key(password, salt=None):
def _generate_derived_key(password, salt=None, iterations=None):
"""
Generate a derived key by feeding 'password' to the Password-Based Key
Derivation Function (PBKDF2). PyCrypto's PBKDF2 implementation is
@ -514,6 +536,9 @@ def _generate_derived_key(password, salt=None):
if salt is None:
salt = Crypto.Random.new().read(_SALT_SIZE)
if iterations is None:
iterations = _PBKDF2_ITERATIONS
def pseudorandom_function(password, salt):
"""
@ -530,10 +555,10 @@ def pseudorandom_function(password, salt):
# must be callable.
derived_key = Crypto.Protocol.KDF.PBKDF2(password, salt,
dkLen=_AES_KEY_SIZE,
count=_PBKDF2_ITERATIONS,
count=iterations,
prf=pseudorandom_function)
return salt, derived_key
return salt, iterations, derived_key
@ -554,8 +579,9 @@ def _encrypt(key_data, derived_key_information):
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
'derived_key_information' is a dictionary of the form:
{'salt': '...'
'derived_key': '...'}
{'salt': '...',
'derived_key': '...',
'iterations': '...'}
'tuf.CryptoError' raised if the encryption fails.
@ -563,7 +589,7 @@ def _encrypt(key_data, derived_key_information):
# Generate a random initialization vector (IV). The 'iv' is treated as the
# initial counter block to a stateful counter block function (i.e.,
# PyCrypto's 'Crypto.Util.Counter'. The AES block cipher operates on 128-bit
# PyCrypto's 'Crypto.Util.Counter'). The AES block cipher operates on 128-bit
# blocks, so generate a random 16-byte initialization block. PyCrypto expects
# the initial value of the stateful counter to be an integer.
# Follow the provably secure encrypt-then-MAC approach, which affords the
@ -583,7 +609,11 @@ def _encrypt(key_data, derived_key_information):
# repetitions are performed by AES, 14 cycles for 256-bit keys.
try:
ciphertext = aes_cipher.encrypt(key_data)
except:
# Raise generic exception message to avoid revealing sensitive information,
# such as invalid passwords, encryption keys, etc., that an attacker can use
# to his advantage.
except Exception, e:
message = 'The key data could not be encrypted.'
raise tuf.CryptoError(message)
@ -591,15 +621,22 @@ def _encrypt(key_data, derived_key_information):
# The decryption routine may verify a ciphertext without having to perform
# a decryption operation.
salt = derived_key_information['salt']
derived_key = derived_key_information['derived_key']
hmac_object = Crypto.Hash.HMAC.new(derived_key, ciphertext, Crypto.Hash.SHA256)
hmac_object = Crypto.Hash.HMAC.new(symmetric_key, ciphertext,
Crypto.Hash.SHA256)
hmac = hmac_object.hexdigest()
# Return the hmac, initialization vector, and ciphertext as a single string.
# These three values are delimited by '_ENCRYPTION_DELIMITER' to make
# extraction easier. This delimiter is arbitrarily chosen and should not
# occur in the hexadecimal representations of the fields it is separating.
# Store the number of PBKDF2 iterations used to derive the symmetric key so
# that the decryption routine can regenerate the symmetric key successfully.
# The pbkdf2 iterations are allowed to vary for the keys loaded and saved.
iterations = derived_key_information['iterations']
# Return the salt, iterations, hmac, initialization vector, and ciphertext
# as a single string. These five values are delimited by
# '_ENCRYPTION_DELIMITER' to make extraction easier. This delimiter is
# arbitrarily chosen and should not occur in the hexadecimal representations
# of the fields it is separating.
return binascii.hexlify(salt) + _ENCRYPTION_DELIMITER + \
binascii.hexlify(str(iterations)) + _ENCRYPTION_DELIMITER + \
binascii.hexlify(hmac) + _ENCRYPTION_DELIMITER + \
binascii.hexlify(iv) + _ENCRYPTION_DELIMITER + \
binascii.hexlify(ciphertext)
@ -616,21 +653,25 @@ def _decrypt(file_contents, password):
"""
# Extract the salt, hmac, initialization vector, and ciphertext from
# 'file_contents'. These three values are delimited by '_ENCRYPTION_DELIMITER'.
# This delimiter is arbitrarily chosen and should not occur in the
# hexadecimal representations of the fields it is separating.
salt, hmac, iv, ciphertext = file_contents.split(_ENCRYPTION_DELIMITER)
# Extract the salt, iterations, hmac, initialization vector, and ciphertext
# from 'file_contents'. These five values are delimited by
# '_ENCRYPTION_DELIMITER'. This delimiter is arbitrarily chosen and should
# not occur in the hexadecimal representations of the fields it is separating.
salt, iterations, hmac, iv, ciphertext = \
file_contents.split(_ENCRYPTION_DELIMITER)
# Ensure we have the expected raw data for the delimited cryptographic data.
salt = binascii.unhexlify(salt)
salt = binascii.unhexlify(salt)
iterations = int(binascii.unhexlify(iterations))
hmac = binascii.unhexlify(hmac)
iv = binascii.unhexlify(iv)
ciphertext = binascii.unhexlify(ciphertext)
# Generate derived key from 'password'. The salt is specified so that
# the expected derived key is regenerated correctly.
junk, derived_key = _generate_derived_key(password, salt)
# Generate derived key from 'password'. The salt and iterations are specified
# so that the expected derived key is regenerated correctly. Discard the old
# "salt" and "iterations" values, as we only need the old derived key.
junk_old_salt, junk_old_iterations, derived_key = \
_generate_derived_key(password, salt, iterations)
# Verify the hmac to ensure the ciphertext is valid and has not been altered.
# See the encryption routine for why we use the encrypt-then-MAC approach.
@ -650,7 +691,11 @@ def _decrypt(file_contents, password):
counter=stateful_counter_128bit_blocks)
try:
key_plaintext = aes_cipher.decrypt(ciphertext)
except:
# Raise generic exception message to avoid revealing sensitive information,
# such as invalid passwords, encryption keys, etc., that an attacker can
# use to his advantage.
except Exception, e:
raise tuf.CryptoError('Decryption failed.')
return key_plaintext

View file

@ -378,9 +378,14 @@ def create_signature(rsakey_dict, data):
keyid = rsakey_dict['keyid']
method = 'PyCrypto-PKCS#1 PSS'
sig = None
if private_key:
# Take
# Verify the signature, but only if the private key has been set. The private
# key is a NULL string if unset. Although it may be clearer to explicit check
# that 'private_key' is not '', we can/should check for a value and not
# compare identities with the 'is' keyword.
if len(private_key):
# Calculate the SHA256 hash of 'data' and generate the hash's PKCS1-PSS
# signature.
try:
rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)
sha256_object = Crypto.Hash.SHA256.new(data)

View file

@ -390,9 +390,10 @@ def generate_rsakey():
Modified_TestCase.rsa_keyids.append(keyid)
password = Modified_TestCase.random_string()
Modified_TestCase.rsa_passwords[keyid] = password
salt, derived_key = keystore._generate_derived_key(password)
salt, iterations, derived_key = keystore._generate_derived_key(password)
Modified_TestCase.rsa_derived_keys[keyid] = {'salt': salt,
'derived_key': derived_key}
'derived_key': derived_key,
'iterations': iterations}
Modified_TestCase.rsa_keystore[keyid] = rsakey
return keyid

32
tuf/time_ed25519.py Executable file
View file

@ -0,0 +1,32 @@
from __future__ import print_function, absolute_import, division
import sys
import timeit
import tuf
from tuf.ed25519_key import *
use_pynacl = False
if '--pynacl' in sys.argv:
use_pynacl = True
print('Time generate()')
print(timeit.timeit('generate(use_pynacl)',
setup='from __main__ import generate, use_pynacl',
number=1))
print('\nTime create_signature()')
print(timeit.timeit('create_signature(ed25519_key, data, use_pynacl)',
setup='from __main__ import generate, create_signature, \
use_pynacl; \
ed25519_key = generate(use_pynacl);\
data = "The quick brown fox jumps over the lazy dog"',
number=1))
print('\nTime verify_signature()')
print(timeit.timeit('verify_signature(ed25519_key, signature, data, use_pynacl)',
setup='from __main__ import generate, create_signature, \
verify_signature, use_pynacl;\
ed25519_key = generate(use_pynacl);\
data = "The quick brown fox jumps over the lazy dog";\
signature = create_signature(ed25519_key, data, use_pynacl)',
number=1))