mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
Merge and resolve conflicts
This commit is contained in:
commit
100e83136f
23 changed files with 2093 additions and 148 deletions
0
ed25519/__init__.py
Executable file
0
ed25519/__init__.py
Executable file
13
ed25519/checkparams.py
Normal file
13
ed25519/checkparams.py
Normal 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
104
ed25519/ed25519.py
Executable 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
1024
ed25519/sign.input
Normal file
File diff suppressed because it is too large
Load diff
39
ed25519/sign.py
Normal file
39
ed25519/sign.py
Normal 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)
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@
|
|||
Ensure that TUF meets expectations about target delegations.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import os
|
||||
import time
|
||||
import tempfile
|
||||
|
|
|
|||
|
|
@ -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!')
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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!')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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]),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
12
tuf/conf.py
12
tuf/conf.py
|
|
@ -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
618
tuf/ed25519_key.py
Executable 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()
|
||||
|
|
@ -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).
|
||||
|
|
|
|||
40
tuf/log.py
40
tuf/log.py
|
|
@ -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.')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
32
tuf/time_ed25519.py
Executable 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))
|
||||
Loading…
Reference in a new issue