Merge and resolve conflicts

This commit is contained in:
Vladimir 2013-11-15 10:09:59 -05:00
commit 3ca6261f79
44 changed files with 6766 additions and 1939 deletions

1
ed25519/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.pyc

28
ed25519/.travis.yml Normal file
View file

@ -0,0 +1,28 @@
language: python
python: 2.7
env:
- TOXENV=py26
- TOXENV=py27
#- TOXENV=py32
#- TOXENV=py33
- TOXENV=pypy
install:
# Add the PyPy repository
- "if [[ $TOXENV == 'pypy' ]]; then sudo add-apt-repository -y ppa:pypy/ppa; fi"
# Upgrade PyPy
- "if [[ $TOXENV == 'pypy' ]]; then sudo apt-get -y install pypy; fi"
# This is required because we need to get rid of the Travis installed PyPy
# or it'll take precedence over the PPA installed one.
- "if [[ $TOXENV == 'pypy' ]]; then sudo rm -rf /usr/local/pypy/bin; fi"
- pip install tox
script:
- tox
notifications:
irc:
channels:
- "irc.freenode.org#cryptography-dev"
use_notice: true
skip_join: true

0
ed25519/__init__.py Executable file → Normal file
View file

199
ed25519/ed25519.py Executable file → Normal file
View file

@ -1,104 +1,161 @@
import hashlib
b = 256
q = 2**255 - 19
l = 2**252 + 27742317777372353535851937790883648493
q = 2 ** 255 - 19
l = 2 ** 252 + 27742317777372353535851937790883648493
def H(m):
return hashlib.sha512(m).digest()
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)
def pow2(x, p):
"""== pow(x, 2**p, q)"""
while p > 0:
x = x * x % q
p -= 1
return x
def inv(z):
"""$= z^{-1} \mod q$, for z != 0"""
# Adapted from curve25519_athlon.c in djb's Curve25519.
z2 = z * z % q # 2
z9 = pow2(z2, 2) * z % q # 9
z11 = z9 * z2 % q # 11
z2_5_0 = (z11*z11)%q * z9 % q # 31 == 2^5 - 2^0
z2_10_0 = pow2(z2_5_0, 5) * z2_5_0 % q # 2^10 - 2^0
z2_20_0 = pow2(z2_10_0, 10) * z2_10_0 % q # ...
z2_40_0 = pow2(z2_20_0, 20) * z2_20_0 % q
z2_50_0 = pow2(z2_40_0, 10) * z2_10_0 % q
z2_100_0 = pow2(z2_50_0, 50) * z2_50_0 % q
z2_200_0 = pow2(z2_100_0, 100) * z2_100_0 % q
z2_250_0 = pow2(z2_200_0, 50) * z2_50_0 % q # 2^250 - 2^0
return pow2(z2_250_0, 5) * z11 % q # 2^255 - 2^5 + 11 = q - 2
d = -121665 * inv(121666)
I = expmod(2,(q-1)/4,q)
I = pow(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
xx = (y * y - 1) * inv(d * y * y + 1)
x = pow(xx, (q + 3) / 8, q)
if (x * x - xx) % q != 0:
x = (x * I) % q
if x % 2 != 0:
x = q-x
return x
By = 4 * inv(5)
Bx = xrecover(By)
B = [Bx % q,By % q]
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 edwards(P, Q):
x1, y1 = P
x2, y2 = Q
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)])
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)])
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 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)
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))
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[j] for j in range(b / 8, b / 4)]) + m)
R = scalarmult(B, r)
S = (r + Hint(encodepoint(R) + pk + m) * a) % l
return encodepoint(R) + encodeint(S)
def 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
x, y = P
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))
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
y = sum(2 ** i * bit(s, i) for i in range(0, b - 1))
x = xrecover(y)
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")
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[: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")

8
ed25519/runtests.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/sh
set -e
python -u signfast.py < sign.input
if [[ $TEST == 'slow' ]]; then
python -u sign.py < sign.input
fi

32
ed25519/science.py Normal file
View file

@ -0,0 +1,32 @@
import os
import timeit
import ed25519
seed = os.urandom(32)
data = "The quick brown fox jumps over the lazy dog"
private_key = seed
public_key = ed25519.publickey(seed)
signature = ed25519.signature(data, private_key, public_key)
print('Time generate')
print(timeit.timeit("ed25519.publickey(seed)",
setup="from __main__ import ed25519, seed",
number=10,
))
print('\nTime create signature')
print(timeit.timeit("ed25519.signature(data, private_key, public_key)",
setup="from __main__ import ed25519, data, private_key, public_key",
number=10,
))
print('\nTime verify signature')
print(timeit.timeit("ed25519.checkvalid(signature, data, public_key)",
setup="from __main__ import ed25519, signature, data, public_key",
number=10,
))

11
ed25519/setup.py Normal file
View file

@ -0,0 +1,11 @@
from setuptools import setup
setup(
name="ed25519",
version="1.0",
py_modules="ed25519",
zip_safe=False,
)

View file

@ -1,3 +1,5 @@
from __future__ import print_function
import sys
import binascii
import ed25519
@ -16,6 +18,7 @@
while 1:
line = sys.stdin.readline()
if not line: break
print(".", end="")
x = line.split(':')
sk = binascii.unhexlify(x[0][0:64])
pk = ed25519.publickey(sk)

48
ed25519/signfast.py Normal file
View file

@ -0,0 +1,48 @@
from __future__ import print_function
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
MAX = 10
i = 0
while 1:
if i >= MAX:
break
i += 1
line = sys.stdin.readline()
if not line: break
print(".", end="")
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)

5
ed25519/tox.ini Normal file
View file

@ -0,0 +1,5 @@
[tox]
envlist = py26,py27,pypy,py32,py33
[testenv]
commands = ./runtests.sh

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -69,6 +69,7 @@
url='https://www.updateframework.com',
install_requires=['pycrypto>=2.6'],
packages=[
'ed25519',
'tuf',
'tuf.client',
'tuf.compatibility',
@ -82,6 +83,7 @@
'tuf/repo/quickstart.py',
'tuf/pushtools/push.py',
'tuf/pushtools/receivetools/receive.py',
'tuf/repo/signercli.py'
'tuf/repo/signercli.py',
'tuf/client/basic_client.py'
]
)

123
tests/unit/test_ed25519_keys.py Executable file
View file

@ -0,0 +1,123 @@
"""
<Program Name>
test_ed25519_keys.py
<Author>
Vladimir Diaz
<Started>
October 11, 2013.
<Copyright>
See LICENSE for licensing information.
<Purpose>
Test cases for test_ed25519_keys.py.
"""
import unittest
import logging
import tuf
import tuf.log
import tuf.formats
import tuf.ed25519_keys as ed25519
logger = logging.getLogger('tuf.test_ed25519_keys')
public, private = ed25519.generate_public_and_private()
FORMAT_ERROR_MSG = 'tuf.FormatError raised. Check object\'s format.'
class TestEd25519_keys(unittest.TestCase):
def setUp(self):
pass
def test_generate_public_and_private(self):
pub, priv = ed25519.generate_public_and_private()
# Check format of 'pub' and 'priv'.
self.assertEqual(True, tuf.formats.ED25519PUBLIC_SCHEMA.matches(pub))
self.assertEqual(True, tuf.formats.ED25519SEED_SCHEMA.matches(priv))
# Check for invalid argument.
self.assertRaises(tuf.FormatError,
ed25519.generate_public_and_private, 'True')
self.assertRaises(tuf.FormatError,
ed25519.generate_public_and_private, 2048)
def test_create_signature(self):
global public
global private
data = 'The quick brown fox jumps over the lazy dog'
signature, method = ed25519.create_signature(public, private, data)
# Verify format of returned values.
self.assertEqual(True,
tuf.formats.ED25519SIGNATURE_SCHEMA.matches(signature))
self.assertEqual(True, tuf.formats.NAME_SCHEMA.matches(method))
self.assertEqual('ed25519-python', method)
# Check for improperly formatted argument.
self.assertRaises(tuf.FormatError,
ed25519.create_signature, 123, private, data)
self.assertRaises(tuf.FormatError,
ed25519.create_signature, public, 123, data)
# Check for invalid 'data'.
self.assertRaises(tuf.CryptoError,
ed25519.create_signature, public, private, 123)
def test_verify_signature(self):
global public
global private
data = 'The quick brown fox jumps over the lazy dog'
signature, method = ed25519.create_signature(public, private, data)
valid_signature = ed25519.verify_signature(public, method, signature, data)
self.assertEqual(True, valid_signature)
# Check for improperly formatted arguments.
self.assertRaises(tuf.FormatError, ed25519.verify_signature, 123, method,
signature, data)
# Signature method improperly formatted.
self.assertRaises(tuf.FormatError, ed25519.verify_signature, public, 123,
signature, data)
# Signature not a string.
self.assertRaises(tuf.FormatError, ed25519.verify_signature, public, method,
123, data)
# Invalid signature length, which must be exactly 64 bytes..
self.assertRaises(tuf.FormatError, ed25519.verify_signature, public, method,
'bad_signature', data)
# Check for invalid signature and data.
# Mismatched data.
self.assertEqual(False, ed25519.verify_signature(public, method,
signature, '123'))
# Mismatched signature.
bad_signature = 'a'*64
self.assertEqual(False, ed25519.verify_signature(public, method,
bad_signature, data))
# Generated signature created with different data.
new_signature, method = ed25519.create_signature(public, private,
'mismatched data')
self.assertEqual(False, ed25519.verify_signature(public, method,
new_signature, data))
# Run the unit tests.
if __name__ == '__main__':
unittest.main()

View file

@ -13,7 +13,6 @@
<Purpose>
Unit test for 'keydb.py'.
"""
import unittest
@ -21,7 +20,7 @@
import tuf
import tuf.formats
import tuf.rsa_key
import tuf.keys
import tuf.keydb
import tuf.log
@ -31,7 +30,7 @@
# Generate the three keys to use in our test cases.
KEYS = []
for junk in range(3):
KEYS.append(tuf.rsa_key.generate(2048))
KEYS.append(tuf.keys.generate_rsa_key(2048))
@ -89,7 +88,7 @@ def test_get_key(self):
def test_add_rsakey(self):
def test_add_key(self):
# Test conditions using valid 'keyid' arguments.
rsakey = KEYS[0]
keyid = KEYS[0]['keyid']
@ -97,9 +96,9 @@ def test_add_rsakey(self):
keyid2 = KEYS[1]['keyid']
rsakey3 = KEYS[2]
keyid3 = KEYS[2]['keyid']
self.assertEqual(None, tuf.keydb.add_rsakey(rsakey, keyid))
self.assertEqual(None, tuf.keydb.add_rsakey(rsakey2, keyid2))
self.assertEqual(None, tuf.keydb.add_rsakey(rsakey3))
self.assertEqual(None, tuf.keydb.add_key(rsakey, keyid))
self.assertEqual(None, tuf.keydb.add_key(rsakey2, keyid2))
self.assertEqual(None, tuf.keydb.add_key(rsakey3))
self.assertEqual(rsakey, tuf.keydb.get_key(keyid))
self.assertEqual(rsakey2, tuf.keydb.get_key(keyid2))
@ -109,26 +108,26 @@ def test_add_rsakey(self):
tuf.keydb.clear_keydb()
rsakey3['keytype'] = 'bad_keytype'
self.assertRaises(tuf.FormatError, tuf.keydb.add_rsakey, None, keyid)
self.assertRaises(tuf.FormatError, tuf.keydb.add_rsakey, '', keyid)
self.assertRaises(tuf.FormatError, tuf.keydb.add_rsakey, ['123'], keyid)
self.assertRaises(tuf.FormatError, tuf.keydb.add_rsakey, {'a': 'b'}, keyid)
self.assertRaises(tuf.FormatError, tuf.keydb.add_rsakey, rsakey, {'keyid': ''})
self.assertRaises(tuf.FormatError, tuf.keydb.add_rsakey, rsakey, 123)
self.assertRaises(tuf.FormatError, tuf.keydb.add_rsakey, rsakey, False)
self.assertRaises(tuf.FormatError, tuf.keydb.add_rsakey, rsakey, ['keyid'])
self.assertRaises(tuf.FormatError, tuf.keydb.add_rsakey, rsakey3, keyid3)
self.assertRaises(tuf.FormatError, tuf.keydb.add_key, None, keyid)
self.assertRaises(tuf.FormatError, tuf.keydb.add_key, '', keyid)
self.assertRaises(tuf.FormatError, tuf.keydb.add_key, ['123'], keyid)
self.assertRaises(tuf.FormatError, tuf.keydb.add_key, {'a': 'b'}, keyid)
self.assertRaises(tuf.FormatError, tuf.keydb.add_key, rsakey, {'keyid': ''})
self.assertRaises(tuf.FormatError, tuf.keydb.add_key, rsakey, 123)
self.assertRaises(tuf.FormatError, tuf.keydb.add_key, rsakey, False)
self.assertRaises(tuf.FormatError, tuf.keydb.add_key, rsakey, ['keyid'])
self.assertRaises(tuf.FormatError, tuf.keydb.add_key, rsakey3, keyid3)
rsakey3['keytype'] = 'rsa'
# Test conditions where keyid does not match the rsakey.
self.assertRaises(tuf.Error, tuf.keydb.add_rsakey, rsakey, keyid2)
self.assertRaises(tuf.Error, tuf.keydb.add_rsakey, rsakey2, keyid)
self.assertRaises(tuf.Error, tuf.keydb.add_key, rsakey, keyid2)
self.assertRaises(tuf.Error, tuf.keydb.add_key, rsakey2, keyid)
# Test conditions using keyids that have already been added.
tuf.keydb.add_rsakey(rsakey, keyid)
tuf.keydb.add_rsakey(rsakey2, keyid2)
self.assertRaises(tuf.KeyAlreadyExistsError, tuf.keydb.add_rsakey, rsakey)
self.assertRaises(tuf.KeyAlreadyExistsError, tuf.keydb.add_rsakey, rsakey2)
tuf.keydb.add_key(rsakey, keyid)
tuf.keydb.add_key(rsakey2, keyid2)
self.assertRaises(tuf.KeyAlreadyExistsError, tuf.keydb.add_key, rsakey)
self.assertRaises(tuf.KeyAlreadyExistsError, tuf.keydb.add_key, rsakey2)
@ -140,12 +139,13 @@ def test_remove_key(self):
keyid2 = KEYS[1]['keyid']
rsakey3 = KEYS[2]
keyid3 = KEYS[2]['keyid']
tuf.keydb.add_rsakey(rsakey, keyid)
tuf.keydb.add_rsakey(rsakey2, keyid2)
tuf.keydb.add_rsakey(rsakey3, keyid3)
tuf.keydb.add_key(rsakey, keyid)
tuf.keydb.add_key(rsakey2, keyid2)
tuf.keydb.add_key(rsakey3, keyid3)
self.assertEqual(None, tuf.keydb.remove_key(keyid))
self.assertEqual(None, tuf.keydb.remove_key(keyid2))
# Ensure the keys were actually removed.
self.assertRaises(tuf.UnknownKeyError, tuf.keydb.get_key, keyid)
self.assertRaises(tuf.UnknownKeyError, tuf.keydb.get_key, keyid2)

193
tests/unit/test_keys.py Executable file
View file

@ -0,0 +1,193 @@
"""
<Program Name>
test_keys.py
<Author>
Vladimir Diaz
<Started>
October 10, 2013.
<Copyright>
See LICENSE for licensing information.
<Purpose>
Test cases for test_keys.py.
TODO: test case for ed25519 key generation and refactor.
"""
import unittest
import logging
import tuf
import tuf.log
import tuf.formats
import tuf.keys
logger = logging.getLogger('tuf.test_keys')
KEYS = tuf.keys
FORMAT_ERROR_MSG = 'tuf.FormatError was raised! Check object\'s format.'
DATA = 'SOME DATA REQUIRING AUTHENTICITY.'
rsakey_dict = KEYS.generate_rsa_key()
temp_key_info_vals = rsakey_dict.values()
temp_key_vals = rsakey_dict['keyval'].values()
class TestKeys(unittest.TestCase):
def setUp(self):
rsakey_dict['keytype']=temp_key_info_vals[0]
rsakey_dict['keyid']=temp_key_info_vals[1]
rsakey_dict['keyval']=temp_key_info_vals[2]
rsakey_dict['keyval']['public']=temp_key_vals[0]
rsakey_dict['keyval']['private']=temp_key_vals[1]
def test_generate_rsa_key(self):
_rsakey_dict = KEYS.generate_rsa_key()
# Check if the format of the object returned by generate() corresponds
# to RSAKEY_SCHEMA format.
self.assertEqual(None, tuf.formats.RSAKEY_SCHEMA.check_match(_rsakey_dict),
FORMAT_ERROR_MSG)
# Passing a bit value that is <2048 to generate() - should raise
# 'tuf.FormatError'.
self.assertRaises(tuf.FormatError, KEYS.generate_rsa_key, 555)
# Passing a string instead of integer for a bit value.
self.assertRaises(tuf.FormatError, KEYS.generate_rsa_key, 'bits')
# NOTE if random bit value >=2048 (not 4096) is passed generate(bits)
# does not raise any errors and returns a valid key.
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(KEYS.generate_rsa_key(2048)))
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(KEYS.generate_rsa_key(4096)))
def test_format_keyval_to_metadata(self):
keyvalue = rsakey_dict['keyval']
keytype = rsakey_dict['keytype']
key_meta = KEYS.format_keyval_to_metadata(keytype, keyvalue)
# Check if the format of the object returned by this function corresponds
# to KEY_SCHEMA format.
self.assertEqual(None,
tuf.formats.KEY_SCHEMA.check_match(key_meta),
FORMAT_ERROR_MSG)
key_meta = KEYS.format_keyval_to_metadata(keytype, keyvalue, private=True)
# Check if the format of the object returned by this function corresponds
# to KEY_SCHEMA format.
self.assertEqual(None, tuf.formats.KEY_SCHEMA.check_match(key_meta),
FORMAT_ERROR_MSG)
# Supplying a 'bad' keyvalue.
self.assertRaises(tuf.FormatError, KEYS.format_keyval_to_metadata,
'bad_keytype', keyvalue)
del keyvalue['public']
self.assertRaises(tuf.FormatError, KEYS.format_keyval_to_metadata,
keytype, keyvalue)
def test_format_metadata_to_key(self):
# Reconfiguring rsakey_dict to conform to KEY_SCHEMA
# i.e. {keytype: 'rsa', keyval: {public: pub_key, private: priv_key}}
#keyid = rsakey_dict['keyid']
del rsakey_dict['keyid']
rsakey_dict_from_meta = KEYS.format_metadata_to_key(rsakey_dict)
# Check if the format of the object returned by this function corresponds
# to RSAKEY_SCHEMA format.
self.assertEqual(None,
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict_from_meta),
FORMAT_ERROR_MSG)
# Supplying a wrong number of arguments.
self.assertRaises(TypeError, KEYS.format_metadata_to_key)
args = (rsakey_dict, rsakey_dict)
self.assertRaises(TypeError, KEYS.format_metadata_to_key, *args)
# Supplying a malformed argument to the function - should get FormatError
del rsakey_dict['keyval']
self.assertRaises(tuf.FormatError, KEYS.format_metadata_to_key,
rsakey_dict)
def test_helper_get_keyid(self):
keytype = rsakey_dict['keytype']
keyvalue = rsakey_dict['keyval']
# Check format of 'keytype'.
self.assertEqual(None, tuf.formats.KEYTYPE_SCHEMA.check_match(keytype),
FORMAT_ERROR_MSG)
# Check format of 'keyvalue'.
self.assertEqual(None, tuf.formats.KEYVAL_SCHEMA.check_match(keyvalue),
FORMAT_ERROR_MSG)
keyid = KEYS._get_keyid(keytype, keyvalue)
# Check format of 'keyid' - the output of '_get_keyid()' function.
self.assertEqual(None, tuf.formats.KEYID_SCHEMA.check_match(keyid),
FORMAT_ERROR_MSG)
def test_create_signature(self):
# Creating a signature for 'DATA'.
signature = KEYS.create_signature(rsakey_dict, DATA)
# Check format of output.
self.assertEqual(None,
tuf.formats.SIGNATURE_SCHEMA.check_match(signature),
FORMAT_ERROR_MSG)
# Removing private key from 'rsakey_dict' - should raise a TypeError.
rsakey_dict['keyval']['private'] = ''
args = (rsakey_dict, DATA)
self.assertRaises(TypeError, KEYS.create_signature, *args)
# Supplying an incorrect number of arguments.
self.assertRaises(TypeError, KEYS.create_signature)
def test_verify_signature(self):
# Creating a signature 'signature' of 'DATA' to be verified.
signature = KEYS.create_signature(rsakey_dict, DATA)
# Verifying the 'signature' of 'DATA'.
verified = KEYS.verify_signature(rsakey_dict, signature, DATA)
self.assertTrue(verified, "Incorrect signature.")
# Testing an invalid 'signature'. Same 'signature' is passed, with
# 'DATA' different than the original 'DATA' that was used
# in creating the 'signature'. Function should return 'False'.
# Modifying 'DATA'.
_DATA = '1111'+DATA+'1111'
# Verifying the 'signature' of modified '_DATA'.
verified = KEYS.verify_signature(rsakey_dict, signature, _DATA)
self.assertFalse(verified,
'Returned \'True\' on an incorrect signature.')
# Modifying 'signature' to pass an incorrect method since only
# 'PyCrypto-PKCS#1 PSS'
# is accepted.
signature['method'] = 'Biff'
args = (rsakey_dict, signature, DATA)
self.assertRaises(tuf.UnknownMethodError, KEYS.verify_signature, *args)
# Passing incorrect number of arguments.
self.assertRaises(TypeError, KEYS.verify_signature)
# Run the unit tests.
if __name__ == '__main__':
unittest.main()

View file

@ -13,7 +13,6 @@
<Purpose>
Unit test for keystore.py.
"""
import unittest
@ -25,7 +24,7 @@
import tuf
import tuf.repo.keystore
import tuf.rsa_key
import tuf.keys
import tuf.formats
import tuf.util
import tuf.log
@ -56,7 +55,7 @@
for i in range(3):
# Populating the original 'RSAKEYS' and 'PASSWDS' lists.
RSAKEYS.append(tuf.rsa_key.generate())
RSAKEYS.append(tuf.keys.generate_rsa_key())
PASSWDS.append('passwd_'+str(i))
# Saving original copies of 'RSAKEYS' and 'PASSWDS' to temp variables
@ -350,6 +349,7 @@ def tearDownModule():
tuf.repo.keystore.clear_keystore()
# Run the unit tests.
if __name__ == '__main__':
unittest.main()

198
tests/unit/test_pycrypto_keys.py Executable file
View file

@ -0,0 +1,198 @@
"""
<Program Name>
test_pycrypto_keys.py
<Author>
Vladimir Diaz
<Started>
October 10, 2013.
<Copyright>
See LICENSE for licensing information.
<Purpose>
Test cases for test_pycrypto_keys.py.
"""
import unittest
import logging
import tuf
import tuf.log
import tuf.formats
import tuf.pycrypto_keys as pycrypto
logger = logging.getLogger('tuf.test_pycrypto_keys')
public_rsa, private_rsa = pycrypto.generate_rsa_public_and_private()
FORMAT_ERROR_MSG = 'tuf.FormatError raised. Check object\'s format.'
class TestPycrypto_keys(unittest.TestCase):
def setUp(self):
pass
def test_generate_rsa_public_and_private(self):
pub, priv = pycrypto.generate_rsa_public_and_private()
# Check format of 'pub' and 'priv'.
self.assertEqual(None, tuf.formats.PEMRSA_SCHEMA.check_match(pub),
FORMAT_ERROR_MSG)
self.assertEqual(None, tuf.formats.PEMRSA_SCHEMA.check_match(priv),
FORMAT_ERROR_MSG)
# Check for invalid bits argument. bit >= 2048 and a multiple of 256.
self.assertRaises(tuf.FormatError,
pycrypto.generate_rsa_public_and_private, 1024)
self.assertRaises(ValueError,
pycrypto.generate_rsa_public_and_private, 2049)
self.assertRaises(tuf.FormatError,
pycrypto.generate_rsa_public_and_private, '2048')
def test_create_rsa_signature(self):
global private_rsa
data = 'The quick brown fox jumps over the lazy dog'
signature, method = pycrypto.create_rsa_signature(private_rsa, data)
# Verify format of returned values.
self.assertNotEqual(None, signature)
self.assertEqual(None, tuf.formats.NAME_SCHEMA.check_match(method),
FORMAT_ERROR_MSG)
self.assertEqual('PyCrypto-PKCS#1 PSS', method)
# Check for improperly formatted argument.
self.assertRaises(tuf.FormatError,
pycrypto.create_rsa_signature, 123, data)
# Check for invalid 'data'.
self.assertRaises(tuf.CryptoError,
pycrypto.create_rsa_signature, private_rsa, 123)
def test_verify_rsa_signature(self):
global public_rsa
global private_rsa
data = 'The quick brown fox jumps over the lazy dog'
signature, method = pycrypto.create_rsa_signature(private_rsa, data)
valid_signature = pycrypto.verify_rsa_signature(signature, method, public_rsa,
data)
self.assertEqual(True, valid_signature)
# Check for improperly formatted arguments.
self.assertRaises(tuf.FormatError, pycrypto.verify_rsa_signature, signature,
123, public_rsa, data)
self.assertRaises(tuf.FormatError, pycrypto.verify_rsa_signature, signature,
method, 123, data)
self.assertRaises(tuf.FormatError, pycrypto.verify_rsa_signature, 123, method,
public_rsa, data)
# Check for invalid signature and data.
self.assertRaises(tuf.CryptoError, pycrypto.verify_rsa_signature, signature,
method, public_rsa, 123)
self.assertEqual(False, pycrypto.verify_rsa_signature(signature, method,
public_rsa, 'mismatched data'))
mismatched_signature, method = pycrypto.create_rsa_signature(private_rsa,
'mismatched data')
self.assertEqual(False, pycrypto.verify_rsa_signature(mismatched_signature,
method, public_rsa, data))
def test_create_rsa_encrypted_pem(self):
global public_rsa
global private_rsa
passphrase = 'pw'
# Check format of 'public_rsa'.
self.assertEqual(None, tuf.formats.PEMRSA_SCHEMA.check_match(public_rsa),
FORMAT_ERROR_MSG)
# Check format of 'passphrase'.
self.assertEqual(None, tuf.formats.PASSWORD_SCHEMA.check_match(passphrase),
FORMAT_ERROR_MSG)
# Generate the encrypted PEM string of 'public_rsa'.
pem_rsakey = pycrypto.create_rsa_encrypted_pem(private_rsa, passphrase)
# Check format of 'pem_rsakey'.
self.assertEqual(None, tuf.formats.PEMRSA_SCHEMA.check_match(pem_rsakey),
FORMAT_ERROR_MSG)
# Check for invalid arguments.
self.assertRaises(tuf.FormatError,
pycrypto.create_rsa_encrypted_pem, 1, passphrase)
self.assertRaises(tuf.FormatError,
pycrypto.create_rsa_encrypted_pem, private_rsa, ['pw'])
def test_create_rsa_public_and_private_from_encrypted_pem(self):
global private_rsa
passphrase = 'pw'
# Generate the encrypted PEM string of 'private_rsa'.
pem_rsakey = pycrypto.create_rsa_encrypted_pem(private_rsa, passphrase)
# Check format of 'passphrase'.
self.assertEqual(None, tuf.formats.PASSWORD_SCHEMA.check_match(passphrase),
FORMAT_ERROR_MSG)
# Decrypt 'pem_rsakey' and verify the decrypted object is properly
# formatted.
public_decrypted, private_decrypted = \
pycrypto.create_rsa_public_and_private_from_encrypted_pem(pem_rsakey,
passphrase)
self.assertEqual(None,
tuf.formats.PEMRSA_SCHEMA.check_match(public_decrypted),
FORMAT_ERROR_MSG)
self.assertEqual(None,
tuf.formats.PEMRSA_SCHEMA.check_match(private_decrypted),
FORMAT_ERROR_MSG)
# Does 'public_decrypted' and 'private_decrypted' match the originals?
self.assertEqual(public_rsa, public_decrypted)
self.assertEqual(private_rsa, private_decrypted)
# Attempt decryption of 'pem_rsakey' using an incorrect passphrase.
self.assertRaises(tuf.CryptoError,
pycrypto.create_rsa_public_and_private_from_encrypted_pem,
pem_rsakey, 'bad_pw')
# Check for non-encrypted PEM strings.
# create_rsa_public_and_private_from_encrypted_pem()
# returns a tuple of tuf.formats.PEMRSA_SCHEMA objects if the PEM formatted
# string is not actually encrypted but still a valid PEM string.
pub, priv = pycrypto.create_rsa_public_and_private_from_encrypted_pem(
private_rsa, passphrase)
self.assertEqual(None, tuf.formats.PEMRSA_SCHEMA.check_match(pub),
FORMAT_ERROR_MSG)
self.assertEqual(None, tuf.formats.PEMRSA_SCHEMA.check_match(priv),
FORMAT_ERROR_MSG)
# Check for invalid arguments.
self.assertRaises(tuf.FormatError,
pycrypto.create_rsa_public_and_private_from_encrypted_pem,
123, passphrase)
self.assertRaises(tuf.FormatError,
pycrypto.create_rsa_public_and_private_from_encrypted_pem,
pem_rsakey, ['pw'])
self.assertRaises(tuf.CryptoError,
pycrypto.create_rsa_public_and_private_from_encrypted_pem,
'invalid_pem', passphrase)
# Run the unit tests.
if __name__ == '__main__':
unittest.main()

View file

@ -18,7 +18,6 @@
Given that all message prompts don't change - this will work pretty well
for running quickstart without having to manually enter input to prompts
every time you want to run quickstart.
"""
import os

View file

@ -13,7 +13,6 @@
<Purpose>
Unit test for 'roledb.py'.
"""
@ -22,7 +21,7 @@
import tuf
import tuf.formats
import tuf.rsa_key
import tuf.keys
import tuf.roledb
import tuf.log
@ -32,7 +31,7 @@
# Generate the three keys to use in our test cases.
KEYS = []
for junk in range(3):
KEYS.append(tuf.rsa_key.generate(2048))
KEYS.append(tuf.keys.generate_rsa_key(2048))

View file

@ -1,258 +0,0 @@
"""
<Program Name>
test_rsa_key.py
<Author>
Konstantin Andrianov
<Started>
April 24, 2012.
<Copyright>
See LICENSE for licensing information.
<Purpose>
Test cases for rsa_key.py.
<Notes>
I'm using 'global rsakey_dict' - there is no harm in doing so since
in order to modify the global variable in any method, python requires
explicit indication to modify i.e. declaring 'global' in each method
that modifies the global variable 'rsakey_dict'.
"""
import unittest
import logging
import tuf
import tuf.log
import tuf.formats
import tuf.rsa_key
logger = logging.getLogger('tuf.test_rsa_key')
RSA_KEY = tuf.rsa_key
FORMAT_ERROR_MSG = 'tuf.FormatError was raised! Check object\'s format.'
DATA = 'SOME DATA REQUIRING AUTHENTICITY.'
rsakey_dict = RSA_KEY.generate()
temp_key_info_vals = rsakey_dict.values()
temp_key_vals = rsakey_dict['keyval'].values()
class TestRsa_key(unittest.TestCase):
def setUp(self):
rsakey_dict['keytype']=temp_key_info_vals[0]
rsakey_dict['keyid']=temp_key_info_vals[1]
rsakey_dict['keyval']=temp_key_info_vals[2]
rsakey_dict['keyval']['public']=temp_key_vals[0]
rsakey_dict['keyval']['private']=temp_key_vals[1]
def test_generate(self):
_rsakey_dict = RSA_KEY.generate()
# Check if the format of the object returned by generate() corresponds
# to RSAKEY_SCHEMA format.
self.assertEqual(None, tuf.formats.RSAKEY_SCHEMA.check_match(_rsakey_dict),
FORMAT_ERROR_MSG)
# Passing a bit value that is <2048 to generate() - should raise
# 'tuf.FormatError'.
self.assertRaises(tuf.FormatError, RSA_KEY.generate, 555)
# Passing a string instead of integer for a bit value.
self.assertRaises(tuf.FormatError, RSA_KEY.generate, 'bits')
# NOTE if random bit value >=2048 (not 4096) is passed generate(bits)
# does not raise any errors and returns a valid key.
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(RSA_KEY.generate(2048)))
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(RSA_KEY.generate(4096)))
def test_create_in_metadata_format(self):
key_value = rsakey_dict['keyval']
key_meta = RSA_KEY.create_in_metadata_format(key_value)
# Check if the format of the object returned by this function corresponds
# to KEY_SCHEMA format.
self.assertEqual(None,
tuf.formats.KEY_SCHEMA.check_match(key_meta),
FORMAT_ERROR_MSG)
key_meta = RSA_KEY.create_in_metadata_format(key_value, private=True)
# Check if the format of the object returned by this function corresponds
# to KEY_SCHEMA format.
self.assertEqual(None, tuf.formats.KEY_SCHEMA.check_match(key_meta),
FORMAT_ERROR_MSG)
# Supplying a 'bad' key_value.
del key_value['public']
self.assertRaises(tuf.FormatError, RSA_KEY.create_in_metadata_format,
key_value)
def test_create_from_metadata_format(self):
# Reconfiguring rsakey_dict to conform to KEY_SCHEMA
# i.e. {keytype: 'rsa', keyval: {public: pub_key, private: priv_key}}
#keyid = rsakey_dict['keyid']
del rsakey_dict['keyid']
rsakey_dict_from_meta = RSA_KEY.create_from_metadata_format(rsakey_dict)
# Check if the format of the object returned by this function corresponds
# to RSAKEY_SCHEMA format.
self.assertEqual(None,
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict_from_meta),
FORMAT_ERROR_MSG)
# Supplying a wrong number of arguments.
self.assertRaises(TypeError, RSA_KEY.create_from_metadata_format)
args = (rsakey_dict, rsakey_dict)
self.assertRaises(TypeError, RSA_KEY.create_from_metadata_format, *args)
# Supplying a malformed argument to the function - should get FormatError
del rsakey_dict['keyval']
self.assertRaises(tuf.FormatError, RSA_KEY.create_from_metadata_format,
rsakey_dict)
def test_helper_get_keyid(self):
key_value = rsakey_dict['keyval']
# Check format of 'key_value'.
self.assertEqual(None, tuf.formats.KEYVAL_SCHEMA.check_match(key_value),
FORMAT_ERROR_MSG)
keyid = RSA_KEY._get_keyid(key_value)
# Check format of 'keyid' - the output of '_get_keyid()' function.
self.assertEqual(None, tuf.formats.KEYID_SCHEMA.check_match(keyid),
FORMAT_ERROR_MSG)
def test_createsignature(self):
# Creating a signature for 'DATA'.
signature = RSA_KEY.create_signature(rsakey_dict, DATA)
# Check format of output.
self.assertEqual(None,
tuf.formats.SIGNATURE_SCHEMA.check_match(signature),
FORMAT_ERROR_MSG)
# Removing private key from 'rsakey_dict' - should raise a TypeError.
rsakey_dict['keyval']['private'] = ''
args = (rsakey_dict, DATA)
self.assertRaises(TypeError, RSA_KEY.create_signature, *args)
# Supplying an incorrect number of arguments.
self.assertRaises(TypeError, RSA_KEY.create_signature)
def test_verify_signature(self):
# Creating a signature 'signature' of 'DATA' to be verified.
signature = RSA_KEY.create_signature(rsakey_dict, DATA)
# Verifying the 'signature' of 'DATA'.
verified = RSA_KEY.verify_signature(rsakey_dict, signature, DATA)
self.assertTrue(verified, "Incorrect signature.")
# Testing an invalid 'signature'. Same 'signature' is passed, with
# 'DATA' different than the original 'DATA' that was used
# in creating the 'signature'. Function should return 'False'.
# Modifying 'DATA'.
_DATA = '1111'+DATA+'1111'
# Verifying the 'signature' of modified '_DATA'.
verified = RSA_KEY.verify_signature(rsakey_dict, signature, _DATA)
self.assertFalse(verified,
'Returned \'True\' on an incorrect signature.')
# Modifying 'signature' to pass an incorrect method since only
# 'PyCrypto-PKCS#1 PSS'
# is accepted.
signature['method'] = 'Biff'
args = (rsakey_dict, signature, DATA)
self.assertRaises(tuf.UnknownMethodError, RSA_KEY.verify_signature, *args)
# Passing incorrect number of arguments.
self.assertRaises(TypeError,RSA_KEY.verify_signature)
def test_create_encrypted_pem(self):
passphrase = 'pw'
# Check format of 'rsakey_dict'.
self.assertEqual(None, tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict),
FORMAT_ERROR_MSG)
# Check format of 'passphrase'.
self.assertEqual(None, tuf.formats.PASSWORD_SCHEMA.check_match(passphrase),
FORMAT_ERROR_MSG)
# Generate the encrypted PEM string of 'rsakey_dict'.
pem_rsakey = tuf.rsa_key.create_encrypted_pem(rsakey_dict, passphrase)
# Check for invalid arguments.
self.assertRaises(tuf.FormatError,
tuf.rsa_key.create_encrypted_pem, 'Biff', passphrase)
self.assertRaises(tuf.FormatError,
tuf.rsa_key.create_encrypted_pem, rsakey_dict, ['pw'])
def test_create_from_encrypted_pem(self):
passphrase = 'pw'
# Check format of 'rsakey_dict'.
self.assertEqual(None, tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict),
FORMAT_ERROR_MSG)
# Check format of 'passphrase'.
self.assertEqual(None, tuf.formats.PASSWORD_SCHEMA.check_match(passphrase),
FORMAT_ERROR_MSG)
# Generate the encrypted PEM string of 'rsakey_dict'.
pem_rsakey = tuf.rsa_key.create_encrypted_pem(rsakey_dict, passphrase)
# Decrypt 'pem_rsakey' and verify the decrypted object is properly
# formatted.
decrypted_rsakey = tuf.rsa_key.create_from_encrypted_pem(pem_rsakey,
passphrase)
self.assertEqual(None, tuf.formats.RSAKEY_SCHEMA.check_match(decrypted_rsakey),
FORMAT_ERROR_MSG)
# Does 'decrypted_rsakey' match the original 'rsakey_dict'.
self.assertEqual(rsakey_dict, decrypted_rsakey)
# Attempt decryption of 'pem_rsakey' using an incorrect passphrase.
self.assertRaises(tuf.CryptoError,
tuf.rsa_key.create_from_encrypted_pem, pem_rsakey,
'bad_pw')
# Check for non-encrypted PEM string. create_from_encrypted_pem()/PyCrypto
# returns a tuf.formats.RSAKEY_SCHEMA object if PEM formatted string is
# not actually encrypted but still a valid PEM string.
non_encrypted_private_key = rsakey_dict['keyval']['private']
decrypted_non_encrypted = tuf.rsa_key.create_from_encrypted_pem(
non_encrypted_private_key, passphrase)
self.assertEqual(None, tuf.formats.RSAKEY_SCHEMA.check_match(
decrypted_non_encrypted), FORMAT_ERROR_MSG)
# Check for invalid arguments.
self.assertRaises(tuf.FormatError,
tuf.rsa_key.create_from_encrypted_pem, 123, passphrase)
self.assertRaises(tuf.FormatError,
tuf.rsa_key.create_from_encrypted_pem, pem_rsakey, ['pw'])
self.assertRaises(tuf.CryptoError,
tuf.rsa_key.create_from_encrypted_pem, 'invalid_pem',
passphrase)
# Run the unit tests.
if __name__ == '__main__':
unittest.main()

View file

@ -14,10 +14,8 @@
<Purpose>
Test cases for for sig.py.
"""
import unittest
import logging
@ -26,7 +24,7 @@
import tuf.formats
import tuf.keydb
import tuf.roledb
import tuf.rsa_key
import tuf.keys
import tuf.sig
logger = logging.getLogger('tuf.test_sig')
@ -34,7 +32,7 @@
# Setup the keys to use in our test cases.
KEYS = []
for _ in range(3):
KEYS.append(tuf.rsa_key.generate(2048))
KEYS.append(tuf.keys.generate_rsa_key(2048))
@ -55,7 +53,7 @@ def test_get_signature_status_no_role(self):
signable['signatures'].append(tuf.sig.generate_rsa_signature(
signable['signed'], KEYS[0]))
tuf.keydb.add_rsakey(KEYS[0])
tuf.keydb.add_key(KEYS[0])
# No specific role we're considering.
sig_status = tuf.sig.get_signature_status(signable, None)
@ -82,7 +80,7 @@ def test_get_signature_status_bad_sig(self):
signable['signed'], KEYS[0]))
signable['signed'] += 'signature no longer matches signed data'
tuf.keydb.add_rsakey(KEYS[0])
tuf.keydb.add_key(KEYS[0])
threshold = 1
roleinfo = tuf.formats.make_role_metadata(
[KEYS[0]['keyid']], threshold)
@ -112,7 +110,7 @@ def test_get_signature_status_unknown_method(self):
signable['signed'], KEYS[0]))
signable['signatures'][0]['method'] = 'fake-sig-method'
tuf.keydb.add_rsakey(KEYS[0])
tuf.keydb.add_key(KEYS[0])
threshold = 1
roleinfo = tuf.formats.make_role_metadata(
[KEYS[0]['keyid']], threshold)
@ -142,7 +140,7 @@ def test_get_signature_status_single_key(self):
signable['signatures'].append(tuf.sig.generate_rsa_signature(
signable['signed'], KEYS[0]))
tuf.keydb.add_rsakey(KEYS[0])
tuf.keydb.add_key(KEYS[0])
threshold = 1
roleinfo = tuf.formats.make_role_metadata(
[KEYS[0]['keyid']], threshold)
@ -171,7 +169,7 @@ def test_get_signature_status_below_threshold(self):
signable['signatures'].append(tuf.sig.generate_rsa_signature(
signable['signed'], KEYS[0]))
tuf.keydb.add_rsakey(KEYS[0])
tuf.keydb.add_key(KEYS[0])
threshold = 2
roleinfo = tuf.formats.make_role_metadata(
[KEYS[0]['keyid'],
@ -205,8 +203,8 @@ def test_get_signature_status_below_threshold_unrecognized_sigs(self):
signable['signatures'].append(tuf.sig.generate_rsa_signature(
signable['signed'], KEYS[2]))
tuf.keydb.add_rsakey(KEYS[0])
tuf.keydb.add_rsakey(KEYS[1])
tuf.keydb.add_key(KEYS[0])
tuf.keydb.add_key(KEYS[1])
threshold = 2
roleinfo = tuf.formats.make_role_metadata(
[KEYS[0]['keyid'],
@ -242,8 +240,8 @@ def test_get_signature_status_below_threshold_unauthorized_sigs(self):
signable['signatures'].append(tuf.sig.generate_rsa_signature(
signable['signed'], KEYS[1]))
tuf.keydb.add_rsakey(KEYS[0])
tuf.keydb.add_rsakey(KEYS[1])
tuf.keydb.add_key(KEYS[0])
tuf.keydb.add_key(KEYS[1])
threshold = 2
roleinfo = tuf.formats.make_role_metadata(
[KEYS[0]['keyid'], KEYS[2]['keyid']], threshold)
@ -278,7 +276,7 @@ def test_check_signatures_no_role(self):
signable['signatures'].append(tuf.sig.generate_rsa_signature(
signable['signed'], KEYS[0]))
tuf.keydb.add_rsakey(KEYS[0])
tuf.keydb.add_key(KEYS[0])
# No specific role we're considering. It's invalid to use the
# function tuf.sig.verify() without a role specified because
@ -295,7 +293,7 @@ def test_verify_single_key(self):
signable['signatures'].append(tuf.sig.generate_rsa_signature(
signable['signed'], KEYS[0]))
tuf.keydb.add_rsakey(KEYS[0])
tuf.keydb.add_key(KEYS[0])
threshold = 1
roleinfo = tuf.formats.make_role_metadata(
[KEYS[0]['keyid']], threshold)
@ -321,8 +319,8 @@ def test_verify_unrecognized_sig(self):
signable['signatures'].append(tuf.sig.generate_rsa_signature(
signable['signed'], KEYS[2]))
tuf.keydb.add_rsakey(KEYS[0])
tuf.keydb.add_rsakey(KEYS[1])
tuf.keydb.add_key(KEYS[0])
tuf.keydb.add_key(KEYS[1])
threshold = 2
roleinfo = tuf.formats.make_role_metadata(
[KEYS[0]['keyid'], KEYS[1]['keyid']], threshold)
@ -363,7 +361,7 @@ def test_may_need_new_keys(self):
signable['signatures'].append(tuf.sig.generate_rsa_signature(
signable['signed'], KEYS[0]))
tuf.keydb.add_rsakey(KEYS[1])
tuf.keydb.add_key(KEYS[1])
threshold = 1
roleinfo = tuf.formats.make_role_metadata(
[KEYS[1]['keyid']], threshold)

312
tuf/README.md Normal file
View file

@ -0,0 +1,312 @@
## Create TUF Repository
![Repo Tools Diagram 1](https://raw.github.com/SantiagoTorres/tuf/repository-tools/resources/images/TUF%20repository%20tools.png)
### Keys
#### Create RSA Keys
```python
from tuf.libtuf import *
# Generate and write the first of two root keys for the repository.
# The following function creates an RSA key pair, where the private key is saved to
# “path/to/root_key” and the public key to “path/to/root_key.pub”.
generate_and_write_rsa_keypair("path/to/root_key", bits=2048, password="password")
# If the key length is unspecified, it defaults to 3072 bits. A length of less
# than 2048 bits prints an error mesage. A password may be supplied as an
# argument, otherwise a user prompt is presented.
generate_and_write_rsa_keypair("path/to/root_key2")
Enter a password for the RSA key:
Confirm:
```
The following four files should now exist:
1. root_key
2. root_key.pub
3. root_key2
4. root_key2.pub
### Import RSA Keys
```python
from tuf.libtuf import *
# Import an existing public key.
public_root_key = import_rsa_publickey_from_file("path/to/root_key.pub")
# Import an existing private key. Importing a private key requires a password, whereas
# importing a public key does not.
private_root_key = import_rsa_privatekey_from_file("path/to/root_key")
Enter a password for the RSA key:
Confirm:
```
At the time of importing the private RSA, a tuf.CryptoError can be thrown if
the key/password is invalid
### Create a new Repository
#### Create Root
```python
# Continuing from the previous section...
# Create a new Repository object that holds the file path to the repository and the four
# top-level role objects (Root, Targets, Release, Timestamp). Metadata files are created when
# repository.write() is called. The repository directory is created if it does not exist.
repository = create_new_repository("path/to/repository/")
# The Repository instance, repository, initially contains top-level Metadata objects.
# Add one of the public keys, created in the previous section, to the root role. Metadata is
# considered valid if it is signed by the public keys corresponding private key.
repository.root.add_key(public_root_key)
# Role keys (i.e., keyid) may be queried. Other attributes include: signing_keys, version,
# signatures, expiration, threshold, and compressions.
repository.root.keys
[u'b23514431a53676595922e955c2d547293da4a7917e3ca243a175e72bbf718df']
# Add a second public key to the root role. Although previously generated and saved to a file,
# the second public key must be imported before it can added to a role.
public_root_key2 = import_rsa_publickey_from_file("path/to/root_key2.pub")
repository.root.add_key(public_root_key2)
# Threshold for each role defaults to 1. Users may change the threshold value, but libtuf.py
# validates thresholds and signatures and warns users. Set the threshold of the root role to 2,
# which means the root metadata file is considered valid if it contains at least 2 valid
# signatures.
repository.root.threshold = 2
private_root_key2=import_rsa_privatekey_from_file("path/to/root_key2", password="pw")
# Load the root signing keys to the repository, which write() uses to sign the root metadata.
# The load_signing_key() method SHOULD warn when the key is NOT explicitly allowed to
# sign for it.
repository.root.load_signing_key(private_root_key)
repository.root_load_signing_key(private_root_key2)
# Print the number of valid signatures and public & private keys of the repository's metadata.
repository.status()
'root' role contains 2 / 2 signatures.
'targets' role contains 0 / 1 public keys.
try:
repository.write()
# An exception is raised here by write() because the other top-level roles (targets, release,
# and timestamp) have not been configured with keys.
except tuf.Error, e:
print e
Not enough signatures for 'path/to/repository/metadata.staged/root.txt'
# In the next section, update the other top-level roles and create a repository with valid metadata.
```
#### Create Timestamp, Release, Targets
```python
# Continuing from the previous section . . .
# Generate keys for the remaining top-level roles. The root keys have been set above.
# The password argument may be omitted if a password prompt is needed.
generate_and_write_rsa_keypair("path/to/targets_key", password="pw")
generate_and_write_rsa_keypair("path/to/release_key", password="pw")
generate_and_write_rsa_keypair("path/to/timestamp_key", password="pw")
# Add the public keys of the remaining top-level roles.
repository.targets.add_key(import_rsa_publickey_from_file("path/to/targets_key.pub"))
repository.release.add_key(import_rsa_publickey_from_file("path/to/release_key.pub"))
repository.timestamp.add_key(import_rsa_publickey_from_file("path/to/timestamp_key.pub"))
# Import the signing keys of the remaining top-level roles. Prompt for passwords.
private_targets_key = import_rsa_privatekey_from_file("path/to/targets_key")
Enter a password for the RSA key:
Confirm:
private_release_key = import_rsa_privatekey_from_file("path/to/release_key")
Enter a password for the RSA key:
Confirm:
private_timestamp_key = import_rsa_privatekey_from_file("path/to/timestamp_key")
Enter a password for the RSA key:
Confirm:
# Load the signing keys of the remaining roles so that valid signatures are generated when
# repository.write() is called.
repository.targets.load_signing_key(private_targets_key)
repository.release.load_signing_key(private_release_key)
repository.timestamp.load_signing_key(private_timestamp_key)
# Optionally set the expiration date of the timestamp role. By default, roles are set to expire
# as follows: root(1 year), targets(3 months), release(1 week), timestamp(1 day).
repository.timestamp.expiration = "2014-10-28 12:08:00"
# Metadata files may also be compressed. Only "gz" is currently supported.
repository.targets.compressions = ["gz"]
repository.release.compressions = ["gz"]
# Write all metadata to “path/to/repository/metadata/”
# The common case is to crawl the filesystem for all roles in
# “path/to/repository/metadata/targets/”.
repository.write()
```
### Targets
#### Add Target Files
```Bash
# Create and save target files to the targets directory of the repository.
$ cd path/to/repository/targets/
$ echo 'file1' > file1.txt
$ echo 'file2' > file2.txt
$ echo 'file3' > file3.txt
$ mkdir django; echo 'file4' > django/file4.txt
```
```python
# Load the repository created in the previous section. This repository contains metadata for
# the top-level roles, but no targets.
repository = load_repository("path/to/repository/")
# Get a list of file paths in a directory, even those in sub-directories.
# This must be relative to an existing directory in the repository, otherwise throw an
# error.
list_of_targets = repository.get_filepaths_in_directory("path/to/repository/targets/",
recursive_walk=False, followlinks=True)
# Add the list of target paths to the metadata of the Targets role. Any target file paths
# that may already exist are NOT replaced. add_targets() does not create or move target files.
repository.targets.add_targets(list_of_targets)
# Individual target files may also be added.
repository.targets.add_target("path/to/repository/targets/file3.txt")
# The private key of the updated targets metadata must be loaded before it can be signed and
# written (Note the load_repository() call above).
private_targets_key = import_rsa_privatekey_from_file("path/to/targets_key")
Enter a password for the RSA key:
Confirm:
repository.targets.load_signing_key(private_targets_key)
# Due to the load_repository(), we must also load the private keys of the other top-level roles
# to generate a valid set of metadata.
private_root_key = import_rsa_privatekey_from_file("path/to/root_key")
Enter a password for the RSA key:
Confirm:
private_root_key2 = import_rsa_privatekey_from_file("path/to/root_key2")
Enter a password for the RSA key:
Confirm:
private_release_key = import_rsa_privatekey_from_file("path/to/release_key")
Enter a password for the RSA key:
Confirm:
private_timestamp_key = import_rsa_privatekey_from_file("path/to/timestamp_key")
Enter a password for the RSA key:
Confirm:
repository.root.load_signing_key(private_root_key)
repository.root.load_signing_key(private_root_key2)
repository.release.load_signing_key(private_release_key)
repository.timestamp.load_signing_key(private_timestamp_key)
# Generate new versions of all the top-level metadata and increment version numbers.
repository.write()
```
#### Remove Target Files
```python
# Continuing from the previous section . . .
# Remove a target file listed in the “targets” metadata. The target file is not actually deleted
# from the file system.
repository.targets.remove_target("path/to/repository/targets/file3.txt")
# repository.write() creates any new metadata files, updates those that have changed, and any that
# need updating to make a new “release” (new release.txt and timestamp.txt).
repository.write()
```
### Delegations
```python
# Continuing from the previous section . . .
# Generate a key for a new delegated role named “unclaimed”.
generate_and_write_rsa_keypair("path/to/unclaimed_key", bits=2048, password="pw")
public_unclaimed_key = import_rsa_publickey_from_file("path/to/unclaimed_key.pub")
# Make a delegation from “targets” to “targets/unclaimed”, for all targets in “list_of_targets”.
# The delegated roles full name is not required.
# delegated(rolename, list_of_public_keys, list_of_file_paths, threshold, restricted_paths)
repository.targets.delegate("unclaimed", [public_unclaimed_key], [])
# Load the private key of “targets/unclaimed” so that signatures are added and valid metadata
# is created.
private_unclaimed_key = import_rsa_privatekey_from_file("path/to/unclaimed_key")
Enter a password for the RSA key:
Confirm:
repository.targets.unclaimed.load_signing_key(private_unclaimed_key)
# Update an attribute of the unclaimed role and add a target file.
repository.targets.unclaimed.version = 2
# Delegations may also be nested. Create the delegated role "targets/unclaimed/django",
# where it initially contains zero targets and future targets are restricted to a
# particular directory.
repository.targets.unclaimed.delegate("django", [public_unclaimed_key], [],
restricted_paths=["path/to/repository/targets/django/"])
repository.targets.unclaimed.django.load_signing_key(private_unclaimed_key)
repository.targets.unclaimed.django.add_target("path/to/repository/targets/django/file4.txt")
repository.targets.unclaimed.django.compressions = ["gz"]
# Write the metadata of "targets/unclaimed", targets/unclaimed/django", targets, release,
# and timestamp.
repository.write()
```
#### Revoke Delegated Role
```python
# Continuing from the previous section . . .
# Create a delegated role that will be revoked in the next step.
repository.targets.unclaimed.delegate("flask", [public_unclaimed_key], [])
# Revoke “targets/unclaimed/flask” and write the metadata of all remaining roles.
repository.targets.unclaimed.revoke("flask")
repository.write()
```
```bash
# Copy the staged metadata directory changes to the live repository.
$ cp -r "path/to/repository/metadata.staged" "path/to/repository/metadata"
```
## Client Setup and Repository Trial
### Using TUF Within an Example Client Updater
```python
# The following function creates a directory structure that a client
# downloading new software using tuf (via tuf/client/updater.py) will expect.
# The root.txt metadata file must exist, and also the directories that hold the metadata files
# downloaded from a repository. Software updaters integrating with TUF may use this
# directory to store TUF updates saved on the client side. create_tuf_client_directory()
# moves metadata files “path/to/repository/” to “path/to/client/”. The repository in
# “path/to/repository/” is the repository created in the “Create TUF Repository” section.
create_tuf_client_directory("path/to/repository/", "path/to/client/")
```
#### Test TUF Locally
```Bash
# Run the local TUF repository server.
$ cd “path/to/repository/”; python -m SimpleHTTPServer 8001
# Retrieve targets from the TUF repository and save them to "path/to/client/". The
# basic_client.py module is available in "tuf/client/".
# In a different command-line prompt . . .
$ cd "path/to/client/"
$ ls
metadata/
$ python basic_client.py --repo http://localhost:8001
$ ls . targets/ targets/django/
.:
metadata targets tuf.log
targets/:
django file1.txt file2.txt
targets/django/:
file4.txt
```

View file

@ -18,7 +18,6 @@
The names chosen for TUF Exception classes should end in
'Error' except where there is a good reason not to, and
provide that reason in those cases.
"""
import urlparse
@ -117,6 +116,14 @@ class RepositoryError(Error):
class InsufficientKeysError(Error):
"""Indicate that metadata role lacks a threshold of pubic or private keys."""
pass
class ForbiddenTargetError(RepositoryError):
"""Indicate that a role signed for a target that it was not delegated to."""
pass

View file

@ -97,7 +97,6 @@
# The updated target files are saved locally to 'destination_directory'.
for target in updated_targets:
updater.download_target(target, destination_directory)
"""
import errno
@ -111,6 +110,7 @@
import tuf.download
import tuf.formats
import tuf.hash
import tuf.keys
import tuf.keydb
import tuf.log
import tuf.mirrors
@ -194,7 +194,6 @@ class Updater(object):
Any files located in 'destination_directory' that were previously
served by the repository but have since been removed, can be deleted
from disk by the client by calling this method.
"""
def __init__(self, updater_name, repository_mirrors):
@ -251,7 +250,6 @@ def __init__(self, updater_name, repository_mirrors):
<Returns>
None.
"""
# Do the arguments have the correct format?
@ -327,7 +325,6 @@ def __init__(self, updater_name, repository_mirrors):
def __str__(self):
"""
The string representation of an Updater object.
"""
return self.name
@ -370,7 +367,6 @@ def _load_metadata_from_file(self, metadata_set, metadata_role):
<Returns>
None.
"""
# Ensure we have a valid metadata set.
@ -435,7 +431,6 @@ def _rebuild_key_and_role_db(self):
<Returns>
None.
"""
# Clobbering this means all delegated metadata files are rendered outdated
@ -475,7 +470,6 @@ def _import_delegations(self, parent_role):
<Returns>
None.
"""
current_parent_metadata = self.metadata['current'][parent_role]
@ -492,13 +486,13 @@ def _import_delegations(self, parent_role):
# Iterate through the keys of the delegated roles of 'parent_role'
# and load them.
for keyid, keyinfo in keys_info.items():
if keyinfo['keytype'] == 'rsa':
rsa_key = tuf.rsa_key.create_from_metadata_format(keyinfo)
if keyinfo['keytype'] in ['rsa', 'ed25519']:
key = tuf.keys.format_metadata_to_key(keyinfo)
# We specify the keyid to ensure that it's the correct keyid
# for the key.
try:
tuf.keydb.add_rsakey(rsa_key, keyid)
tuf.keydb.add_key(key, keyid)
except tuf.KeyAlreadyExistsError:
pass
except (tuf.FormatError, tuf.Error), e:
@ -556,7 +550,6 @@ def refresh(self):
<Returns>
None.
"""
# The timestamp role does not have signed metadata about it; otherwise we
@ -617,7 +610,6 @@ def __check_hashes(self, file_object, trusted_hashes):
<Returns>
None.
"""
# Verify each trusted hash of 'trusted_hashes'. Raise exception if
@ -700,7 +692,6 @@ def __soft_check_compressed_file_length(self, file_object,
<Returns>
None.
"""
observed_length = file_object.get_compressed_length()
@ -745,7 +736,6 @@ def get_target_file(self, target_filepath, compressed_file_length,
<Returns>
A tuf.util.TempFile file-like object containing the target.
"""
def verify_uncompressed_target_file(target_file_object):
@ -801,7 +791,6 @@ def __verify_uncompressed_metadata_file(self, metadata_file_object,
<Returns>
None.
"""
metadata = metadata_file_object.read()
@ -871,7 +860,6 @@ def unsafely_get_metadata_file(self, metadata_role, metadata_filepath,
<Returns>
A tuf.util.TempFile file-like object containing the metadata.
"""
def unsafely_verify_uncompressed_metadata_file(metadata_file_object):
@ -926,7 +914,6 @@ def safely_get_metadata_file(self, metadata_role, metadata_filepath,
<Returns>
A tuf.util.TempFile file-like object containing the metadata.
"""
def safely_verify_uncompressed_metadata_file(metadata_file_object):
@ -992,7 +979,6 @@ def __get_file(self, filepath, verify_uncompressed_file, file_type,
<Returns>
A tuf.util.TempFile file-like object containing the metadata or target.
"""
file_mirrors = tuf.mirrors.get_list_of_mirrors(file_type, filepath,
@ -1085,7 +1071,6 @@ def _update_metadata(self, metadata_role, fileinfo, compression=None):
<Returns>
None.
"""
# Construct the metadata filename as expected by the download/mirror modules.
@ -1234,7 +1219,6 @@ def _update_metadata_if_changed(self, metadata_role, referenced_metadata='releas
<Returns>
None.
"""
uncompressed_metadata_filename = metadata_role + '.txt'
@ -1373,7 +1357,6 @@ def _ensure_all_targets_allowed(self, metadata_role, metadata_object):
<Returns>
None.
"""
# Return if 'metadata_role' is 'targets'. 'targets' is not
@ -1536,7 +1519,6 @@ def _fileinfo_has_changed(self, metadata_filename, new_fileinfo):
<Returns>
Boolean. True if the fileinfo has changed, false otherwise.
"""
# If there is no fileinfo currently stored for 'metadata_filename',
@ -1595,7 +1577,6 @@ def _update_fileinfo(self, metadata_filename):
<Returns>
None.
"""
# In case we delayed loading the metadata and didn't do it in
@ -1640,7 +1621,6 @@ def _move_current_to_previous(self, metadata_role):
<Returns>
None.
"""
# Get the 'current' and 'previous' full file paths for 'metadata_role'
@ -1685,7 +1665,6 @@ def _delete_metadata(self, metadata_role):
<Returns>
None.
"""
# The root metadata role is never deleted without a replacement.
@ -1723,7 +1702,6 @@ def _ensure_not_expired(self, metadata_role):
<Returns>
None.
"""
# Construct the full metadata filename and the location of its
@ -1779,7 +1757,6 @@ def all_targets(self):
<Returns>
A list of targets, conformant to 'tuf.formats.TARGETFILES_SCHEMA'.
"""
# Load the most up-to-date targets of the 'targets' role and all
@ -1834,7 +1811,6 @@ def _refresh_targets_metadata(self, rolename='targets', include_delegations=Fals
<Returns>
None.
"""
roles_to_update = []
@ -1846,7 +1822,10 @@ def _refresh_targets_metadata(self, rolename='targets', include_delegations=Fals
if metadata_path == rolename + '.txt':
roles_to_update.append(metadata_path[:-len('.txt')])
elif include_delegations and metadata_path.startswith(role_prefix):
roles_to_update.append(metadata_path[:-len('.txt')])
# Add delegated roles. Skip roles names containing compression
# extensions.
if metadata_path.endswith('.txt'):
roles_to_update.append(metadata_path[:-len('.txt')])
# Remove the 'targets' role because it gets updated when the targets.txt
# file is updated in _update_metadata_if_changed('targets').
@ -1886,7 +1865,6 @@ def _refresh_targets_metadata(self, rolename='targets', include_delegations=Fals
def refresh_targets_metadata_chain(self, rolename):
"""
Proof-of-concept.
"""
# List of parent roles to update.
@ -1993,7 +1971,6 @@ def _targets_of_role(self, rolename, targets=None, skip_refresh=False):
<Returns>
A list of dict objects containing the target information of all the
targets of 'rolename'. Conformant to 'tuf.formats.TARGETFILES_SCHEMA'.
"""
if targets is None:
@ -2063,7 +2040,6 @@ def targets_of_role(self, rolename='targets'):
<Returns>
A list of targets, conformant to 'tuf.formats.TARGETFILES_SCHEMA'.
"""
# Does 'rolename' have the correct format?
@ -2104,7 +2080,6 @@ def target(self, target_filepath):
<Returns>
The target information for 'target_filepath', conformant to
'tuf.formats.TARGETFILE_SCHEMA'.
"""
# Does 'target_filepath' have the correct format?
@ -2152,7 +2127,6 @@ def _preorder_depth_first_walk(self, target_filepath):
<Returns>
The target information for 'target_filepath', conformant to
'tuf.formats.TARGETFILE_SCHEMA'.
"""
target = None
@ -2234,7 +2208,6 @@ def _get_target_from_targets_role(self, role_name, targets, target_filepath):
<Returns>
The target information for 'target_filepath', conformant to
'tuf.formats.TARGETFILE_SCHEMA'.
"""
target = None
@ -2295,7 +2268,6 @@ def _visit_child_role(self, child_role, target_filepath):
'target_filepath', then we return the role name of 'child_role'.
Otherwise, we return None.
"""
child_role_name = child_role['name']
@ -2367,7 +2339,6 @@ def _get_target_hash(self, target_filepath, hash_function='sha256'):
<Returns>
The hash of 'target_filepath'.
"""
# Calculate the hash of the filepath to determine which bin to find the
@ -2417,7 +2388,6 @@ def remove_obsolete_targets(self, destination_directory):
<Returns>
None.
"""
# Does 'destination_directory' have the correct format?
@ -2480,7 +2450,6 @@ def updated_targets(self, targets, destination_directory):
<Returns>
A list of targets, conformant to 'tuf.formats.TARGETFILES_SCHEMA'.
"""
# Do the arguments have the correct format?
@ -2545,7 +2514,6 @@ def download_target(self, target, destination_directory):
<Returns>
None.
"""
# Do the arguments have the correct format?

View file

@ -13,10 +13,8 @@
<Purpose>
A central location for TUF configuration settings.
"""
# Set a directory that should be used for all temporary files. If this
# is None, then the system default will be used. The system default
# will also be used if a directory path set here is invalid or
@ -65,3 +63,11 @@
# iteration setting used by the old '<keyid>.key'.
# https://en.wikipedia.org/wiki/PBKDF2
PBKDF2_ITERATIONS = 100000
# The user client may set the cryptography library used by The Update Framework
# updater, or the software updater integrating TUF. The repository tools may
# Supported RSA cryptography libraries: ['pycrypto']
RSA_CRYPTO_LIBRARY = 'pycrypto'
# Supported ed25519 cryptography libraries: ['pynacl', 'ed25519']
ED25519_CRYPTO_LIBRARY = 'ed25519'

View file

@ -1,624 +0,0 @@
"""
<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 format_keyval_to_metadata() and
format_metadata_to_key(). 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 tuf
# 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 an intel core 2 duo @
# 2.2 ghz x 2). Optionally, the PyNaCl module may be used to speed up ed25519
# cryptographic operations.
# http://ed25519.cr.yp.to/software.html
# Try to import PyNaCl. The functions found in this module provide the option
# of using PyNaCl over the slower implementation of ed25519.
try:
import nacl.signing
import nacl.encoding
except (ImportError, IOError):
message = 'The PyNacl library and/or its dependencies cannot be imported.'
raise tuf.UnsupportedLibraryError(message)
# The pure Python implementation of ed25519.
import ed25519.ed25519
# 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 to 64
bytes.
>>> 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 format_keyval_to_metadata(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 to 64 bytes.
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 = format_keyval_to_metadata(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 format_metadata_to_key(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 to 64
bytes.
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 = format_keyval_to_metadata(key_val, private=True)
>>> ed25519_key_2 = format_metadata_to_key(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.
# 'format_keyval_to_metadata()' returns the object needed by _get_keyid().
ed25519_key_meta = format_keyval_to_metadata(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 to 64
bytes.
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
(128 bytes) 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 to
64 bytes.
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()

408
tuf/ed25519_keys.py Executable file
View file

@ -0,0 +1,408 @@
"""
<Program Name>
ed25519_keys.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_keys.py' calls 'ed25519/ed25519.py', which is the pure Python
implementation of ed25519 optimized for a faster runtime.
The Python reference implementation is concise, but very slow (verifying
signatures takes ~9 seconds on an Intel core 2 duo @ 2.2 ghz x 2). The
optimized version can verify signatures in ~2 seconds.
http://ed25519.cr.yp.to/software.html
https://github.com/pyca/ed25519
Optionally, ed25519 cryptographic operations may be executed by PyNaCl, which
is a Python binding to the NaCl library and is faster than the pure python
implementation. Verifying signatures can take approximately 0.0009 seconds.
PyNaCl relies on the libsodium C library.
https://github.com/pyca/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_keys.py generate the actual ed25519 keys and the functions listed
above can be viewed as an easy-to-use public interface.
"""
# Help with Python 3 compatibility, where the print statement is a function, an
# implicit relative import is invalid, and the '/' operator performs true
# division. Example: print 'hello world' raises a 'SyntaxError' exception.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
# 'binascii' required for hexadecimal conversions. Signatures and
# public/private keys are hexlified.
import binascii
# 'os' required to generate OS-specific randomness (os.urandom) suitable for
# cryptographic use.
# http://docs.python.org/2/library/os.html#miscellaneous-functions
import os
# Import the python implementation of the ed25519 algorithm provided by pyca,
# which is an optimized version of the one provided by ed25519's authors.
# Note: The pure Python version do not include protection against side-channel
# attacks. Verifying signatures can take approximately 2 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
# https://github.com/pyca/ed25519
# https://github.com/pyca/pynacl
#
# PyNaCl's 'cffi' dependency may thrown an 'IOError' exception when
# importing 'nacl.signing'.
try:
import nacl.signing
import nacl.encoding
except (ImportError, IOError):
pass
# The optimized pure Python implementation of ed25519 provided by TUF. If
# PyNaCl cannot be imported and an attempt to use is made in this module, a
# 'tuf.UnsupportedLibraryError' exception is raised.
import ed25519.ed25519
import tuf
# Digest objects needed to generate hashes.
import tuf.hash
# Perform object format-checking.
import tuf.formats
# 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_public_and_private(use_pynacl=False):
"""
<Purpose>
Generate a pair of ed25519 public and private keys.
The public and private keys returned conform to
'tuf.formats.ED25519PULIC_SCHEMA' and 'tuf.formats.ED25519SEED_SCHEMA',
respectively, and have the form:
'\xa2F\x99\xe0\x86\x80%\xc8\xee\x11\xb95T\xd9\...'
An ed25519 seed key is a random 32-byte string. Public keys are also 32
bytes.
>>> public, private = generate_public_and_private(use_pynacl=False)
>>> tuf.formats.ED25519PUBLIC_SCHEMA.matches(public)
True
>>> tuf.formats.ED25519SEED_SCHEMA.matches(private)
True
>>> public, private = generate_public_and_private(use_pynacl=True)
>>> tuf.formats.ED25519PUBLIC_SCHEMA.matches(public)
True
>>> tuf.formats.ED25519SEED_SCHEMA.matches(private)
True
<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
(slower).
<Exceptions>
tuf.FormatError, if 'use_pynacl' is not a Boolean.
tuf.UnsupportedLibraryError, if the PyNaCl ('nacl') module is unavailable
and 'use_pynacl' is True.
NotImplementedError, if a randomness source is not found by 'os.urandom'.
<Side Effects>
The ed25519 keys are generated by first creating a random 32-byte seed
with os.urandom() and then calling ed25519's
ed25519.25519.publickey(seed) or PyNaCl's nacl.signing.SigningKey().
<Returns>
A (public, private) tuple that conform to 'tuf.formats.ED25519PUBLIC_SCHEMA'
and 'tuf.formats.ED25519SEED_SCHEMA', respectively.
"""
# Does 'use_pynacl' have the correct format?
# This check will ensure 'use_pynacl' conforms to 'tuf.formats.TOGGLE_SCHEMA'.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.TOGGLE_SCHEMA.check_match(use_pynacl)
# 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.
try:
nacl_key = nacl.signing.SigningKey(seed)
public = str(nacl_key.verify_key)
except NameError:
message = 'The PyNaCl library and/or its dependencies unavailable.'
raise tuf.UnsupportedLibraryError(message)
# Use the pure Python implementation of ed25519.
else:
public = ed25519.ed25519.publickey(seed)
return public, seed
def create_signature(public_key, private_key, data, use_pynacl=False):
"""
<Purpose>
Return a (signature, method) tuple, where the method is either:
'ed25519-python' if the signature is generated by the pure python
implemenation, or 'ed25519-pynacl' if generated by 'nacl'.
signature conforms to 'tuf.formats.ED25519SIGNATURE_SCHEMA', and has the
form:
'\xae\xd7\x9f\xaf\x95{bP\x9e\xa8YO Z\x86\x9d...'
A signature is a 64-byte string.
>>> public, private = generate_public_and_private(use_pynacl=False)
>>> data = 'The quick brown fox jumps over the lazy dog'
>>> signature, method = \
create_signature(public, private, data, use_pynacl=False)
>>> tuf.formats.ED25519SIGNATURE_SCHEMA.matches(signature)
True
>>> method == 'ed25519-python'
True
>>> signature, method = \
create_signature(public, private, data, use_pynacl=True)
>>> tuf.formats.ED25519SIGNATURE_SCHEMA.matches(signature)
True
>>> method == 'ed25519-pynacl'
True
<Arguments>
public:
The ed25519 public key, which is a 32-byte string.
private:
The ed25519 private key, which is a 32-byte string.
data:
Data object used by create_signature() to generate the signature.
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>
tuf.FormatError, if the arguments are improperly formatted.
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 'public_key' have the correct format?
# This check will ensure 'public_key' conforms to
# 'tuf.formats.ED25519PUBLIC_SCHEMA', which must have length 32 bytes.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.ED25519PUBLIC_SCHEMA.check_match(public_key)
# Is 'private_key' properly formatted?
tuf.formats.ED25519SEED_SCHEMA.check_match(private_key)
# Is 'use_pynacl' properly formatted?
tuf.formats.TOGGLE_SCHEMA.check_match(use_pynacl)
# 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.
public = public_key
private = private_key
method = None
signature = None
# The private and public keys have been validated above by 'tuf.formats' and
# should be 32-byte strings.
if use_pynacl:
method = 'ed25519-pynacl'
try:
nacl_key = nacl.signing.SigningKey(private)
nacl_sig = nacl_key.sign(data)
signature = nacl_sig.signature
except NameError:
message = 'The PyNaCl library and/or its dependencies unavailable.'
raise tuf.UnsupportedLibraryError(message)
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:
signature = ed25519.ed25519.signature(data, private, public)
# 'Exception' raised by ed25519.py for any exception that may occur.
except Exception, e:
message = 'An "ed25519-python" signature could not be generated.'
raise tuf.CryptoError(message)
return signature, method
def verify_signature(public_key, method, signature, data, use_pynacl=False):
"""
<Purpose>
Determine whether the private key corresponding to 'public_key' produced
'signature'. verify_signature() will use the public key, the 'method' and
'sig', and 'data' arguments to complete the verification.
>>> public, private = generate_public_and_private(use_pynacl=False)
>>> data = 'The quick brown fox jumps over the lazy dog'
>>> signature, method = \
create_signature(public, private, data, use_pynacl=False)
>>> verify_signature(public, method, signature, data, use_pynacl=False)
True
>>> verify_signature(public, method, signature, data, use_pynacl=True)
True
>>> bad_data = 'The sly brown fox jumps over the lazy dog'
>>> bad_signature, method = \
create_signature(public, private, bad_data, use_pynacl=False)
>>> verify_signature(public, method, bad_signature, data, use_pynacl=False)
False
<Arguments>
public_key:
The public key is a 32-byte string.
method:
'ed25519-python' if the signature was generated by the pure python
implementation and 'ed25519-pynacl' if generated by 'nacl'.
signature:
The signature is a 64-byte string.
data:
Data object used by tuf.ed25519_keys.create_signature() to generate
'signature'. 'data' is needed here to verify the signature.
use_pynacl:
True, if the ed25519 signature should be verified by PyNaCl. False,
if the signature should be verified with the pure Python implementation
of ed25519 (slower).
<Exceptions>
tuf.UnknownMethodError. Raised if the signing method used by
'signature' is not one supported by tuf.ed25519_keys.create_signature().
tuf.FormatError. Raised if the arguments are improperly formatted.
<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 'public_key' have the correct format?
# This check will ensure 'public_key' conforms to
# 'tuf.formats.ED25519PUBLIC_SCHEMA', which must have length 32 bytes.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.ED25519PUBLIC_SCHEMA.check_match(public_key)
# Is 'method' properly formatted?
tuf.formats.NAME_SCHEMA.check_match(method)
# Is 'signature' properly formatted?
tuf.formats.ED25519SIGNATURE_SCHEMA.check_match(signature)
# Is 'use_pynacl' properly formatted?
tuf.formats.TOGGLE_SCHEMA.check_match(use_pynacl)
# Verify 'signature'. Before returning the Boolean result,
# ensure 'ed25519-python' or 'ed25519-pynacl' was used as the signing method.
# Raise 'tuf.UnsupportedLibraryError' if 'use_pynacl' is True but 'nacl' is
# unavailable.
public = public_key
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, signature)
if nacl_message == data:
valid_signature = True
except NameError:
message = 'The PyNaCl library and/or its dependencies unavailable.'
raise tuf.UnsupportedLibraryError(message)
except nacl.signing.BadSignatureError:
pass
# Verify signature with 'ed25519-python' (i.e., pure Python implementation).
else:
try:
ed25519.ed25519.checkvalid(signature, 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_keys.py' as a standalone module.
# python -B ed25519_keys.py
import doctest
doctest.testmod()

425
tuf/evp.py Executable file
View file

@ -0,0 +1,425 @@
"""
<Program Name>
evp.py
<Author>
Vladimir Diaz <vladimir.v.diaz@gmail.com>
<Started>
October 2013.
<Copyright>
See LICENSE for licensing information.
<Purpose>
The goal of this module is to support public-key cryptography using
the RSA algorithm. The RSA-related functions provided include
generate(), create_signature(), and verify_signature(). The 'evpy' package
used by 'rsa_key.py' generates the actual RSA keys and the functions listed
above can be viewed as an easy-to-use public interface. Additional functions
contained here include create_in_metadata_format() and
create_from_metadata_format(). These last two functions produce or use RSA
keys compatible with the key structures listed in TUF Metadata files.
The generate() function returns a dictionary containing all the information
needed of RSA keys, such as public and private keys, keyIDs, and an iden-
fier. create_signature() and verify_signature() are supplemental functions
used for generating RSA signatures and verifying them.
Key IDs are used as identifiers for keys (e.g., RSA key). They are the
hexadecimal representation of the hash of key object (specifically, the key
object containing only the public key). See 'rsa_key.py' and the
'_get_keyid()' function to see precisely how keyids are generated. One may
get the keyid of a key object by simply accessing the dictionary's 'keyid'
key (i.e., rsakey['keyid']).
"""
# Required for hexadecimal conversions.
import binascii
# Needed to generate, sign, and verify RSA keys.
import evpy.signature
import evpy.envelope
# Digest objects needed to generate hashes.
import tuf.hash
# Perform object format-checking.
import tuf.formats
_KEY_ID_HASH_ALGORITHM = 'sha256'
# Recommended RSA key sizes: http://www.rsa.com/rsalabs/node.asp?id=2004
# According to the document above, revised May 6, 2003, RSA keys of
# size 3072 provide security through 2031 and beyond.
_DEFAULT_RSA_KEY_BITS = 3072
def generate(bits=_DEFAULT_RSA_KEY_BITS):
"""
<Purpose>
Generate public and private RSA keys, with modulus length 'bits'.
In addition, a keyid used as an identifier for RSA keys is generated.
The object returned conforms to tuf.formats.RSAKEY_SCHEMA and as the form:
{'keytype': 'rsa',
'keyid': keyid,
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
The public and private keys are in PEM format and stored as strings.
<Arguments>
bits:
The key size, or key length, of the RSA key.
<Exceptions>
tuf.CryptoError, if an exception occurs after calling evpy.envelope.keygen().
tuf.FormatError, if 'bits' does not contain the correct format.
<Side Effects>
The RSA keys are generated by calling evpy.envelope.keygen().
<Returns>
A dictionary containing the RSA keys and other identifying information.
"""
# Does 'bits' have the correct format?
# This check will ensure 'bits' conforms to 'tuf.formats.RSAKEYBITS_SCHEMA'.
# 'bits' must be an integer object, with a minimum value of 2048.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.RSAKEYBITS_SCHEMA.check_match(bits)
# Begin building the RSA key dictionary.
rsakey_dict = {}
keytype = 'rsa'
# Generate the public and private keys. 'public_key' and 'private_key'
# will both be strings containing RSA keys in PEM format.
# The evpy.envelope module performs the actual key generation. The
# evpy.envelope.keygen() function returns a (public, private) tuple.
try:
public_key, private_key = evpy.envelope.keygen(bits, pem=True)
except (evpy.envelope.EnvelopeError, evpy.envelope.KeygenError, MemoryError), e:
raise tuf.CryptoError(e)
# Generate the keyid for the RSA key. 'key_value' corresponds to the
# 'keyval' entry of the RSAKEY_SCHEMA dictionary.
key_value = {'public': public_key,
'private': ''}
keyid = _get_keyid(key_value)
# Build the 'rsakey_dict' dictionary.
# Update 'key_value' with the RSA private key prior to adding
# 'key_value' to 'rsakey_dict'.
key_value['private'] = private_key
rsakey_dict['keytype'] = keytype
rsakey_dict['keyid'] = keyid
rsakey_dict['keyval'] = key_value
return rsakey_dict
def create_in_metadata_format(key_value, private=False):
"""
<Purpose>
Return a dictionary conformant to tuf.formats.KEY_SCHEMA.
If 'private' is True, include the private key. The dictionary
returned has the form:
{'keytype': 'rsa',
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
or
{'keytype': 'rsa',
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': ''}} if 'private' is False.
The private and public keys are in PEM format.
RSA keys are stored in Metadata files (e.g., root.txt) in the format
returned by this function.
<Arguments>
key_value:
A dictionary containing a private and public RSA key.
'key_value' is of the form:
{'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}},
conformat to tuf.formats.KEYVAL_SCHEMA.
private:
Indicates if the private key should be included in the
returned dictionary.
<Exceptions>
tuf.FormatError, if 'key_value' does not conform to
tuf.formats.KEYVAL_SCHEMA.
<Side Effects>
None.
<Returns>
An KEY_SCHEMA dictionary.
"""
# Does 'key_value' have the correct format?
# This check will ensure 'key_value' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.KEYVAL_SCHEMA.check_match(key_value)
if private and key_value['private']:
return {'keytype': 'rsa', 'keyval': key_value}
else:
public_key_value = {'public': key_value['public'], 'private': ''}
return {'keytype': 'rsa', 'keyval': public_key_value}
def create_from_metadata_format(key_metadata):
"""
<Purpose>
Construct an RSA key dictionary (i.e., tuf.formats.RSAKEY_SCHEMA)
from 'key_metadata'. The dict returned by this function has the exact
format as the dict returned by generate(). It is of the form:
{'keytype': 'rsa',
'keyid': keyid,
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
The public and private keys are in PEM format and stored as strings.
RSA key dictionaries in RSAKEY_SCHEMA format should be used by
modules storing a collection of keys, such as a keydb and keystore.
RSA keys as stored in metadata files use a different format, so this
function should be called if an RSA key is extracted from one of these
metadata files and needs converting. Generate() creates an entirely
new key and returns it in the format appropriate for keydb and keystore.
<Arguments>
key_metadata:
The RSA key dictionary as stored in Metadata files, conforming to
tuf.formats.KEY_SCHEMA.
<Exceptions>
tuf.FormatError, if 'key_metadata' does not conform to
tuf.formats.KEY_SCHEMA.
<Side Effects>
None.
<Returns>
A dictionary containing the RSA keys and other identifying information.
"""
# Does 'key_metadata' have the correct format?
# This check will ensure 'key_metadata' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.KEY_SCHEMA.check_match(key_metadata)
# Construct the dictionary to be returned.
rsakey_dict = {}
keytype = 'rsa'
key_value = key_metadata['keyval']
keyid = _get_keyid(key_value)
# We now have all the required key values.
# Build 'rsakey_dict'.
rsakey_dict['keytype'] = keytype
rsakey_dict['keyid'] = keyid
rsakey_dict['keyval'] = key_value
return rsakey_dict
def _get_keyid(key_value):
"""Return the keyid for 'key_value'."""
# 'keyid' will be generated from an object conformant to KEY_SCHEMA,
# which is the format Metadata files (e.g., root.txt) store keys.
# 'create_in_metadata_format()' returns the object needed by _get_keyid().
rsakey_meta = create_in_metadata_format(key_value, private=False)
# Convert the RSA key to JSON Canonical format suitable for adding
# to digest objects.
rsakey_update_data = tuf.formats.encode_canonical(rsakey_meta)
# Create a digest object and call update(), using the JSON
# canonical format of 'rskey_meta' as the update data.
digest_object = tuf.hash.digest(_KEY_ID_HASH_ALGORITHM)
digest_object.update(rsakey_update_data)
# 'keyid' becomes the hexadecimal representation of the hash.
keyid = digest_object.hexdigest()
return keyid
def create_signature(rsakey_dict, data):
"""
<Purpose>
Return a signature dictionary of the form:
{'keyid': keyid, 'method': 'evp', 'sig': sig}.
The signing process will use the private key
rsakey_dict['keyval']['private'] and 'data' to generate the signature.
<Arguments>
rsakey_dict:
A dictionary containing the RSA keys and other identifying information.
'rsakey_dict' has the form:
{'keytype': 'rsa',
'keyid': keyid,
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
The public and private keys are in PEM format and stored as strings.
data:
Data object used by create_signature() to generate the signature.
<Exceptions>
TypeError, if a private key is not defined for 'rsakey_dict'.
tuf.FormatError, if an incorrect format is found for the
'rsakey_dict' object.
<Side Effects>
evpy.signature.sign() called to perform the actual signing.
<Returns>
A signature dictionary conformat to tuf.format.SIGNATURE_SCHEMA.
"""
# Does 'rsakey_dict' have the correct format?
# This check will ensure 'rsakey_dict' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
# Signing the 'data' object requires a private key.
# The 'evp' (i.e., evpy) signing method is the only method
# currently supported.
signature = {}
private_key = rsakey_dict['keyval']['private']
keyid = rsakey_dict['keyid']
method = 'evp'
if private_key:
sig = evpy.signature.sign(data, key=private_key)
else:
raise TypeError('The required private key is not defined for rsakey_dict.')
# Build the signature dictionary to be returned.
# The hexadecimal representation of 'sig' is stored in the signature.
signature['keyid'] = keyid
signature['method'] = method
signature['sig'] = binascii.hexlify(sig)
return signature
def verify_signature(rsakey_dict, signature, data):
"""
<Purpose>
Determine whether the private key belonging to 'rsakey_dict' produced
'signature'. verify_signature() will use the public key found in
'rsakey_dict', the 'method' and 'sig' objects contained in 'signature',
and 'data' to complete the verification. Type-checking performed on both
'rsakey_dict' and 'signature'.
<Arguments>
rsakey_dict:
A dictionary containing the RSA keys and other identifying information.
'rsakey_dict' has the form:
{'keytype': 'rsa',
'keyid': keyid,
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
The public and private keys are in PEM format and stored as strings.
signature:
The signature dictionary produced by tuf.rsa_key.create_signature().
'signature' has the form:
{'keyid': keyid, 'method': 'method', 'sig': sig}. Conformant to
tuf.formats.SIGNATURE_SCHEMA.
data:
Data object used by tuf.rsa_key.create_signature() to generate
'signature'. 'data' is needed here to verify the signature.
<Exceptions>
tuf.UnknownMethodError. Raised if the signing method used by
'signature' is not one supported by tuf.rsa_key.create_signature().
tuf.FormatError. Raised if either 'rsakey_dict'
or 'signature' do not match their respective tuf.formats schema.
'rsakey_dict' must conform to tuf.formats.RSAKEY_SCHEMA.
'signature' must conform to tuf.formats.SIGNATURE_SCHEMA.
<Side Effects>
evpy.signature_verify() called to do the actual verification.
<Returns>
Boolean.
"""
# Does 'rsakey_dict' have the correct format?
# This check will ensure 'rsakey_dict' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
# Does 'signature' have the correct format?
tuf.formats.SIGNATURE_SCHEMA.check_match(signature)
# Using the public key belonging to 'rsakey_dict'
# (i.e., rsakey_dict['keyval']['public']), verify whether 'signature'
# was produced by rsakey_dict's corresponding private key
# rsakey_dict['keyval']['private']. Before returning the Boolean result,
# ensure 'evp' was used as the signing method.
method = signature['method']
sig = signature['sig']
public_key = rsakey_dict['keyval']['public']
if method != 'evp':
raise tuf.UnknownMethodError(method)
return evpy.signature.verify(data, binascii.unhexlify(sig), key=public_key)

View file

@ -57,10 +57,8 @@
Example:
signable_object = make_signable(unsigned_object)
"""
import binascii
import calendar
import re
@ -76,8 +74,12 @@
# easily backwards compatible with clients that are already deployed.
# A date in 'YYYY-MM-DD HH:MM:SS UTC' format.
# TODO: Support timestamps according to the ISO 8601 standard.
TIME_SCHEMA = SCHEMA.RegularExpression(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} UTC')
# A date in 'YYYY-MM-DD HH:MM:SS UTC' format.
DATETIME_SCHEMA = SCHEMA.RegularExpression(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}')
# A hexadecimal value in '23432df87ab..' format.
HASH_SCHEMA = SCHEMA.RegularExpression(r'[a-fA-F0-9]+')
@ -109,7 +111,7 @@
# A dictionary holding version information.
VERSION_SCHEMA = SCHEMA.Object(
object_name='version',
object_name='VERSION_SCHEMA',
major=SCHEMA.Integer(lo=0),
minor=SCHEMA.Integer(lo=0),
fix=SCHEMA.Integer(lo=0))
@ -142,6 +144,9 @@
# The minimum number of bits for an RSA key. Must be 2048 bits and greater.
RSAKEYBITS_SCHEMA = SCHEMA.Integer(lo=2048)
# A PyCrypto signature.
PYCRYPTOSIGNATURE_SCHEMA = SCHEMA.AnyString()
# An RSA key in PEM format.
PEMRSA_SCHEMA = SCHEMA.AnyString()
@ -155,26 +160,52 @@
# key identifier ('rsa', 233df889cb). For RSA keys, the key value is a pair of
# public and private keys in PEM Format stored as strings.
KEYVAL_SCHEMA = SCHEMA.Object(
object_name='keyval',
object_name='KEYVAL_SCHEMA',
public=SCHEMA.AnyString(),
private=SCHEMA.AnyString())
# A generic key. All TUF keys should be saved to metadata files in this format.
# Supported TUF key types.
KEYTYPE_SCHEMA = SCHEMA.OneOf(
[SCHEMA.String('rsa'), SCHEMA.String('ed25519')])
# A generic TUF key. All TUF keys should be saved to metadata files in this
# format.
KEY_SCHEMA = SCHEMA.Object(
object_name='key',
object_name='KEY_SCHEMA',
keytype=SCHEMA.AnyString(),
keyval=KEYVAL_SCHEMA)
# An RSA key.
# A TUF key object. This schema simplifies validation of keys that may be
# one of the supported key types.
# Supported key types: 'rsa', 'ed25519'.
ANYKEY_SCHEMA = SCHEMA.Object(
object_name='ANYKEY_SCHEMA',
keytype=KEYTYPE_SCHEMA,
keyid=KEYID_SCHEMA,
keyval=KEYVAL_SCHEMA)
# A list of TUF key objects.
ANYKEYLIST_SCHEMA = SCHEMA.ListOf(ANYKEY_SCHEMA)
# An RSA TUF key.
RSAKEY_SCHEMA = SCHEMA.Object(
object_name='rsakey',
object_name='RSAKEY_SCHEMA',
keytype=SCHEMA.String('rsa'),
keyid=KEYID_SCHEMA,
keyval=KEYVAL_SCHEMA)
# An ed25519 key.
# An ED25519 raw public key, which must be 32 bytes.
ED25519PUBLIC_SCHEMA = SCHEMA.LengthString(32)
# An ED25519 raw seed key, which must be 32 bytes.
ED25519SEED_SCHEMA = SCHEMA.LengthString(32)
# An ED25519 raw signature, which must be 64 bytes.
ED25519SIGNATURE_SCHEMA = SCHEMA.LengthString(64)
# An ed25519 TUF key.
ED25519KEY_SCHEMA = SCHEMA.Object(
object_name='ed25519key',
object_name='ED25519KEY_SCHEMA',
keytype=SCHEMA.String('ed25519'),
keyid=KEYID_SCHEMA,
keyval=KEYVAL_SCHEMA)
@ -183,7 +214,7 @@
# This schema allows the storage of multiple hashes for the same file
# (e.g., sha256 and sha512 may be computed for the same file and stored).
FILEINFO_SCHEMA = SCHEMA.Object(
object_name='fileinfo',
object_name='FILEINFO_SCHEMA',
length=LENGTH_SCHEMA,
hashes=HASHDICT_SCHEMA,
custom=SCHEMA.Optional(SCHEMA.Object()))
@ -196,7 +227,7 @@
# A dict holding a target file.
TARGETFILE_SCHEMA = SCHEMA.Object(
object_name='targetfile',
object_name='TARGETFILE_SCHEMA',
filepath=RELPATH_SCHEMA,
fileinfo=FILEINFO_SCHEMA)
TARGETFILES_SCHEMA = SCHEMA.ListOf(TARGETFILE_SCHEMA)
@ -210,18 +241,21 @@
# one can imagine that maybe a key wants to sign multiple times with different
# signature methods.
SIGNATURE_SCHEMA = SCHEMA.Object(
object_name='signature',
object_name='SIGNATURE_SCHEMA',
keyid=KEYID_SCHEMA,
method=SIG_METHOD_SCHEMA,
sig=HEX_SCHEMA)
# List of SIGNATURE_SCHEMA.
SIGNATURES_SCHEMA = SCHEMA.ListOf(SIGNATURE_SCHEMA)
# A schema holding the result of checking the signatures of a particular
# 'SIGNABLE_SCHEMA' role.
# For example, how many of the signatures for the 'Target' role are
# valid? This SCHEMA holds this information. See 'sig.py' for
# more information.
SIGNATURESTATUS_SCHEMA = SCHEMA.Object(
object_name='signaturestatus',
object_name='SIGNATURESTATUS_SCHEMA',
threshold=SCHEMA.Integer(),
good_sigs=SCHEMA.ListOf(KEYID_SCHEMA),
bad_sigs=SCHEMA.ListOf(KEYID_SCHEMA),
@ -231,7 +265,7 @@
# A signable object. Holds the signing role and its associated signatures.
SIGNABLE_SCHEMA = SCHEMA.Object(
object_name='signable',
object_name='SIGNABLE_SCHEMA',
signed=SCHEMA.Any(),
signatures=SCHEMA.ListOf(SIGNATURE_SCHEMA))
@ -255,7 +289,7 @@
# 'remote_directory' entries. See 'tuf/pushtools/pushtoolslib.py' and
# 'tuf/pushtools/push.py'.
SCPCONFIG_SCHEMA = SCHEMA.Object(
object_name='scp_config',
object_name='SCPCONFIG_SCHEMA',
general=SCHEMA.Object(
object_name='[general]',
transfer_module=SCHEMA.String('scp'),
@ -275,8 +309,7 @@
# 'backup_directory' entries.
# see 'tuf/pushtools/pushtoolslib.py' and 'tuf/pushtools/receive/receive.py'
RECEIVECONFIG_SCHEMA = SCHEMA.Object(
object_name='receive_config',
general=SCHEMA.Object(
object_name='RECEIVECONFIG_SCHEMA', general=SCHEMA.Object(
object_name='[general]',
pushroots=SCHEMA.ListOf(PATH_SCHEMA),
repository_directory=PATH_SCHEMA,
@ -286,15 +319,16 @@
# A path hash prefix is a hexadecimal string.
PATH_HASH_PREFIX_SCHEMA = HEX_SCHEMA
# A list of path hash prefixes.
PATH_HASH_PREFIXES_SCHEMA = SCHEMA.ListOf(PATH_HASH_PREFIX_SCHEMA)
# Role object in {'keyids': [keydids..], 'name': 'ABC', 'threshold': 1,
# 'paths':[filepaths..]} # format.
# 'paths':[filepaths..]} format.
ROLE_SCHEMA = SCHEMA.Object(
object_name='role',
keyids=SCHEMA.ListOf(KEYID_SCHEMA),
object_name='ROLE_SCHEMA',
name=SCHEMA.Optional(ROLENAME_SCHEMA),
keyids=SCHEMA.ListOf(KEYID_SCHEMA),
threshold=THRESHOLD_SCHEMA,
paths=SCHEMA.Optional(RELPATHS_SCHEMA),
path_hash_prefixes=SCHEMA.Optional(PATH_HASH_PREFIXES_SCHEMA))
@ -308,37 +342,66 @@
# Like ROLEDICT_SCHEMA, except that ROLE_SCHEMA instances are stored in order.
ROLELIST_SCHEMA = SCHEMA.ListOf(ROLE_SCHEMA)
# The root: indicates root keys and top-level roles.
# The delegated roles of a Targets role (a parent).
DELEGATIONS_SCHEMA = SCHEMA.Object(
keys=KEYDICT_SCHEMA,
roles=ROLELIST_SCHEMA)
# The number of seconds before metadata expires. The minimum is 86400 seconds
# (= 1 day). This schema is used for the initial expiration date. Repository
# maintainers may later modify this value (TIME_SCHEMA).
EXPIRATION_SCHEMA = SCHEMA.Integer(lo=86400)
# Supported compression extension (e.g., 'gz').
COMPRESSION_SCHEMA = SCHEMA.OneOf([SCHEMA.String(''), SCHEMA.String('gz')])
# List of supported compression extensions.
COMPRESSIONS_SCHEMA = SCHEMA.ListOf(
SCHEMA.OneOf([SCHEMA.String(''), SCHEMA.String('gz')]))
# tuf.roledb
ROLEDB_SCHEMA = SCHEMA.Object(
object_name='ROLEDB_SCHEMA',
keyids=SCHEMA.ListOf(KEYID_SCHEMA),
signing_keyids=SCHEMA.Optional(SCHEMA.ListOf(KEYID_SCHEMA)),
threshold=THRESHOLD_SCHEMA,
version=SCHEMA.Optional(METADATAVERSION_SCHEMA),
expires=SCHEMA.Optional(SCHEMA.OneOf([EXPIRATION_SCHEMA, TIME_SCHEMA])),
signatures=SCHEMA.Optional(SCHEMA.ListOf(SIGNATURE_SCHEMA)),
compressions=SCHEMA.Optional(COMPRESSIONS_SCHEMA),
paths=SCHEMA.Optional(RELPATHS_SCHEMA),
path_hash_prefixes=SCHEMA.Optional(PATH_HASH_PREFIXES_SCHEMA),
delegations=SCHEMA.Optional(DELEGATIONS_SCHEMA))
# Root role: indicates root keys and top-level roles.
ROOT_SCHEMA = SCHEMA.Object(
object_name='root',
object_name='ROOT_SCHEMA',
_type=SCHEMA.String('Root'),
version=METADATAVERSION_SCHEMA,
expires=TIME_SCHEMA,
keys=KEYDICT_SCHEMA,
roles=ROLEDICT_SCHEMA)
# Targets. Indicates targets and delegates target paths to other roles.
# Targets role: Indicates targets and delegates target paths to other roles.
TARGETS_SCHEMA = SCHEMA.Object(
object_name='targets',
object_name='TARGETS_SCHEMA',
_type=SCHEMA.String('Targets'),
version=METADATAVERSION_SCHEMA,
expires=TIME_SCHEMA,
targets=FILEDICT_SCHEMA,
delegations=SCHEMA.Optional(SCHEMA.Object(
keys=KEYDICT_SCHEMA,
roles=ROLELIST_SCHEMA)))
delegations=SCHEMA.Optional(DELEGATIONS_SCHEMA))
# A Release: indicates the latest versions of all metadata (except timestamp).
# Release role: indicates the latest versions of all metadata (except timestamp).
RELEASE_SCHEMA = SCHEMA.Object(
object_name='release',
object_name='RELEASE_SCHEMA',
_type=SCHEMA.String('Release'),
version=METADATAVERSION_SCHEMA,
expires=TIME_SCHEMA,
meta=FILEDICT_SCHEMA)
# A Timestamp: indicates the latest version of the release file.
# Timestamp role: indicates the latest version of the release file.
TIMESTAMP_SCHEMA = SCHEMA.Object(
object_name='timestamp',
object_name='TIMESTAMP_SCHEMA',
_type=SCHEMA.String('Timestamp'),
version=METADATAVERSION_SCHEMA,
expires=TIME_SCHEMA,
@ -347,7 +410,7 @@
# A schema containing information a repository mirror may require,
# such as a url, the path of the directory metadata files, etc.
MIRROR_SCHEMA = SCHEMA.Object(
object_name='mirror',
object_name='MIRROR_SCHEMA',
url_prefix=URL_SCHEMA,
metadata_path=RELPATH_SCHEMA,
targets_path=RELPATH_SCHEMA,
@ -365,7 +428,7 @@
# A Mirrorlist: indicates all the live mirrors, and what documents they
# serve.
MIRRORLIST_SCHEMA = SCHEMA.Object(
object_name='mirrorlist',
object_name='MIRRORLIST_SCHEMA',
_type=SCHEMA.String('Mirrors'),
version=METADATAVERSION_SCHEMA,
expires=TIME_SCHEMA,
@ -383,7 +446,6 @@ class MetaFile(object):
and ReleaseFile all inherit from MetaFile. The
__eq__, __ne__, perform 'equal' and 'not equal' comparisons
between Metadata File objects.
"""
info = None
@ -401,7 +463,6 @@ def __getattr__(self, name):
Allow all metafile objects to have their interesting attributes
referred to directly without the info dict. The info dict is just
to be able to do the __eq__ comparison generically.
"""
if name in self.info:
@ -473,14 +534,10 @@ def from_metadata(object):
@staticmethod
def make_metadata(version, expiration_seconds, keydict, roledict):
# Is 'expiration_seconds' properly formatted?
# Raise 'tuf.FormatError' if not.
LENGTH_SCHEMA.check_match(expiration_seconds)
def make_metadata(version, expiration_date, keydict, roledict):
result = {'_type' : 'Root'}
result['version'] = version
result['expires'] = format_time(time.time() + expiration_seconds)
result['expires'] = expiration_date
result['keys'] = keydict
result['roles'] = roledict
@ -645,7 +702,6 @@ def format_time(timestamp):
<Returns>
A string in 'YYYY-MM-DD HH:MM:SS UTC' format.
"""
try:
@ -677,7 +733,6 @@ def parse_time(string):
<Returns>
A timestamp (e.g., 499137660).
"""
# Is 'string' properly formatted?
@ -715,7 +770,6 @@ def format_base64(data):
<Returns>
A base64-encoded string.
"""
try:
@ -746,7 +800,6 @@ def parse_base64(base64_string):
<Returns>
A byte string representing the parsed based64 encoding of
'base64_string'.
"""
if not isinstance(base64_string, basestring):
@ -791,7 +844,6 @@ def make_signable(object):
<Returns>
A dict in 'SIGNABLE_SCHEMA' format.
"""
if not isinstance(object, dict) or 'signed' not in object:
@ -832,7 +884,6 @@ def make_fileinfo(length, hashes, custom=None):
<Returns>
A dictionary conformant to 'FILEINFO_SCHEMA', representing the file
information of a metadata or target file.
"""
fileinfo = {'length' : length, 'hashes' : hashes}
@ -889,7 +940,6 @@ def make_role_metadata(keyids, threshold, name=None, paths=None,
<Returns>
A properly formatted role meta dict, conforming to
'ROLE_SCHEMA'.
"""
role_meta = {}
@ -950,7 +1000,6 @@ def get_role_class(expected_rolename):
The class corresponding to 'expected_rolename'.
E.g., 'Release' as an argument to this function causes
'ReleaseFile' to be returned.
"""
# Does 'expected_rolename' have the correct type?
@ -993,7 +1042,6 @@ def expected_meta_rolename(meta_rolename):
<Returns>
A string (e.g., 'Root', 'Targets').
"""
# Does 'meta_rolename' have the correct type?
@ -1033,7 +1081,6 @@ def check_signable_object_format(object):
<Returns>
A string representing the signing role (e.g., 'root', 'targets').
The role string is returned with characters all lower case.
"""
# Does 'object' have the correct type?
@ -1077,7 +1124,6 @@ def _canonical_string_encoder(string):
<Returns>
A string with the canonical-encoded 'string' embedded.
"""
string = '"%s"' % re.sub(r'(["\\])', r'\\\1', string)
@ -1182,7 +1228,6 @@ def encode_canonical(object, output_function=None):
<Returns>
A string representing the 'object' encoded in canonical JSON form.
"""
result = None

View file

@ -25,15 +25,14 @@
and the '_get_keyid()' function to learn precisely how keyids are generated.
One may get the keyid of a key object by simply accessing the dictionary's
'keyid' key (i.e., rsakey['keyid']).
"""
import logging
import copy
import tuf
import tuf.formats
import tuf.rsa_key
import tuf.keys
# See 'log.py' to learn how logging is handled in TUF.
logger = logging.getLogger('tuf.keydb')
@ -62,13 +61,12 @@ def create_keydb_from_root_metadata(root_metadata):
<Side Effects>
A function to add the key to the database is called. In the case of RSA
keys, this function is add_rsakey().
keys, this function is add_key().
The old keydb key database is replaced.
<Returns>
None.
"""
# Does 'root_metadata' have the correct format?
@ -87,10 +85,10 @@ def create_keydb_from_root_metadata(root_metadata):
if key_metadata['keytype'] == 'rsa':
# 'key_metadata' is stored in 'KEY_SCHEMA' format. Call
# create_from_metadata_format() to get the key in 'RSAKEY_SCHEMA'
# format, which is the format expected by 'add_rsakey()'.
rsakey_dict = tuf.rsa_key.create_from_metadata_format(key_metadata)
# format, which is the format expected by 'add_key()'.
rsakey_dict = tuf.keys.format_metadata_to_key(key_metadata)
try:
add_rsakey(rsakey_dict, keyid)
add_key(rsakey_dict, keyid)
# 'tuf.Error' raised if keyid does not match the keyid for 'rsakey_dict'.
except tuf.Error, e:
logger.error(e)
@ -105,7 +103,7 @@ def create_keydb_from_root_metadata(root_metadata):
def add_rsakey(rsakey_dict, keyid=None):
def add_key(key_dict, keyid=None):
"""
<Purpose>
Add 'rsakey_dict' to the key database while avoiding duplicates.
@ -113,8 +111,8 @@ def add_rsakey(rsakey_dict, keyid=None):
and raise an exception if it is not.
<Arguments>
rsakey_dict:
A dictionary conformant to 'tuf.formats.RSAKEY_SCHEMA'.
key_dict:
A dictionary conformant to 'tuf.formats.ANYKEY_SCHEMA'.
It has the form:
{'keytype': 'rsa',
'keyid': keyid,
@ -138,15 +136,13 @@ def add_rsakey(rsakey_dict, keyid=None):
<Returns>
None.
"""
# Does 'rsakey_dict' have the correct format?
# This check will ensure 'rsakey_dict' has the appropriate number of objects
# and object types, and that all dict keys are properly named.
# Raise 'tuf.FormatError if the check fails.
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
tuf.formats.ANYKEY_SCHEMA.check_match(key_dict)
# Does 'keyid' have the correct format?
if keyid is not None:
@ -154,16 +150,16 @@ def add_rsakey(rsakey_dict, keyid=None):
tuf.formats.KEYID_SCHEMA.check_match(keyid)
# Check if the keyid found in 'rsakey_dict' matches 'keyid'.
if keyid != rsakey_dict['keyid']:
raise tuf.Error('Incorrect keyid '+rsakey_dict['keyid']+' expected '+keyid)
if keyid != key_dict['keyid']:
raise tuf.Error('Incorrect keyid '+key_dict['keyid']+' expected '+keyid)
# Check if the keyid belonging to 'rsakey_dict' is not already
# available in the key database before returning.
keyid = rsakey_dict['keyid']
keyid = key_dict['keyid']
if keyid in _keydb_dict:
raise tuf.KeyAlreadyExistsError('Key: '+keyid)
_keydb_dict[keyid] = rsakey_dict
_keydb_dict[keyid] = copy.deepcopy(key_dict)
@ -190,7 +186,6 @@ def get_key(keyid):
<Returns>
The key matching 'keyid'. In the case of RSA keys, a dictionary conformant
to 'tuf.formats.RSAKEY_SCHEMA' is returned.
"""
# Does 'keyid' have the correct format?
@ -201,7 +196,7 @@ def get_key(keyid):
# Return the key belonging to 'keyid', if found in the key database.
try:
return _keydb_dict[keyid]
return copy.deepcopy(_keydb_dict[keyid])
except KeyError:
raise tuf.UnknownKeyError('Key: '+keyid)
@ -229,7 +224,6 @@ def remove_key(keyid):
<Returns>
None.
"""
# Does 'keyid' have the correct format?
@ -264,7 +258,6 @@ def clear_keydb():
<Returns>
None.
"""
_keydb_dict.clear()

1021
tuf/keys.py Executable file

File diff suppressed because it is too large Load diff

2947
tuf/libtuf.py Executable file

File diff suppressed because it is too large Load diff

466
tuf/pycrypto_keys.py Executable file
View file

@ -0,0 +1,466 @@
"""
<Program Name>
pycrypto_keys.py
<Author>
Vladimir Diaz <vladimir.v.diaz@gmail.com>
<Started>
October 7, 2013.
<Copyright>
See LICENSE for licensing information.
<Purpose>
The goal of this module is to support public-key cryptography and RSA
keys through the PyCrypto library. The RSA-related functions provided:
generate_rsa_public_and_private()
create_rsa_signature()
verify_rsa_signature()
create_rsa_encrypted_pem()
create_rsa_public_and_private_from_encrypted_pem()
PyCrypto (i.e., the 'Crypto' package) performs the actual cryptographic
operations and the functions listed above can be viewed as an easy-to-use
public interface.
https://en.wikipedia.org/wiki/RSA_(algorithm)
https://github.com/dlitz/pycrypto
"""
# Crypto.PublicKey (i.e., PyCrypto's public-key cryptography modules) supports
# algorithms like the Digital Signature Algorithm (DSA) and the ElGamal
# encryption system. 'Crypto.PublicKey.RSA' is needed here to generate, sign,
# and verify RSA keys.
import Crypto.PublicKey.RSA
# PyCrypto requires 'Crypto.Hash' hash objects to generate PKCS#1 PSS
# signatures (i.e., Crypto.Signature.PKCS1_PSS).
import Crypto.Hash.SHA256
# RSA's probabilistic signature scheme with appendix (RSASSA-PSS).
# PKCS#1 v1.5 is available for compatibility with existing applications, but
# RSASSA-PSS is encouraged for newer applications. RSASSA-PSS generates
# a random salt to ensure the signature generated is probabilistic rather than
# deterministic, like PKCS#1 v1.5.
# http://en.wikipedia.org/wiki/RSA-PSS#Schemes
# https://tools.ietf.org/html/rfc3447#section-8.1
import Crypto.Signature.PKCS1_PSS
# Import the TUF package and TUF-defined exceptions in __init__.py.
import tuf
# Digest objects needed to generate hashes.
import tuf.hash
# Perform object format-checking.
import tuf.formats
# Recommended RSA key sizes:
# http://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm#table1
# According to the document above, revised May 6, 2003, RSA keys of
# size 3072 provide security through 2031 and beyond.
_DEFAULT_RSA_KEY_BITS = 3072
def generate_rsa_public_and_private(bits=_DEFAULT_RSA_KEY_BITS):
"""
<Purpose>
Generate public and private RSA keys with modulus length 'bits'.
The public and private keys returned conform to 'tuf.formats.PEMRSA_SCHEMA'
and have the form:
'-----BEGIN RSA PUBLIC KEY----- ...'
or
'-----BEGIN RSA PRIVATE KEY----- ...'
The public and private keys are returned as strings in PEM format.
Although PyCrypto sets a 1024-bit minimum key size,
generate_rsa_public_and_private() enforces a minimum key size of 2048 bits.
If 'bits' is unspecified, a 3072-bit RSA key is generated, which is the key
size recommended by TUF.
>>> public, private = generate_rsa_public_and_private(2048)
>>> tuf.formats.PEMRSA_SCHEMA.matches(public)
True
>>> tuf.formats.PEMRSA_SCHEMA.matches(private)
True
<Arguments>
bits:
The key size, or key length, of the RSA key. 'bits' must be 2048, or
greater, and a multiple of 256.
<Exceptions>
tuf.FormatError, if 'bits' does not contain the correct format.
ValueError, if an exception occurs in the RSA key generation routine.
'bits' must be a multiple of 256. The 'ValueError' exception is raised by
the PyCrypto key generation function.
<Side Effects>
The RSA keys are generated by PyCrypto's Crypto.PublicKey.RSA.generate().
<Returns>
A (public, private) tuple containing the RSA keys in PEM format.
"""
# Does 'bits' have the correct format?
# This check will ensure 'bits' conforms to 'tuf.formats.RSAKEYBITS_SCHEMA'.
# 'bits' must be an integer object, with a minimum value of 2048.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.RSAKEYBITS_SCHEMA.check_match(bits)
# Generate the public and private RSA keys. The PyCrypto module performs
# the actual key generation. Raise 'ValueError' if 'bits' is less than 1024
# or not a multiple of 256, although a 2048-bit minimum is enforced by
# tuf.formats.RSAKEYBITS_SCHEMA.check_match().
rsa_key_object = Crypto.PublicKey.RSA.generate(bits)
# Extract the public & private halves of the RSA key and generate their
# PEM-formatted representations. Return the key pair as a (public, private)
# tuple, where each RSA is a string in PEM format.
private = rsa_key_object.exportKey(format='PEM')
rsa_pubkey = rsa_key_object.publickey()
public = rsa_pubkey.exportKey(format='PEM')
return public, private
def create_rsa_signature(private_key, data):
"""
<Purpose>
Generate an RSASSA-PSS signature. The signature, and the method (signature
algorithm) used, is returned as a (signature, method) tuple.
The signing process will use 'private_key' and 'data' to generate the
signature.
RFC3447 - RSASSA-PSS
http://www.ietf.org/rfc/rfc3447.txt
>>> public, private = generate_rsa_public_and_private(2048)
>>> data = 'The quick brown fox jumps over the lazy dog'
>>> signature, method = create_rsa_signature(private, data)
>>> tuf.formats.NAME_SCHEMA.matches(method)
True
>>> method == 'PyCrypto-PKCS#1 PSS'
True
>>> tuf.formats.PYCRYPTOSIGNATURE_SCHEMA.matches(method)
True
<Arguments>
private_key:
The private RSA key, a string in PEM format.
data:
Data object used by create_rsa_signature() to generate the signature.
<Exceptions>
tuf.FormatError, if 'private_key' is improperly formatted.
TypeError, if 'private_key' is unset.
tuf.CryptoError, if the signature cannot be generated.
<Side Effects>
PyCrypto's 'Crypto.Signature.PKCS1_PSS' called to generate the signature.
<Returns>
A (signature, method) tuple, where the signature is a string and the method
is 'PyCrypto-PKCS#1 PSS'.
"""
# Does 'private_key' have the correct format?
# This check will ensure 'private_key' conforms to 'tuf.formats.PEMRSA_SCHEMA'.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.PEMRSA_SCHEMA.check_match(private_key)
# Signing the 'data' object requires a private key.
# The 'PyCrypto-PKCS#1 PSS' (i.e., PyCrypto module) signing method is the
# only method currently supported.
method = 'PyCrypto-PKCS#1 PSS'
signature = None
# Verify the signature, but only if the private key has been set. The private
# key is a NULL string if unset. Although it may be clearer to 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)
pkcs1_pss_signer = Crypto.Signature.PKCS1_PSS.new(rsa_key_object)
signature = pkcs1_pss_signer.sign(sha256_object)
except (ValueError, IndexError, TypeError), e:
message = 'An RSA signature could not be generated.'
raise tuf.CryptoError(message)
else:
raise TypeError('The required private key is unset.')
return signature, method
def verify_rsa_signature(signature, signature_method, public_key, data):
"""
<Purpose>
Determine whether the corresponding private key of 'public_key' produced
'signature'. verify_signature() will use the public key, signature method,
and 'data' to complete the verification.
>>> public, private = generate_rsa_public_and_private(2048)
>>> data = 'The quick brown fox jumps over the lazy dog'
>>> signature, method = create_rsa_signature(private, data)
>>> verify_rsa_signature(signature, method, public, data)
True
>>> verify_rsa_signature(signature, method, public, 'bad_data')
False
<Arguments>
signature:
An RSASSA PSS signature as a string. This is the signature returned
by create_rsa_signature().
signature_method:
A string that indicates the signature algorithm used to generate
'signature'. 'PyCrypto-PKCS#1 PSS' is currently supported.
public_key:
The RSA public key, a string in PEM format.
data:
Data object used by tuf.rsa_key.create_signature() to generate
'signature'. 'data' is needed here to verify the signature.
<Exceptions>
tuf.UnknownMethodError. Raised if the signing method used by
'signature' is not one supported by tuf.rsa_key.create_signature().
tuf.FormatError. Raised if 'signature', 'signature_method', or 'public_key'
is improperly formatted.
<Side Effects>
Crypto.Signature.PKCS1_PSS.verify() called to do the actual verification.
<Returns>
Boolean. True if the signature is valid, False otherwise.
"""
# Does 'public_key' have the correct format?
# This check will ensure 'public_key' conforms to 'tuf.formats.PEMRSA_SCHEMA'.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.PEMRSA_SCHEMA.check_match(public_key)
# Does 'signature_method' have the correct format?
tuf.formats.NAME_SCHEMA.check_match(signature_method)
# Does 'signature' have the correct format?
tuf.formats.PYCRYPTOSIGNATURE_SCHEMA.check_match(signature)
# Verify whether the private key of 'public_key' produced the signature.
# Before returning the Boolean result, ensure 'PyCrypto-PKCS#1 PSS' was used
# as the signing method.
signature = signature
method = signature_method
public = public_key
valid_signature = False
# Verify the signature with PyCrypto if the signature method is valid, else
# raise 'tuf.UnknownMethodError'.
if method == 'PyCrypto-PKCS#1 PSS':
try:
rsa_key_object = Crypto.PublicKey.RSA.importKey(public_key)
pkcs1_pss_verifier = Crypto.Signature.PKCS1_PSS.new(rsa_key_object)
sha256_object = Crypto.Hash.SHA256.new(data)
valid_signature = pkcs1_pss_verifier.verify(sha256_object, signature)
except (ValueError, IndexError, TypeError), e:
message = 'The RSA signature could not be verified.'
raise tuf.CryptoError(message)
else:
raise tuf.UnknownMethodError(method)
return valid_signature
def create_rsa_encrypted_pem(private_key, passphrase):
"""
<Purpose>
Return a string in PEM format, where the private part of the RSA key is
encrypted. The private part of the RSA key is encrypted by the Triple
Data Encryption Algorithm (3DES) and Cipher-block chaining (CBC) for the
mode of operation. Password-Based Key Derivation Function 1 (PBKF1) + MD5
is used to strengthen 'passphrase'.
https://en.wikipedia.org/wiki/Triple_DES
https://en.wikipedia.org/wiki/PBKDF2
>>> public, private = generate_rsa_public_and_private(2048)
>>> passphrase = 'secret'
>>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase)
>>> tuf.formats.PEMRSA_SCHEMA.matches(encrypted_pem)
True
<Arguments>
private_key:
The private key string in PEM format.
passphrase:
The passphrase, or password, to encrypt the private part of the RSA
key. 'passphrase' is not used directly as the encryption key, a stronger
encryption key is derived from it.
<Exceptions>
tuf.FormatError, if the arguments are improperly formatted.
tuf.CryptoError, if an RSA key in encrypted PEM format cannot be created.
TypeError, 'private_key' is unset.
<Side Effects>
PyCrypto's Crypto.PublicKey.RSA.exportKey() called to perform the actual
generation of the PEM-formatted output.
<Returns>
A string in PEM format, where the private RSA key is encrypted.
Conforms to 'tuf.formats.PEMRSA_SCHEMA'.
"""
# Does 'private_key' have the correct format?
# This check will ensure 'private_key' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.PEMRSA_SCHEMA.check_match(private_key)
# Does 'passphrase' have the correct format?
tuf.formats.PASSWORD_SCHEMA.check_match(passphrase)
# 'private_key' is in PEM format and unencrypted. The extracted key will be
# imported and converted to PyCrypto's RSA key object
# (i.e., Crypto.PublicKey.RSA). Use PyCrypto's exportKey method, with a
# passphrase specified, to create the string. PyCrypto uses PBKDF1+MD5 to
# strengthen 'passphrase', and 3DES with CBC mode for encryption.
# 'private_key' may still be a NULL string after the tuf.formats check.
if len(private_key):
try:
rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)
encrypted_pem = rsa_key_object.exportKey(format='PEM', passphrase=passphrase)
except (ValueError, IndexError, TypeError), e:
message = 'An encrypted RSA key in PEM format could not be generated.'
raise tuf.CryptoError(message)
else:
raise TypeError('The required private key is unset.')
return encrypted_pem
def create_rsa_public_and_private_from_encrypted_pem(encrypted_pem, passphrase):
"""
<Purpose>
Generate public and private RSA keys from an encrypted PEM.
The public and private keys returned conform to 'tuf.formats.PEMRSA_SCHEMA'
and have the form:
'-----BEGIN RSA PUBLIC KEY----- ...'
or
'-----BEGIN RSA PRIVATE KEY----- ...'
The public and private keys are returned as strings in PEM format.
The private key part of 'encrypted_pem' is encrypted. PyCrypto's importKey
method is used, where a passphrase is specified. PyCrypto uses PBKDF1+MD5
to strengthen 'passphrase', and 3DES with CBC mode for encryption/decryption.
Alternatively, key data may be encrypted with AES-CTR-Mode and the passphrase
strengthened with PBKDF2+SHA256. See 'keystore.py'.
>>> public, private = generate_rsa_public_and_private(2048)
>>> passphrase = 'secret'
>>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase)
>>> returned_public, returned_private = \
create_rsa_public_and_private_from_encrypted_pem(encrypted_pem, passphrase)
>>> tuf.formats.PEMRSA_SCHEMA.matches(returned_public)
True
>>> tuf.formats.PEMRSA_SCHEMA.matches(returned_private)
True
>>> public == returned_public
True
>>> private == returned_private
True
<Arguments>
encrypted_pem:
A byte string in PEM format, where the private key is encrypted. It has
the form:
'-----BEGIN RSA PRIVATE KEY-----\n
Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC ...'
passphrase:
The passphrase, or password, to decrypt the private part of the RSA
key. 'passphrase' is not directly used as the encryption key, instead
it is used to derive a stronger symmetric key.
<Exceptions>
tuf.FormatError, if the arguments are improperly formatted.
<Side Effects>
PyCrypto's 'Crypto.PublicKey.RSA.importKey()' called to perform the actual
conversion from an encrypted RSA private key.
<Returns>
A (public, private) tuple containing the RSA keys in PEM format.
"""
# Does 'encryped_pem' have the correct format?
# This check will ensure 'encrypted_pem' 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.PEMRSA_SCHEMA.check_match(encrypted_pem)
# Does 'passphrase' have the correct format?
tuf.formats.PASSWORD_SCHEMA.check_match(passphrase)
try:
rsa_key_object = Crypto.PublicKey.RSA.importKey(encrypted_pem, passphrase)
except (ValueError, IndexError, TypeError), e:
message = 'An RSA key object could not be generated from the encrypted '+\
'PEM string.'
# Raise 'tuf.CryptoError' instead of PyCrypto's exception to avoid
# revealing sensitive error, such as a decryption error due to an
# invalid passphrase.
raise tuf.CryptoError(message)
# Extract the public and private halves of the RSA key and generate their
# PEM-formatted representations. The dictionary returned contains the
# private and public RSA keys in PEM format, as strings.
private = rsa_key_object.exportKey(format='PEM')
rsa_pubkey = rsa_key_object.publickey()
public = rsa_pubkey.exportKey(format='PEM')
return public, private
if __name__ == '__main__':
# The interactive sessions of the documentation strings can
# be tested by running 'pycrypto_keys.py' as a standalone module.
# python -B pycrypto_keys.py
import doctest
doctest.testmod()

View file

@ -34,7 +34,6 @@
algorithm. User passwords are strengthened with PBKDF2, currently set to
100,000 passphrase iterations. The previous evpy implementation used 1,000
iterations.
"""
import os
@ -68,7 +67,7 @@
# the AES algorithm to perform cipher block operations on them.
import Crypto.Util.Counter
import tuf.rsa_key
import tuf.keys
import tuf.util
import tuf.conf
@ -104,6 +103,9 @@
# https://en.wikipedia.org/wiki/PBKDF2
_PBKDF2_ITERATIONS = tuf.conf.PBKDF2_ITERATIONS
#
_SUPPORTED_KEY_TYPES = ['rsa', 'ed25519']
# 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:
@ -159,7 +161,6 @@ def add_rsakey(rsakey_dict, password, keyid=None):
<Returns>
None.
"""
# Does 'rsakey_dict' have the correct format?
@ -235,7 +236,6 @@ def load_keystore_from_keyfiles(directory_name, keyids, passwords):
<Returns>
A list containing the keyids of the loaded keys.
"""
# Does 'directory_name' have the correct format?
@ -286,11 +286,11 @@ def load_keystore_from_keyfiles(directory_name, keyids, passwords):
# Create the key based on its key type. RSA keys currently
# supported.
if keydata['keytype'] == 'rsa':
if keydata['keytype'] in _SUPPORTED_KEY_TYPES:
# 'keydata' is stored in KEY_SCHEMA format. Call
# create_from_metadata_format() to get the key in RSAKEY_SCHEMA
# format, which is the format expected by 'add_rsakey()'.
rsa_key = tuf.rsa_key.create_from_metadata_format(keydata)
rsa_key = tuf.keys.create_from_metadata_format(keydata)
# Ensure the keyid for 'rsa_key' is one of the keys specified in
# 'keyids'. If not, do not load the key.
@ -343,7 +343,6 @@ def save_keystore_to_keyfiles(directory_name):
<Returns>
None.
"""
# Does 'directory_name' have the correct format?
@ -365,9 +364,11 @@ def save_keystore_to_keyfiles(directory_name):
file_object = open(basefilename, 'w')
# Determine the appropriate format to save the key based on its key type.
if key['keytype'] == 'rsa':
if key['keytype'] in _SUPPORTED_KEY_TYPES:
keytype = key['keytype']
keyval = key['keyval']
key_metadata_format = \
tuf.rsa_key.create_in_metadata_format(key['keyval'], private=True)
tuf.keys.create_in_metadata_format(keytype, keyval, private=True)
else:
logger.warn('The keystore has a key with an unrecognized key type.')
continue
@ -402,7 +403,6 @@ def clear_keystore():
<Returns>
None.
"""
_keystore.clear()
@ -442,7 +442,6 @@ def change_password(keyid, old_password, new_password):
<Returns>
None.
"""
# Does 'keyid' have the correct format?
@ -506,7 +505,6 @@ def get_key(keyid):
<Returns>
The key belonging to 'keyid' (e.g., RSA key).
"""
# Does 'keyid' have the correct format?
@ -530,7 +528,6 @@ def _generate_derived_key(password, salt=None, iterations=None):
Derivation Function (PBKDF2). PyCrypto's PBKDF2 implementation is
currently used. 'salt' may be specified so that a previous derived key
may be regenerated.
"""
if salt is None:
@ -584,7 +581,6 @@ def _encrypt(key_data, derived_key_information):
'iterations': '...'}
'tuf.CryptoError' raised if the encryption fails.
"""
# Generate a random initialization vector (IV). The 'iv' is treated as the
@ -650,7 +646,6 @@ def _decrypt(file_contents, password):
The corresponding decryption routine for _encrypt().
'tuf.CryptoError' raised if the decryption fails.
"""
# Extract the salt, iterations, hmac, initialization vector, and ciphertext

View file

@ -46,7 +46,6 @@
<Options>
See the parse_options() function for the full list of supported options.
"""
import os
@ -92,7 +91,6 @@ def _get_password(prompt='Password: ', confirm=False):
is True, the user is asked to enter the previously
entered password once again. If they match, the
password is returned to the caller.
"""
while True:
@ -131,7 +129,6 @@ def _get_metadata_directory():
returned to the caller. 'tuf.FormatError' is raised
if the directory is not properly formatted, and 'tuf.Error'
if it does not exist.
"""
metadata_directory = _prompt('\nEnter the metadata directory: ', str)
@ -151,7 +148,6 @@ def _list_keyids(keystore_directory, metadata_directory):
It is assumed the directory arguments exist and have been validated by
the caller. The keyids are listed without the '.key' extension,
along with their associated roles.
"""
# Determine the 'root.txt' filename. This metadata file is needed
@ -233,7 +229,6 @@ def _get_keyids(keystore_directory):
key files are stored in encrypted form, the user is asked
to enter the password that was used to encrypt the key
file.
"""
# The keyids list containing the keys loaded.
@ -288,7 +283,6 @@ def _get_all_config_keyids(config_filepath, keystore_directory):
loaded_keyids = {'root': [1233d3d, 598djdks, ..],
'release': [sdfsd323, sdsd9090s, ..]
...}
"""
# Save the 'load_keystore_from_keyfiles' function call.
@ -338,7 +332,6 @@ def _get_role_config_keyids(config_filepath, keystore_directory, role):
<Exceptions>
tuf.Error, if the required keys could not be loaded.
"""
# Save the 'load_keystore_from_keyfiles' function call.
@ -409,7 +402,6 @@ def _get_metadata_version(metadata_filename):
'metadata_filename' does not exist, return a version value of 1.
Raise 'tuf.RepositoryError' if 'metadata_filename' cannot be read or
validated.
"""
# If 'metadata_filename' does not exist on the repository, this means
@ -442,7 +434,6 @@ def _get_metadata_expiration():
<Exceptions>
tuf.RepositoryError, if the entered expiration date is invalid.
"""
message = '\nCurrent time: '+tuf.formats.format_time(time.time())+'.\n'+\
@ -487,7 +478,6 @@ def change_password(keystore_directory):
<Returns>
None.
"""
# Save the 'load_keystore_from_keyfiles' function call.
@ -563,7 +553,6 @@ def generate_rsa_key(keystore_directory):
<Returns>
None.
"""
# Save a reference to the generate_and_save_rsa_key() function.
@ -612,7 +601,6 @@ def list_signing_keys(keystore_directory):
<Returns>
None.
"""
# Verify the 'keystore_directory' argument.
@ -654,7 +642,6 @@ def dump_key(keystore_directory):
<Returns>
None.
"""
# Save the 'load_keystore_from_keyfiles' function call.
@ -704,8 +691,10 @@ def dump_key(keystore_directory):
# Retrieve the key metadata according to the keytype.
if key['keytype'] == 'rsa':
key_metadata = tuf.rsa_key.create_in_metadata_format(key['keyval'],
private=show_private)
keytype = key['keytype']
keyval = key['keyval']
key_metadata = tuf.keys.create_in_metadata_format(keytype, keyval,
private=show_private)
else:
message = 'The keystore contains an invalid key type.'
raise tuf.RepositoryError(message)
@ -737,7 +726,6 @@ def make_root_metadata(keystore_directory):
<Returns>
None.
"""
# Verify the 'keystore_directory' argument.
@ -804,7 +792,6 @@ def make_targets_metadata(keystore_directory):
<Returns>
None.
"""
# Verify the 'keystore_directory' argument.
@ -886,7 +873,6 @@ def make_release_metadata(keystore_directory):
<Returns>
None.
"""
# Verify the 'keystore_directory' argument.
@ -951,7 +937,6 @@ def make_timestamp_metadata(keystore_directory):
<Returns>
None.
"""
# Verify the 'keystore_directory' argument.
@ -1017,7 +1002,6 @@ def sign_metadata_file(keystore_directory):
<Returns>
None.
"""
# Verify the 'keystore_directory' argument.
@ -1084,7 +1068,6 @@ def make_delegation(keystore_directory):
<Returns>
None.
"""
# Verify the 'keystore_directory' argument.
@ -1154,7 +1137,6 @@ def _load_parent_role(metadata_directory, keystore_directory, targets_roles):
list of known targets roles and asked to enter the parent role to load.
Ensure the parent role is loaded properly and return a string containing
the parent role's full rolename and a list of keyids belonging to the parent.
"""
# 'load_key' is a reference to the 'load_keystore_from_keyfiles function'.
@ -1210,7 +1192,6 @@ def _get_delegated_role(keystore_directory, metadata_directory):
a list of keyids available in the keystore and asked to enter the keyid
belonging to the delegated role. Return a string containing
the delegated role's full rolename and its keyids.
"""
# Retrieve the delegated rolename from the user (e.g., 'role1').
@ -1240,7 +1221,6 @@ def _make_delegated_metadata(metadata_directory, delegated_targets,
role. Determine the target files from the paths in 'delegated_targets'
and the other information needed to generate the targets metadata file for
delegated_role'. Return the delegated paths to the caller.
"""
repository_directory, junk = os.path.split(metadata_directory)
@ -1336,7 +1316,6 @@ def _update_parent_metadata(metadata_directory, delegated_role,
metadata file is updated with the key and role information belonging
to the newly added delegated role. Finally, the metadata file
is signed and written to the metadata directory.
"""
# According to the specification, the 'paths' and 'path_hash_prefixes'
@ -1376,8 +1355,9 @@ def _update_parent_metadata(metadata_directory, delegated_role,
# Retrieve the key belonging to 'delegated_keyid' from the keystore.
role_key = tuf.repo.keystore.get_key(delegated_keyid)
if role_key['keytype'] == 'rsa':
keytype = role_key['keytype']
keyval = role_key['keyval']
keys[delegated_keyid] = tuf.rsa_key.create_in_metadata_format(keyval)
keys[delegated_keyid] = tuf.keys.create_in_metadata_format(keytype, keyval)
else:
message = 'Invalid keytype encountered: '+delegated_keyid+'\n'
raise tuf.RepositoryError(message)
@ -1450,7 +1430,6 @@ def process_option(options):
<Returns>
None.
"""
# Determine which option was chosen and call its corresponding
@ -1506,7 +1485,6 @@ def parse_options():
<Returns>
The options object returned by the parser's parse_args() method.
"""
usage = 'usage: %prog [option] <keystore_directory>'

View file

@ -16,7 +16,6 @@
These functions contain code that can extract or create needed repository
data, such as the extraction of role and keyid information from a config file,
and the generation of actual metadata content.
"""
import gzip
@ -27,7 +26,7 @@
import tuf
import tuf.formats
import tuf.hash
import tuf.rsa_key
import tuf.keys
import tuf.repo.keystore
import tuf.sig
import tuf.util
@ -81,7 +80,6 @@ def read_config_file(filename):
<Returns>
A dictionary containing the data loaded from the configuration file.
"""
# Does 'filename' have the correct format?
@ -151,7 +149,6 @@ def get_metadata_file_info(filename):
A dictionary conformant to 'tuf.formats.FILEINFO_SCHEMA'. This
dictionary contains the length, hashes, and custom data about
the 'filename' metadata file.
"""
# Does 'filename' have the correct format?
@ -204,7 +201,6 @@ def get_metadata_filenames(metadata_directory=None):
<Returns>
A dictionary containing the expected filenames of the top-level
metadata files, such as 'root.txt' and 'release.txt'.
"""
if metadata_directory is None:
@ -255,7 +251,6 @@ def generate_root_metadata(config_filepath, version):
<Returns>
A root 'signable' object conformant to 'tuf.formats.SIGNABLE_SCHEMA'.
"""
# Does 'config_filepath' have the correct format?
@ -290,8 +285,10 @@ def generate_root_metadata(config_filepath, version):
keyid = key['keyid']
# This appears to be a new keyid. Let's generate the key for it.
if keyid not in keydict:
if key['keytype'] == 'rsa':
keydict[keyid] = tuf.rsa_key.create_in_metadata_format(key['keyval'])
if key['keytype'] in ['rsa', 'ed25519']:
keytype = key['keytype']
keyval = key['keyval']
keydict[keyid] = tuf.keys.create_in_metadata_format(keytype, keyval)
# This is not a recognized key. Raise an exception.
else:
raise tuf.Error('Unsupported keytype: '+keyid)
@ -364,7 +361,6 @@ def generate_targets_metadata(repository_directory, target_files, version,
<Returns>
A targets 'signable' object, conformant to 'tuf.formats.SIGNABLE_SCHEMA'.
"""
# Do the arguments have the correct format.
@ -433,7 +429,6 @@ def generate_release_metadata(metadata_directory, version, expiration_date):
<Returns>
The release 'signable' object, conformant to 'tuf.formats.SIGNABLE_SCHEMA'.
"""
# Does 'metadata_directory' have the correct format?
@ -510,7 +505,6 @@ def generate_timestamp_metadata(release_filename, version,
<Returns>
A timestamp 'signable' object, conformant to 'tuf.formats.SIGNABLE_SCHEMA'.
"""
# Do the arguments have the correct format?
@ -575,7 +569,6 @@ def write_metadata_file(metadata, filename, compression=None):
<Returns>
The path to the written metadata file.
"""
# Are the arguments properly formatted?
@ -645,7 +638,6 @@ def read_metadata_file(filename):
<Returns>
The metadata object.
"""
return tuf.util.load_json_file(filename)
@ -684,7 +676,6 @@ def sign_metadata(metadata, keyids, filename):
<Returns>
A signable object conformant to 'tuf.formats.SIGNABLE_SCHEMA'.
"""
# Does 'keyids' and 'filename' have the correct format?
@ -767,7 +758,6 @@ def generate_and_save_rsa_key(keystore_directory, password,
'keyid': keyid,
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
"""
# Are the arguments correctly formatted?
@ -778,7 +768,7 @@ def generate_and_save_rsa_key(keystore_directory, password,
keystore_directory = check_directory(keystore_directory)
# tuf.FormatError or tuf.CryptoError raised.
rsakey = tuf.rsa_key.generate(bits)
rsakey = tuf.keys.generate_rsa_key(bits)
logger.info('Generated a new key: '+rsakey['keyid'])
@ -820,7 +810,6 @@ def check_directory(directory):
<Returns>
The normalized absolutized path of 'directory'.
"""
# Does 'directory' have the correct format?
@ -868,7 +857,6 @@ def get_target_keyids(metadata_directory):
A dictionary containing the role information extracted from the
metadata.
Ex: {'targets':[keyid1, ...], 'targets/role1':[keyid], ...}
"""
# Does 'metadata_directory' have the correct format?
@ -964,7 +952,6 @@ def build_config_file(config_file_directory, timeout, role_info):
<Returns>
The normalized absolutized path of the saved configuration file.
"""
# Do the arguments have the correct format?
@ -1054,7 +1041,6 @@ def build_root_file(config_filepath, root_keyids, metadata_directory, version):
<Returns>
The path for the written root metadata file.
"""
# Do the arguments have the correct format?
@ -1116,7 +1102,6 @@ def build_targets_file(target_paths, targets_keyids, metadata_directory,
<Returns>
The path for the written targets metadata file.
"""
# Do the arguments have the correct format?
@ -1207,7 +1192,6 @@ def build_release_file(release_keyids, metadata_directory,
<Returns>
The path for the written release metadata file.
"""
# Do the arguments have the correct format?
@ -1282,7 +1266,6 @@ def build_timestamp_file(timestamp_keyids, metadata_directory,
<Returns>
The path for the written timestamp metadata file.
"""
# Do the arguments have the correct format?
@ -1370,7 +1353,6 @@ def build_delegated_role_file(delegated_targets_directory, delegated_keyids,
<Returns>
The path for the written targets metadata file.
"""
# Do the arguments have the correct format?
@ -1432,7 +1414,6 @@ def find_delegated_role(roles, delegated_role):
<Returns>
None, if the role with the given name does not exist, or its unique index
in the list of roles.
"""
# Check argument types.
@ -1487,7 +1468,6 @@ def accept_any_file(full_target_path):
<Returns>
True.
"""
return True
@ -1524,7 +1504,6 @@ def get_targets(files_directory, recursive_walk=False, followlinks=True,
<Returns>
A list of absolute paths to target files in the given files_directory.
"""
targets = []
@ -1546,8 +1525,3 @@ def get_targets(files_directory, recursive_walk=False, followlinks=True,
del dirnames[:]
return targets

View file

@ -23,13 +23,20 @@
The role database is a dictionary conformant to 'tuf.formats.ROLEDICT_SCHEMA'
and has the form:
{'rolename': {'keyids': ['34345df32093bd12...'],
'threshold': 1
'paths': ['path/to/role.txt']}}
'signatures': ['abcd3452...'],
'paths': ['path/to/role.txt'],
'path_hash_prefixes': ['ab34df13'],
'delegations': {'keys': {}, 'roles': {}}}
The 'name', 'paths', 'path_hash_prefixes', and 'delegations' dict keys are
optional.
"""
import logging
import copy
import tuf
import tuf.formats
@ -66,7 +73,6 @@ def create_roledb_from_root_metadata(root_metadata):
<Returns>
None.
"""
# Does 'root_metadata' have the correct object format?
@ -81,6 +87,16 @@ def create_roledb_from_root_metadata(root_metadata):
# Iterate through the roles found in 'root_metadata'
# and add them to '_roledb_dict'. Duplicates are avoided.
for rolename, roleinfo in root_metadata['roles'].items():
if rolename == 'root':
roleinfo['version'] = root_metadata['version']
roleinfo['expires'] = root_metadata['expires']
roleinfo['signatures'] = []
roleinfo['signing_keyids'] = []
roleinfo['compressions'] = ['']
if rolename.startswith('targets'):
roleinfo['delegations'] = {'keys': {}, 'roles': []}
try:
add_role(rolename, roleinfo)
# tuf.Error raised if the parent role of 'rolename' does not exist.
@ -104,10 +120,17 @@ def add_role(rolename, roleinfo, require_parent=True):
roleinfo:
An object representing the role associated with 'rolename', conformant to
ROLE_SCHEMA. 'roleinfo' has the form:
ROLEDB_SCHEMA. 'roleinfo' has the form:
{'keyids': ['34345df32093bd12...'],
'threshold': 1}
'threshold': 1,
'signatures': ['ab23dfc32']
'paths': ['path/to/target1', 'path/to/target2', ...],
'path_hash_prefixes': ['a324fcd...', ...],
'delegations': {'keys': }
The 'paths', 'path_hash_prefixes', and 'delegations' dict keys are
optional.
The 'target' role has an additional 'paths' key. Its value is a list of
strings representing the path of the target file(s).
@ -128,7 +151,6 @@ def add_role(rolename, roleinfo, require_parent=True):
<Returns>
None.
"""
# Does 'rolename' have the correct object format?
@ -137,7 +159,7 @@ def add_role(rolename, roleinfo, require_parent=True):
tuf.formats.ROLENAME_SCHEMA.check_match(rolename)
# Does 'roleinfo' have the correct object format?
tuf.formats.ROLE_SCHEMA.check_match(roleinfo)
tuf.formats.ROLEDB_SCHEMA.check_match(roleinfo)
# Does 'require_parent' have the correct format?
tuf.formats.TOGGLE_SCHEMA.check_match(require_parent)
@ -157,12 +179,71 @@ def add_role(rolename, roleinfo, require_parent=True):
if parent_role not in _roledb_dict:
raise tuf.Error('Parent role does not exist: '+parent_role)
_roledb_dict[rolename] = roleinfo
_roledb_dict[rolename] = copy.deepcopy(roleinfo)
def update_roleinfo(rolename, roleinfo):
"""
<Purpose>
<Arguments>
rolename:
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
(e.g., 'root', 'release', 'timestamp').
roleinfo:
An object representing the role associated with 'rolename', conformant to
ROLEDB_SCHEMA. 'roleinfo' has the form:
{'name': 'role_name',
'keyids': ['34345df32093bd12...'],
'threshold': 1,
'paths': ['path/to/target1', 'path/to/target2', ...],
'path_hash_prefixes': ['a324fcd...', ...]}
The 'name', 'paths', and 'path_hash_prefixes' dict keys are optional.
The 'target' role has an additional 'paths' key. Its value is a list of
strings representing the path of the target file(s).
<Exceptions>
tuf.FormatError, if 'rolename' or 'roleinfo' does not have the correct
object format.
tuf.UnknownRoleError, if 'rolename' cannot be found in the role database.
tuf.InvalidNameError, if 'rolename' is improperly formatted.
<Side Effects>
The role database is modified.
<Returns>
None.
"""
# Does 'rolename' have the correct object format?
# This check will ensure 'rolename' has the appropriate number of objects
# and object types, and that all dict keys are properly named.
tuf.formats.ROLENAME_SCHEMA.check_match(rolename)
# Does 'roleinfo' have the correct object format?
tuf.formats.ROLEDB_SCHEMA.check_match(roleinfo)
# Raises tuf.InvalidNameError.
_validate_rolename(rolename)
if rolename not in _roledb_dict:
raise tuf.UnknownRoleError('Role does not exist: '+rolename)
_roledb_dict[rolename] = copy.deepcopy(roleinfo)
def get_parent_rolename(rolename):
"""
<Purpose>
@ -187,7 +268,6 @@ def get_parent_rolename(rolename):
<Returns>
A string representing the name of the parent role.
"""
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
@ -228,7 +308,6 @@ def get_all_parent_roles(rolename):
<Returns>
A list containing all the parent roles.
"""
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
@ -279,7 +358,6 @@ def role_exists(rolename):
<Returns>
Boolean. True if 'rolename' is found in the role database, False otherwise.
"""
# Raise tuf.FormatError, tuf.InvalidNameError.
@ -318,7 +396,6 @@ def remove_role(rolename):
<Returns>
None.
"""
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
@ -358,7 +435,6 @@ def remove_delegated_roles(rolename):
<Returns>
None.
"""
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
@ -390,7 +466,6 @@ def get_rolenames():
<Returns>
A list of rolenames.
"""
return _roledb_dict.keys()
@ -399,6 +474,47 @@ def get_rolenames():
def get_roleinfo(rolename):
"""
<Purpose>
Return the roleinfo of 'rolename'.
{'keyids': ['34345df32093bd12...'],
'threshold': 1,
'signatures': ['ab453bdf...', ...],
'paths': ['path/to/target1', 'path/to/target2', ...],
'path_hash_prefixes': ['a324fcd...', ...],
'delegations': {'keys': {}, 'roles': []}}
The 'signatures', 'paths', 'path_hash_prefixes', and 'delegations' dict keys
are optional.
<Arguments>
rolename:
An object representing the role's name, conformant to 'ROLENAME_SCHEMA'
(e.g., 'root', 'release', 'timestamp').
<Exceptions>
tuf.FormatError, if 'rolename' is improperly formatted.
tuf.UnknownRoleError, if 'rolename' does not exist.
<Side Effects>
None.
<Returns>
The roleinfo of 'rolename'.
"""
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
_check_rolename(rolename)
return copy.deepcopy(_roledb_dict[rolename])
def get_role_keyids(rolename):
"""
<Purpose>
@ -426,7 +542,6 @@ def get_role_keyids(rolename):
<Returns>
A list of keyids.
"""
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
@ -462,7 +577,6 @@ def get_role_threshold(rolename):
<Returns>
A threshold integer value.
"""
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
@ -498,7 +612,6 @@ def get_role_paths(rolename):
<Returns>
A list of paths.
"""
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
@ -540,8 +653,7 @@ def get_delegated_rolenames(rolename):
<Returns>
A list of rolenames. Note that the rolenames are *NOT* sorted by order of
delegation!
delegation.
"""
# Raises tuf.FormatError, tuf.UnknownRoleError, or tuf.InvalidNameError.
@ -578,7 +690,6 @@ def clear_roledb():
<Returns>
None.
"""
_roledb_dict.clear()
@ -593,7 +704,6 @@ def _check_rolename(rolename):
'tuf.formats.ROLENAME_SCHEMA', tuf.UnknownRoleError if 'rolename' is not
found in the role database, or tuf.InvalidNameError if 'rolename' is
not formatted correctly.
"""
# Does 'rolename' have the correct object format?
@ -616,7 +726,6 @@ def _validate_rolename(rolename):
Raise tuf.InvalidNameError if 'rolename' is not formatted correctly.
It is assumed 'rolename' has been checked against 'ROLENAME_SCHEMA'
prior to calling this function.
"""
if rolename == '':

View file

@ -1,664 +0,0 @@
"""
<Program Name>
rsa_key.py
<Author>
Vladimir Diaz <vladimir.v.diaz@gmail.com>
<Started>
March 9, 2012. Based on a previous version of this module by Geremy Condra.
<Copyright>
See LICENSE for licensing information.
<Purpose>
The goal of this module is to support public-key cryptography using the RSA
algorithm. The RSA-related functions provided include generate(),
create_signature(), and verify_signature(). The create_encrypted_pem() and
create_from_encrypted_pem() functions are optional, and may be used save a
generated RSA key to a file. The 'PyCrypto' package used by 'rsa_key.py'
generates the actual RSA keys and the functions listed above can be viewed
as an easy-to-use public interface. Additional functions contained here
include create_in_metadata_format() and create_from_metadata_format(). These
last two functions produce or use RSA keys compatible with the key structures
listed in TUF Metadata files. The generate() function returns a dictionary
containing all the information needed of RSA keys, such as public and private=
keys, keyIDs, and an idenfier. create_signature() and verify_signature() are
supplemental functions used for generating RSA signatures and verifying them.
https://en.wikipedia.org/wiki/RSA_(algorithm)
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 'rsa_key.py' and the
'_get_keyid()' function to see precisely how keyids are generated. One may
get the keyid of a key object by simply accessing the dictionary's 'keyid'
key (i.e., rsakey['keyid']).
"""
# Required for hexadecimal conversions. Signatures are hexlified.
import binascii
# Crypto.PublicKey (i.e., PyCrypto public-key cryptography) provides algorithms
# such as Digital Signature Algorithm (DSA) and the ElGamal encryption system.
# 'Crypto.PublicKey.RSA' is needed here to generate, sign, and verify RSA keys.
import Crypto.PublicKey.RSA
# PyCrypto requires 'Crypto.Hash' hash objects to generate PKCS#1 PSS
# signatures (i.e., Crypto.Signature.PKCS1_PSS).
import Crypto.Hash.SHA256
# RSA's probabilistic signature scheme with appendix (RSASSA-PSS).
# PKCS#1 v1.5 is provided for compatability with existing applications, but
# RSASSA-PSS is encouraged for newer applications. RSASSA-PSS generates
# a random salt to ensure the signature generated is probabilistic rather than
# deterministic, like PKCS#1 v1.5.
# http://en.wikipedia.org/wiki/RSA-PSS#Schemes
# https://tools.ietf.org/html/rfc3447#section-8.1
import Crypto.Signature.PKCS1_PSS
import tuf
# Digest objects needed to generate hashes.
import tuf.hash
# Perform object format-checking.
import tuf.formats
_KEY_ID_HASH_ALGORITHM = 'sha256'
# Recommended RSA key sizes:
# http://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm#table1
# According to the document above, revised May 6, 2003, RSA keys of
# size 3072 provide security through 2031 and beyond.
_DEFAULT_RSA_KEY_BITS = 3072
def generate(bits=_DEFAULT_RSA_KEY_BITS):
"""
<Purpose>
Generate public and private RSA keys, with modulus length 'bits'.
In addition, a keyid used as an identifier for RSA keys is generated.
The object returned conforms to 'tuf.formats.RSAKEY_SCHEMA' and as the form:
{'keytype': 'rsa',
'keyid': keyid,
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
The public and private keys are in PEM format and stored as strings.
Although the crytography library called sets a 1024-bit minimum key size,
generate() enforces a minimum key size of 2048 bits. If 'bits' is
unspecified, a 3072-bit RSA key is generated, which is the key size
recommended by TUF.
<Arguments>
bits:
The key size, or key length, of the RSA key. 'bits' must be 2048, or
greater, and a multiple of 256.
<Exceptions>
ValueError, if an exception occurs after calling the RSA key generation
routine. 'bits' must be a multiple of 256. The 'ValueError' exception is
raised by the key generation function of the cryptography library called.
tuf.FormatError, if 'bits' does not contain the correct format.
<Side Effects>
The RSA keys are generated by calling PyCrypto's
Crypto.PublicKey.RSA.generate().
<Returns>
A dictionary containing the RSA keys and other identifying information.
"""
# Does 'bits' have the correct format?
# This check will ensure 'bits' conforms to 'tuf.formats.RSAKEYBITS_SCHEMA'.
# 'bits' must be an integer object, with a minimum value of 2048.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.RSAKEYBITS_SCHEMA.check_match(bits)
# Begin building the RSA key dictionary.
rsakey_dict = {}
keytype = 'rsa'
# Generate the public and private RSA keys. The PyCrypto module performs
# the actual key generation. Raise 'ValueError' if 'bits' is less than 1024
# or not a multiple of 256, although a 2048-bit minimum is enforced by
# tuf.formats.RSAKEYBITS_SCHEMA.check_match().
rsa_key_object = Crypto.PublicKey.RSA.generate(bits)
# Extract the public & private halves of the RSA key and generate their
# PEM-formatted representations. The dictionary returned contains the
# private and public RSA keys in PEM format, as strings.
private_key_pem = rsa_key_object.exportKey(format='PEM')
rsa_pubkey = rsa_key_object.publickey()
public_key_pem = rsa_pubkey.exportKey(format='PEM')
# Generate the keyid for the RSA key. 'key_value' corresponds to the
# 'keyval' entry of the 'RSAKEY_SCHEMA' dictionary. The private key
# information is not included in the generation of the 'keyid' identifier.
key_value = {'public': public_key_pem,
'private': ''}
keyid = _get_keyid(key_value)
# Build the 'rsakey_dict' dictionary. Update 'key_value' with the RSA
# private key prior to adding 'key_value' to 'rsakey_dict'.
key_value['private'] = private_key_pem
rsakey_dict['keytype'] = keytype
rsakey_dict['keyid'] = keyid
rsakey_dict['keyval'] = key_value
return rsakey_dict
def create_in_metadata_format(key_value, private=False):
"""
<Purpose>
Return a dictionary conformant to 'tuf.formats.KEY_SCHEMA'.
If 'private' is True, include the private key. The dictionary
returned has the form:
{'keytype': 'rsa',
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
or if 'private' is False:
{'keytype': 'rsa',
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': ''}}
The private and public keys are in PEM format.
RSA keys are stored in Metadata files (e.g., root.txt) in the format
returned by this function.
<Arguments>
key_value:
A dictionary containing a private and public RSA key.
'key_value' is of the form:
{'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}},
conformat to 'tuf.formats.KEYVAL_SCHEMA'.
private:
Indicates if the private key should be included in the
returned dictionary.
<Exceptions>
tuf.FormatError, if 'key_value' does not conform to
'tuf.formats.KEYVAL_SCHEMA'.
<Side Effects>
None.
<Returns>
An 'KEY_SCHEMA' dictionary.
"""
# Does 'key_value' have the correct format?
# This check will ensure 'key_value' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.KEYVAL_SCHEMA.check_match(key_value)
if private is True and key_value['private']:
return {'keytype': 'rsa', 'keyval': key_value}
else:
public_key_value = {'public': key_value['public'], 'private': ''}
return {'keytype': 'rsa', 'keyval': public_key_value}
def create_from_metadata_format(key_metadata):
"""
<Purpose>
Construct an RSA key dictionary (i.e., tuf.formats.RSAKEY_SCHEMA)
from 'key_metadata'. The dict returned by this function has the exact
format as the dict returned by generate(). It is of the form:
{'keytype': 'rsa',
'keyid': keyid,
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
The public and private keys are in PEM format and stored as strings.
RSA key dictionaries in RSAKEY_SCHEMA format should be used by
modules storing a collection of keys, such as a keydb and keystore.
RSA keys as stored in metadata files use a different format, so this
function should be called if an RSA key is extracted from one of these
metadata files and needs converting. Generate() creates an entirely
new key and returns it in the format appropriate for 'keydb.py' and
'keystore.py'.
<Arguments>
key_metadata:
The RSA key dictionary as stored in Metadata files, conforming to
'tuf.formats.KEY_SCHEMA'. It has the form:
{'keytype': '...',
'keyval': {'public': '...',
'private': '...'}}
<Exceptions>
tuf.FormatError, if 'key_metadata' does not conform to
'tuf.formats.KEY_SCHEMA'.
<Side Effects>
None.
<Returns>
A dictionary containing the RSA keys and other identifying information.
"""
# Does 'key_metadata' have the correct format?
# This check will ensure 'key_metadata' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.KEY_SCHEMA.check_match(key_metadata)
# Construct the dictionary to be returned.
rsakey_dict = {}
keytype = 'rsa'
key_value = key_metadata['keyval']
# Convert 'key_value' to 'tuf.formats.KEY_SCHEMA' and generate its hash
# The hash is in hexdigest form.
keyid = _get_keyid(key_value)
# We now have all the required key values. Build 'rsakey_dict'.
rsakey_dict['keytype'] = keytype
rsakey_dict['keyid'] = keyid
rsakey_dict['keyval'] = key_value
return rsakey_dict
def _get_keyid(key_value):
"""Return the keyid for 'key_value'."""
# 'keyid' will be generated from an object conformant to KEY_SCHEMA,
# which is the format Metadata files (e.g., root.txt) store keys.
# 'create_in_metadata_format()' returns the object needed by _get_keyid().
rsakey_meta = create_in_metadata_format(key_value, private=False)
# Convert the RSA key to JSON Canonical format suitable for adding
# to digest objects.
rsakey_update_data = tuf.formats.encode_canonical(rsakey_meta)
# Create a digest object and call update(), using the JSON
# canonical format of 'rskey_meta' as the update data.
digest_object = tuf.hash.digest(_KEY_ID_HASH_ALGORITHM)
digest_object.update(rsakey_update_data)
# 'keyid' becomes the hexadecimal representation of the hash.
keyid = digest_object.hexdigest()
return keyid
def create_signature(rsakey_dict, data):
"""
<Purpose>
Return a signature dictionary of the form:
{'keyid': keyid,
'method': 'PyCrypto-PKCS#1 PPS',
'sig': sig}.
The signing process will use the private key
rsakey_dict['keyval']['private'] and 'data' to generate the signature.
RFC3447 - RSASSA-PSS
http://www.ietf.org/rfc/rfc3447.txt
<Arguments>
rsakey_dict:
A dictionary containing the RSA keys and other identifying information.
'rsakey_dict' has the form:
{'keytype': 'rsa',
'keyid': keyid,
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
The public and private keys are in PEM format and stored as strings.
data:
Data object used by create_signature() to generate the signature.
<Exceptions>
TypeError, if a private key is not defined for 'rsakey_dict'.
tuf.FormatError, if an incorrect format is found for the
'rsakey_dict' object.
<Side Effects>
PyCrypto's 'Crypto.Signature.PKCS1_PSS' called to perform the actual
signing.
<Returns>
A signature dictionary conformat to 'tuf.format.SIGNATURE_SCHEMA'.
"""
# Does 'rsakey_dict' have the correct format?
# This check will ensure 'rsakey_dict' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
# Signing the 'data' object requires a private key.
# The 'PyCrypto-PKCS#1 PSS' (i.e., PyCrypto module) signing method is the
# only method currently supported.
signature = {}
private_key = rsakey_dict['keyval']['private']
keyid = rsakey_dict['keyid']
method = 'PyCrypto-PKCS#1 PSS'
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):
# 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)
pkcs1_pss_signer = Crypto.Signature.PKCS1_PSS.new(rsa_key_object)
sig = pkcs1_pss_signer.sign(sha256_object)
except (ValueError, IndexError, TypeError), e:
message = 'An RSA signature could not be generated.'
raise tuf.CryptoError(message)
else:
raise TypeError('The required private key is not defined for "rsakey_dict".')
# Build the signature dictionary to be returned.
# The hexadecimal representation of 'sig' is stored in the signature.
signature['keyid'] = keyid
signature['method'] = method
signature['sig'] = binascii.hexlify(sig)
return signature
def verify_signature(rsakey_dict, signature, data):
"""
<Purpose>
Determine whether the private key belonging to 'rsakey_dict' produced
'signature'. verify_signature() will use the public key found in
'rsakey_dict', the 'method' and 'sig' objects contained in 'signature',
and 'data' to complete the verification. Type-checking performed on both
'rsakey_dict' and 'signature'.
<Arguments>
rsakey_dict:
A dictionary containing the RSA keys and other identifying information.
'rsakey_dict' has the form:
{'keytype': 'rsa',
'keyid': keyid,
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
The public and private keys are in PEM format and stored as strings.
signature:
The signature dictionary produced by tuf.rsa_key.create_signature().
'signature' has the form:
{'keyid': keyid, 'method': 'method', 'sig': sig}. Conformant to
'tuf.formats.SIGNATURE_SCHEMA'.
data:
Data object used by tuf.rsa_key.create_signature() to generate
'signature'. 'data' is needed here to verify the signature.
<Exceptions>
tuf.UnknownMethodError. Raised if the signing method used by
'signature' is not one supported by tuf.rsa_key.create_signature().
tuf.FormatError. Raised if either 'rsakey_dict'
or 'signature' do not match their respective tuf.formats schema.
'rsakey_dict' must conform to 'tuf.formats.RSAKEY_SCHEMA'.
'signature' must conform to 'tuf.formats.SIGNATURE_SCHEMA'.
<Side Effects>
Crypto.Signature.PKCS1_PSS.verify() called to do the actual verification.
<Returns>
Boolean. True if the signature is valid, False otherwise.
"""
# Does 'rsakey_dict' have the correct format?
# This check will ensure 'rsakey_dict' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
# Does 'signature' have the correct format?
tuf.formats.SIGNATURE_SCHEMA.check_match(signature)
# Using the public key belonging to 'rsakey_dict'
# (i.e., rsakey_dict['keyval']['public']), verify whether 'signature'
# was produced by rsakey_dict's corresponding private key
# rsakey_dict['keyval']['private']. Before returning the Boolean result,
# ensure 'PyCrypto-PKCS#1 PSS' was used as the signing method.
method = signature['method']
sig = signature['sig']
public_key = rsakey_dict['keyval']['public']
valid_signature = False
if method == 'PyCrypto-PKCS#1 PSS':
try:
rsa_key_object = Crypto.PublicKey.RSA.importKey(public_key)
pkcs1_pss_verifier = Crypto.Signature.PKCS1_PSS.new(rsa_key_object)
sha256_object = Crypto.Hash.SHA256.new(data)
# The metadata stores signatures in hex. Unhexlify and verify the
# signature.
signature = binascii.unhexlify(sig)
valid_signature = pkcs1_pss_verifier.verify(sha256_object, signature)
except (ValueError, IndexError, TypeError), e:
message = 'The RSA signature could not be verified.'
raise tuf.CryptoError(message)
else:
raise tuf.UnknownMethodError(method)
return valid_signature
def create_encrypted_pem(rsakey_dict, passphrase):
"""
<Purpose>
Return a string in PEM format, where the private part of the RSA key is
encrypted. The private part of the RSA key is encrypted by the Triple
Data Encryption Algorithm (3DES) and Cipher-block chaining (CBC) for the
mode of operation. Password-Based Key Derivation Function 1 (PBKF1) + MD5
is used to strengthen 'passphrase'.
https://en.wikipedia.org/wiki/Triple_DES
https://en.wikipedia.org/wiki/PBKDF2
<Arguments>
rsakey_dict:
A dictionary containing the RSA keys and other identifying information.
'rsakey_dict' has the form:
{'keytype': 'rsa',
'keyid': keyid,
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
The public and private keys are in PEM format and stored as strings.
passphrase:
The passphrase, or password, to encrypt the private part of the RSA
key. 'passphrase' is not used directly as the encryption key, a stronger
encryption key is derived from it.
<Exceptions>
TypeError, if a private key is not defined for 'rsakey_dict'.
tuf.FormatError, if an incorrect format is found for 'rsakey_dict'.
<Side Effects>
PyCrypto's Crypto.PublicKey.RSA.exportKey() called to perform the actual
generation of the PEM-formatted output.
<Returns>
A string in PEM format, where the private RSA key is encrypted.
"""
# Does 'rsakey_dict' have the correct format?
# This check will ensure 'rsakey_dict' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
# Raise 'tuf.FormatError' if the check fails.
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
# Does 'signature' have the correct format?
tuf.formats.PASSWORD_SCHEMA.check_match(passphrase)
# Extract the private key from 'rsakey_dict', which is stored in PEM format
# and unencrypted. The extracted key will be imported and converted to
# PyCrypto's RSA key object (i.e., Crypto.PublicKey.RSA).Use PyCrypto's
# exportKey method, with a passphrase specified, to create the string.
# PyCrypto uses PBKDF1+MD5 to strengthen 'passphrase', and 3DES with CBC mode
# for encryption.
private_key = rsakey_dict['keyval']['private']
try:
rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)
rsakey_pem_encrypted = rsa_key_object.exportKey(format='PEM',
passphrase=passphrase)
except (ValueError, IndexError, TypeError), e:
message = 'An encrypted RSA key in PEM format could not be generated.'
raise tuf.CryptoError(message)
return rsakey_pem_encrypted
def create_from_encrypted_pem(encrypted_pem, passphrase):
"""
<Purpose>
Return an RSA key in 'tuf.formats.RSAKEY_SCHEMA' format, which has the
form:
{'keytype': 'rsa',
'keyid': keyid,
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
The RSAKEY_SCHEMA object is generated from a byte string in PEM format,
where the private part of the RSA key is encrypted. PyCrypto's importKey
method is used, where a passphrase is specified. PyCrypto uses PBKDF1+MD5
to strengthen 'passphrase', and 3DES with CBC mode for encryption/decryption.
Alternatively, key data may be encrypted with AES-CTR-Mode and the passphrase
strengthened with PBKDF2+SHA256. See 'keystore.py'.
<Arguments>
encrypted_pem:
A byte string in PEM format, where the private key is encrypted. It has
the form:
'-----BEGIN RSA PRIVATE KEY-----\n
Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC ...'
passphrase:
The passphrase, or password, to decrypt the private part of the RSA
key. 'passphrase' is not directly used as the encryption key, instead
it is used to derive a stronger symmetric key.
<Exceptions>
TypeError, if a private key is not defined for 'rsakey_dict'.
tuf.FormatError, if an incorrect format is found for the
'rsakey_dict' object.
<Side Effects>
PyCrypto's 'Crypto.PublicKey.RSA.importKey()' called to perform the actual
conversion from an encrypted RSA private key.
<Returns>
A dictionary in 'tuf.formats.RSAKEY_SCHEMA' format.
"""
# Does 'encryped_pem' have the correct format?
# This check will ensure 'encrypted_pem' 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.PEMRSA_SCHEMA.check_match(encrypted_pem)
# Does 'passphrase' have the correct format?
tuf.formats.PASSWORD_SCHEMA.check_match(passphrase)
keytype = 'rsa'
rsakey_dict = {}
try:
rsa_key_object = Crypto.PublicKey.RSA.importKey(encrypted_pem, passphrase)
except (ValueError, IndexError, TypeError), e:
message = 'An RSA key object could not be generated from the encrypted '+\
'PEM string.'
# Raise 'tuf.CryptoError' instead of PyCrypto's exception to avoid
# revealing sensitive error, such as a decryption error due to an
# invalid passphrase.
raise tuf.CryptoError(message)
# Extract the public & private halves of the RSA key and generate their
# PEM-formatted representations. The dictionary returned contains the
# private and public RSA keys in PEM format, as strings.
private_key_pem = rsa_key_object.exportKey(format='PEM')
rsa_pubkey = rsa_key_object.publickey()
public_key_pem = rsa_pubkey.exportKey(format='PEM')
# Generate the keyid for the RSA key. 'key_value' corresponds to the
# 'keyval' entry of the 'RSAKEY_SCHEMA' dictionary. The private key
# information is not included in the generation of the 'keyid' identifier.
key_value = {'public': public_key_pem,
'private': ''}
keyid = _get_keyid(key_value)
# Build the 'rsakey_dict' dictionary. Update 'key_value' with the RSA
# private key prior to adding 'key_value' to 'rsakey_dict'.
key_value['private'] = private_key_pem
rsakey_dict['keytype'] = keytype
rsakey_dict['keyid'] = keyid
rsakey_dict['keyval'] = key_value
return rsakey_dict

View file

@ -38,7 +38,6 @@
criteria. See 'tuf.formats.py' and the rest of this module for extensive
examples. Anything related to the checking of TUF objects and their formats
can be found in 'formats.py'.
"""
@ -55,7 +54,6 @@ class Schema:
that are encodable in JSON. 'Schema' is the base class for
the other classes defined in this module. All derived classes
should implement check_match().
"""
def matches(self, object):
@ -64,7 +62,6 @@ def matches(self, object):
Return True if 'object' matches this schema, False if it doesn't.
If the caller wishes to signal an error on a failed match, check_match()
should be called, which will raise a 'tuf.FormatError' exception.
"""
try:
@ -82,7 +79,6 @@ def check_match(self, object):
implement check_match(). If 'object' matches the schema, check_match()
should simply return. If 'object' does not match the schema,
'tuf.FormatError' should be raised.
"""
raise NotImplementedError()
@ -110,7 +106,6 @@ class Any(Schema):
True
>>> schema.matches([1, 'list'])
True
"""
def __init__(self):
@ -143,7 +138,6 @@ class String(Schema):
True
>>> schema.matches('Not hi')
False
"""
def __init__(self, string):
@ -187,7 +181,6 @@ class AnyString(Schema):
True
>>> schema.matches({})
False
"""
def __init__(self):
@ -202,6 +195,48 @@ def check_match(self, object):
class LengthString(Schema):
"""
<Purpose>
Matches any string of a specified length. The argument object
must be a string. At instantiation, the string length is set
and any future comparisons are checked against this internal
string value length.
Supported methods include
matches(): returns a Boolean result.
check_match(): raises 'tuf.FormatError' on a mismatch.
<Example Use>
>>> schema = LengthString(5)
>>> schema.matches('Hello')
True
>>> schema.matches('Hi')
False
"""
def __init__(self, length):
if isinstance(length, bool) or not isinstance(length, (int, long)):
# We need to check for bool as a special case, since bool
# is for historical reasons a subtype of int.
raise tuf.FormatError('Got '+repr(length)+' instead of an integer.')
self._string_length = length
def check_match(self, object):
if not isinstance(object, basestring):
raise tuf.FormatError('Expected a string but got '+repr(object))
if len(object) != self._string_length:
raise tuf.FormatError('Expected a string of length '+
repr(self._string_length))
class OneOf(Schema):
"""
<Purpose>
@ -229,7 +264,6 @@ class OneOf(Schema):
True
>>> schema.matches(['Hi'])
False
"""
def __init__(self, alternatives):
@ -275,7 +309,6 @@ class AllOf(Schema):
False
>>> schema.matches('a')
True
"""
def __init__(self, required_schemas):
@ -314,7 +347,6 @@ class Boolean(Schema):
True
>>> schema.matches(11)
False
"""
def __init__(self):
@ -367,7 +399,6 @@ class ListOf(Schema):
True
>>> schema.matches([3]*11)
False
"""
def __init__(self, schema, min_count=0, max_count=sys.maxint, list_name='list'):
@ -380,7 +411,6 @@ def __init__(self, schema, min_count=0, max_count=sys.maxint, list_name='list'):
min_count: The minimum number of sub-schema in 'schema'.
max_count: The maximum number of sub-schema in 'schema'.
list_name: A string identifier for the ListOf object.
"""
if not isinstance(schema, Schema):
@ -443,7 +473,6 @@ class Integer(Schema):
True
>>> Integer(lo=10, hi=30).matches(5)
False
"""
def __init__(self, lo= -sys.maxint, hi=sys.maxint):
@ -454,7 +483,6 @@ def __init__(self, lo= -sys.maxint, hi=sys.maxint):
<Arguments>
lo: The minimum value the int object argument can be.
hi: The maximum value the int object argument can be.
"""
self._lo = lo
@ -468,7 +496,7 @@ def check_match(self, object):
raise tuf.FormatError('Got '+repr(object)+' instead of an integer.')
elif not (self._lo <= object <= self._hi):
int_range = '['+repr(self._lo)+','+repr(self._hi)+'].'
int_range = '['+repr(self._lo)+', '+repr(self._hi)+'].'
raise tuf.FormatError(repr(object)+' not in range '+int_range)
@ -502,7 +530,6 @@ class DictOf(Schema):
False
>>> schema.matches({'a': ['x', 'y'], 'e' : ['', ''], 'd' : ['a', 'b']})
False
"""
def __init__(self, key_schema, value_schema):
@ -513,7 +540,6 @@ def __init__(self, key_schema, value_schema):
<Arguments>
key_schema: The dictionary's key.
value_schema: The dictionary's value.
"""
if not isinstance(key_schema, Schema):
@ -564,7 +590,6 @@ class Optional(Schema):
False
>>> schema.matches({'k1': 'X'})
True
"""
def __init__(self, schema):
@ -604,7 +629,6 @@ class Object(Schema):
False
>>> schema.matches({'a':'ZYYY'})
False
"""
def __init__(self, object_name='object', **required):
@ -616,7 +640,6 @@ def __init__(self, object_name='object', **required):
object_name: A string identifier for the object argument.
A variable number of keyword arguments is accepted.
"""
# Ensure valid arguments.
@ -713,7 +736,6 @@ class Struct(Schema):
False
>>> schema.matches(['X', 3, 'A'])
False
"""
def __init__(self, sub_schemas, optional_schemas=[], allow_more=False,
@ -727,7 +749,6 @@ def __init__(self, sub_schemas, optional_schemas=[], allow_more=False,
optional_schemas: The optional list of schemas.
allow_more: Specifies that an optional list of types is allowed.
struct_name: A string identifier for the Struct object.
"""
# Ensure each item of the list contains the expected object type.
@ -792,7 +813,6 @@ class RegularExpression(Schema):
False
>>> schema.matches([33, 'Hello'])
False
"""
def __init__(self, pattern=None, modifiers=0, re_object=None, re_name=None):
@ -805,7 +825,6 @@ def __init__(self, pattern=None, modifiers=0, re_object=None, re_name=None):
modifiers: Flags to use when compiling the pattern.
re_object: A compiled regular expression object.
re_name: Identifier for the regular expression object.
"""
if not isinstance(pattern, basestring):

View file

@ -33,7 +33,6 @@
that will determine if a role still has a sufficient number of valid keys.
If a caller needs to update the signatures of a 'signable' object, there
is also a function for that.
"""
import tuf
@ -76,7 +75,6 @@ def get_signature_status(signable, role=None):
<Returns>
A dictionary representing the status of the signatures in 'signable'.
Conformant to tuf.formats.SIGNATURESTATUS_SCHEMA.
"""
# Does 'signable' have the correct format?
@ -125,7 +123,7 @@ def get_signature_status(signable, role=None):
# Identify key using an unknown key signing method.
try:
valid_sig = tuf.rsa_key.verify_signature(key, signature, data)
valid_sig = tuf.keys.verify_signature(key, signature, data)
except tuf.UnknownMethodError:
unknown_method_sigs.append(keyid)
continue
@ -201,7 +199,6 @@ def verify(signable, role):
<Returns>
Boolean. True if the number of good signatures >= the role's threshold,
False otherwise.
"""
# Retrieve the signature status. tuf.sig.get_signature_status() raises
@ -243,10 +240,8 @@ def may_need_new_keys(signature_status):
<Returns>
Boolean.
"""
# Does 'signature_status' have the correct format?
# This check will ensure 'signature_status' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
@ -281,11 +276,11 @@ def generate_rsa_signature(signed, rsakey_dict):
<Arguments>
signed:
The data used by 'tuf.rsa_key.create_signature()' to generate signatures.
The data used by 'tuf.keys.create_signature()' to generate signatures.
It is stored in the 'signed' field of 'signable'.
rsakey_dict:
The RSA key, a tuf.formats.RSAKEY_SCHEMA dictionary.
The RSA key, a 'tuf.formats.RSAKEY_SCHEMA' dictionary.
Used here to produce 'keyid', 'method', and 'sig'.
<Exceptions>
@ -300,7 +295,6 @@ def generate_rsa_signature(signed, rsakey_dict):
Signature dictionary conformant to tuf.formats.SIGNATURE_SCHEMA.
Has the form:
{'keyid': keyid, 'method': 'evp', 'sig': sig}
"""
# We need 'signed' in canonical JSON format to generate
@ -309,6 +303,6 @@ def generate_rsa_signature(signed, rsakey_dict):
# Generate the RSA signature.
# Raises tuf.FormatError and TypeError.
signature = tuf.rsa_key.create_signature(rsakey_dict, signed)
signature = tuf.keys.create_signature(rsakey_dict, signed)
return signature

View file

@ -14,7 +14,6 @@
<Purpose>
To provide a quick repository structure to be used in conjunction with
test modules like test_updater.py for instance.
"""
import os
@ -24,7 +23,6 @@
import tempfile
import tuf.formats
import tuf.rsa_key as rsa_key
import tuf.repo.keystore as keystore
import tuf.repo.signerlib as signerlib
import tuf.repo.signercli as signercli
@ -276,7 +274,6 @@ def create_repositories():
<Return>
A dictionary of all repositories, with the following keys:
(main_repository, client_repository, server_repository)
"""
# Ensure the keyids for the required roles are loaded. Role keyids are

View file

@ -15,7 +15,6 @@
Provides an array of various methods for unit testing. Use it instead of
actual unittest module. This module builds on unittest module.
Specifically, Modified_TestCase is a derived class from unittest.TestCase.
"""
import os
@ -27,7 +26,7 @@
import string
import ConfigParser
import tuf.rsa_key as rsa_key
import tuf.keys
import tuf.repo.keystore as keystore
# Modify the number of iterations (from the higher default count) so the unit
@ -100,7 +99,6 @@ def setUp():
random_string(length=7):
Generate a 'length' long string of random characters.
"""
# List of all top level roles.
@ -222,6 +220,7 @@ def make_temp_config_file(self, suffix='', directory=None, config_dict={}, expir
dictionary in it using ConfigParser.
It then returns the temp file path, dictionary tuple.
"""
config = ConfigParser.RawConfigParser()
if not config_dict:
# Using the fact that empty sequences are false.
@ -288,7 +287,6 @@ def make_temp_directory_with_data_files(self, _current_dir=None,
Returns:
('/tmp/tmp_dir_Test_random/', [targets/tmp_random1.txt,
targets/tmp_random2.txt, targets/more_targets/tmp_random3.txt])
"""
if not _current_dir:
@ -385,7 +383,7 @@ def generate_rsakey():
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
"""
rsakey = rsa_key.generate()
rsakey = tuf.keys.generate_rsa_key()
keyid = rsakey['keyid']
Modified_TestCase.rsa_keyids.append(keyid)
password = Modified_TestCase.random_string()

View file

@ -3,30 +3,36 @@
import timeit
import tuf
from tuf.ed25519_key import *
from tuf.ed25519_keys 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',
print('Time generate_public_and_private()')
print(timeit.timeit('generate_public_and_private(use_pynacl)',
setup='from __main__ import generate_public_and_private, \
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, \
print(timeit.timeit('create_signature(public, private, data, use_pynacl)',
setup='from __main__ import generate_public_and_private, \
create_signature, \
use_pynacl; \
ed25519_key = generate(use_pynacl);\
public, private = \
generate_public_and_private(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);\
print(timeit.timeit('verify_signature(public, method, signature, data, use_pynacl)',
setup='from __main__ import generate_public_and_private, \
create_signature, \
verify_signature, use_pynacl; \
public, private = \
generate_public_and_private(use_pynacl); \
data = "The quick brown fox jumps over the lazy dog";\
signature = create_signature(ed25519_key, data, use_pynacl)',
signature, method = \
create_signature(public, private, data, use_pynacl)',
number=1))

View file

@ -547,7 +547,7 @@ def load_json_file(filepath):
Deserialize a JSON object from a file containing the object.
<Arguments>
data:
filepath:
Absolute path of JSON file.
<Exceptions>