mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
Merge and resolve conflicts
This commit is contained in:
commit
3ca6261f79
44 changed files with 6766 additions and 1939 deletions
1
ed25519/.gitignore
vendored
Normal file
1
ed25519/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
*.pyc
|
||||
28
ed25519/.travis.yml
Normal file
28
ed25519/.travis.yml
Normal 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
0
ed25519/__init__.py
Executable file → Normal file
199
ed25519/ed25519.py
Executable file → Normal file
199
ed25519/ed25519.py
Executable file → Normal 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
8
ed25519/runtests.sh
Executable 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
32
ed25519/science.py
Normal 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
11
ed25519/setup.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
from setuptools import setup
|
||||
|
||||
|
||||
setup(
|
||||
name="ed25519",
|
||||
version="1.0",
|
||||
|
||||
py_modules="ed25519",
|
||||
|
||||
zip_safe=False,
|
||||
)
|
||||
|
|
@ -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
48
ed25519/signfast.py
Normal 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
5
ed25519/tox.ini
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[tox]
|
||||
envlist = py26,py27,pypy,py32,py33
|
||||
|
||||
[testenv]
|
||||
commands = ./runtests.sh
|
||||
BIN
resources/images/TUF repository tools.png
Normal file
BIN
resources/images/TUF repository tools.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
4
setup.py
4
setup.py
|
|
@ -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
123
tests/unit/test_ed25519_keys.py
Executable 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()
|
||||
|
|
@ -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
193
tests/unit/test_keys.py
Executable 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()
|
||||
|
|
@ -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
198
tests/unit/test_pycrypto_keys.py
Executable 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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
312
tuf/README.md
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
## Create TUF Repository
|
||||
|
||||

|
||||
### 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 key’s 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 role’s 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
|
||||
```
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
10
tuf/conf.py
10
tuf/conf.py
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
408
tuf/ed25519_keys.py
Executable 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
425
tuf/evp.py
Executable 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)
|
||||
153
tuf/formats.py
153
tuf/formats.py
|
|
@ -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
|
||||
|
|
|
|||
37
tuf/keydb.py
37
tuf/keydb.py
|
|
@ -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
1021
tuf/keys.py
Executable file
File diff suppressed because it is too large
Load diff
2947
tuf/libtuf.py
Executable file
2947
tuf/libtuf.py
Executable file
File diff suppressed because it is too large
Load diff
466
tuf/pycrypto_keys.py
Executable file
466
tuf/pycrypto_keys.py
Executable 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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
153
tuf/roledb.py
153
tuf/roledb.py
|
|
@ -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 == '':
|
||||
|
|
|
|||
664
tuf/rsa_key.py
664
tuf/rsa_key.py
|
|
@ -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
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
14
tuf/sig.py
14
tuf/sig.py
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue