mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
Merge branch 'develop' of github.com:theupdateframework/tuf into developer-tools
This commit is contained in:
commit
533fda5608
92 changed files with 5731 additions and 4203 deletions
|
|
@ -118,3 +118,11 @@ TUF has four major classes of users: clients, for whom TUF is largely transparen
|
|||
* [Low-level Integration](tuf/client/README.md)
|
||||
|
||||
* [High-level Integration](tuf/interposition/README.md)
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
This material is based upon work supported by the National Science Foundation
|
||||
under Grant No. CNS-1345049 and CNS-0959138. Any opinions, findings, and
|
||||
conclusions or recommendations expressed in this material are those of the
|
||||
author(s) and do not necessarily reflect the views of the National Science
|
||||
Foundation.
|
||||
|
|
|
|||
|
|
@ -17,3 +17,7 @@
|
|||
# http://nvie.com/posts/pin-your-packages/
|
||||
pycrypto==2.6.1
|
||||
pynacl==0.2.3
|
||||
|
||||
# Testing requirements. The rest of the testing dependencies available in
|
||||
# 'tox.ini'
|
||||
tox
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
|
@ -21,7 +21,7 @@
|
|||
The custom examples below demonstrate:
|
||||
(1) updating all targets
|
||||
(2) updating all the targets of a specified role
|
||||
(3) updating a specific target explicitely named.
|
||||
(3) updating a specific target explicitly named.
|
||||
|
||||
It assumes a server is listening on 'http://localhost:8001'. One can be
|
||||
started by navigating to the 'examples/repository/' and starting:
|
||||
|
|
|
|||
4
setup.py
4
setup.py
|
|
@ -96,6 +96,10 @@
|
|||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Topic :: Security',
|
||||
'Topic :: Software Development'
|
||||
|
|
|
|||
|
|
@ -5,11 +5,15 @@ branch = True
|
|||
[report]
|
||||
exclude_lines =
|
||||
pragma: no cover
|
||||
def _check_crypto_libraries
|
||||
def check_crypto_libraries
|
||||
def _get_password
|
||||
def _prompt
|
||||
def __str__
|
||||
if __name__ == .__main__.:
|
||||
|
||||
omit =
|
||||
*/tuf/interposition/*
|
||||
*/tuf/_vendor/*
|
||||
*/tuf/compatibility/*
|
||||
|
||||
# Command-line tool and integration example that calls core TUF.
|
||||
*/tuf/client/basic_client.py
|
||||
|
|
|
|||
|
|
@ -23,11 +23,26 @@
|
|||
'tuf/tests'. Use --random to run the tests in random order.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
import glob
|
||||
import random
|
||||
|
||||
# 'unittest2' required for testing under Python < 2.7.
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest
|
||||
|
||||
else:
|
||||
import unittest2 as unittest
|
||||
|
||||
|
||||
# Generate a list of pathnames that match a pattern (i.e., that begin with
|
||||
# 'test_' and end with '.py'. A shell-style wildcard is used with glob() to
|
||||
|
|
@ -42,9 +57,9 @@
|
|||
test = test[:-3]
|
||||
tests_without_extension.append(test)
|
||||
|
||||
# Provide command-line option to randomize the order in which the tests run.
|
||||
# Randomization might catch errors with unit tests that do not properly clean
|
||||
# up or restore monkey-patched modules.
|
||||
# Randomize the order in which the tests run. Randomization might catch errors
|
||||
# with unit tests that do not properly clean up or restore monkey-patched
|
||||
# modules.
|
||||
random.shuffle(tests_without_extension)
|
||||
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
generate.py
|
||||
|
|
@ -86,13 +88,13 @@
|
|||
target3_filepath = 'repository/targets/file3.txt'
|
||||
tuf.util.ensure_parent_dir(target2_filepath)
|
||||
|
||||
with open(target1_filepath, 'wb') as file_object:
|
||||
with open(target1_filepath, 'wt') as file_object:
|
||||
file_object.write('This is an example target file.')
|
||||
|
||||
with open(target2_filepath, 'wb') as file_object:
|
||||
with open(target2_filepath, 'wt') as file_object:
|
||||
file_object.write('This is an another example target file.')
|
||||
|
||||
with open(target3_filepath, 'wb') as file_object:
|
||||
with open(target3_filepath, 'wt') as file_object:
|
||||
file_object.write('This is role1\'s target file.')
|
||||
|
||||
# Add target files to the top-level 'targets.json' role. These target files
|
||||
|
|
@ -106,11 +108,11 @@
|
|||
# Set the top-level expiration times far into the future so that
|
||||
# they do not expire anytime soon, or else the tests fail. Unit tests may
|
||||
# modify the expiration datetimes (of the copied files), if they wish.
|
||||
repository.root.expiration = datetime.datetime(2030, 01, 01, 00, 00)
|
||||
repository.targets.expiration = datetime.datetime(2030, 01, 01, 00, 00)
|
||||
repository.snapshot.expiration = datetime.datetime(2030, 01, 01, 00, 00)
|
||||
repository.timestamp.expiration = datetime.datetime(2030, 01, 01, 00, 00)
|
||||
repository.targets('role1').expiration = datetime.datetime(2030, 01, 01, 00, 00)
|
||||
repository.root.expiration = datetime.datetime(2030, 1, 1, 0, 0)
|
||||
repository.targets.expiration = datetime.datetime(2030, 1, 1, 0, 0)
|
||||
repository.snapshot.expiration = datetime.datetime(2030, 1, 1, 0, 0)
|
||||
repository.timestamp.expiration = datetime.datetime(2030, 1, 1, 0, 0)
|
||||
repository.targets('role1').expiration = datetime.datetime(2030, 1, 1, 0, 0)
|
||||
|
||||
# Compress the 'targets.json' role so that the unit tests have a pre-generated
|
||||
# example of compressed metadata.
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,31815D9E16C988F5
|
||||
DEK-Info: DES-EDE3-CBC,8EB89B1037BC3FA7
|
||||
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsD++h8KXKhrJpzYxvZTE
|
||||
7GqTDKM3uAWL8qGcQnvh7CNbqR4WmZrCQj1zF9hO5OAV+lGVD+JxUXW+yOPw+RjN
|
||||
i7mPVa12Mq+vCS7WuosoCoooLpnYhRRVYMgpnhbvnjS6xA+7myJ1Bob+7WUEZZlC
|
||||
oWdsmjfYG82sA7TOTubPk0s9pqQllINMEsB4JTTt3P3DD9+uifCFhoAEKeAItcgA
|
||||
p4PJw2ImNwJiuet5wTP1ssTclPPWB6ofkm8zXoJUywTIW+hcQhN88jQv4Egx1llj
|
||||
pWZbEdkIpNxjm6BAEqfPfiuAt6MA8cmdRpDl+Jn030A1kI7NJossuvTcfHwvReZO
|
||||
WwIDAQAB
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqKdTRVn3mLQFUf02Rpug
|
||||
wVEU4yJtechILLb6nM7+urfwLe6f7EsNCDFhkiTP7vKuQywdLYrhwZKYZMDmaVnI
|
||||
q4d/tBLvb/jGY/IPFVvWbAOWtwWG7apiAFrcp3Idq6EKGaVVLn7tyv74+nisssYJ
|
||||
cVKodlkzpgX1Ibrdq73BUlAxhEQNDAUM5bzyJUW0BU4OSjUoFKCgc8BSkNcSLwXO
|
||||
RpyqAwDpPWiL68N1Dch7R9uD6GE9aREY9SKoYsNCvUOraIcme4fJZ3NmxpN3SVnX
|
||||
tepoiJo2iAtORtEI1yTCv/dOPap/iebveeCjn667HkMezJodSR8X3pMgMKMVyxhJ
|
||||
gwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
|
|
@ -1,30 +1,30 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,820FC61841CA82E2
|
||||
DEK-Info: DES-EDE3-CBC,B90D8CBD38BD4DE2
|
||||
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA72PwhMhTpDZ/wuB5Bdst
|
||||
gYsfEI931GcIZ46Iq2dqMNWEg2Qt9w6W0xEQj8M5R99XFwbhXL6U7hKGDt958FzT
|
||||
OL6CnjrnnBgzjbFm1vT380Qi5DaUbJkPcNmjzV45gGZkJ6LnohnBtnWUM/IdbbwR
|
||||
PdWaqBxWRJHECPHjgbKt6Y9kDwaO6tJQdUIDGwt2V9hz9orPqwiX+c6uO6qJ0naU
|
||||
F31ZvI4AtHUDaesbyp2j2X2dfCKNiM2t2sgxz79/G6VQvKG30PXxVPXvOhCDowsk
|
||||
5NdM67bWIkFyf1yNArrhw0D/c0aSGZhYZs+FqvBzKjCy+9+uEfLZsRra6zvx8Jw9
|
||||
TwIDAQAB
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7J15ZaeDQPrhQsRj29wB
|
||||
PhibH+Do59xsT2396L+uCg793gZlar5wZN2eHSh725cNQWyTAa9LwG+lXaKMukQ+
|
||||
8176CKR2J5sv3DezrGVu3x8V1qhyJyy79FlNZRVYTVqNaYzvJzxsVnFPpg7f8B7C
|
||||
ffiqWJr9XkpqwRlCpxooXm4hplZ7uek5Ku21CzQ4OWg7hbuc+ZjCGzpXfm8NuosU
|
||||
7TipnKGpEt0Agiph5g6TB2/scoeFar1CKMONIl80maxzAQk+xkWgiJ00+Z2qFCsx
|
||||
ESfis/YkILS6RMFyZz7oa1WwMtUjYmrsRuz+jlFcbNuxZpIkaISiG9a2YdGcJ1Aj
|
||||
3QIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
|
|
@ -1,30 +1,30 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,0E7D0C9F6987D107
|
||||
DEK-Info: DES-EDE3-CBC,9DA302AB20EFABC8
|
||||
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs0uAxc1XwUIayVPNt9U/
|
||||
gLM5hjG/6AJ86XiHVIq6BuzUtiuKONZXq3SJwY5eAxdjylNt/A3FrJwylQbVMZkh
|
||||
wLj0eU1IMh23xV1wOGu63Q9ARkmBAaksM+6apo2CHjtQaNXorhJ3/WDti2hST/cc
|
||||
HjP81+JwJ+T6lXGKvGDbh3h6Car99MWlMAeYODdNqvRaVkkiQ20HgNB+RSiyGZPi
|
||||
bzqlMe+qCQU/vktmmy9Zw3bjY7b8GFBix+7PHzFCpX15xKwB4dITPmsiL2OOo/w/
|
||||
1Vqu3wP7Ct170oK+bH9eIk6wSAQX3IljKhgzkiYFRyCC33XHRpWmSjMgAGErCcl6
|
||||
cwIDAQAB
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9XqJohXw46tOKUiOMzPx
|
||||
lDtrSlpy3WLH2zFSppN0eLIqByD4mk5nbyWKOzzGetQYgv9FzyER4AbmG40kD9bT
|
||||
2jm3zxjoTnCoM+1Qt7khZm3LxcKBa7q1yrAlvSfNLauIC220kauVRn4Kehd+IqeS
|
||||
/LhfOT6YyHUMH9SjZKM8XVHU1ehxTiA69eos4AosMK1Gf7jr042FzfiBTygqV1h5
|
||||
LXxO0IUYXiI4eCYTwzK4ChfQBmG3DGFGh2G8yrgqQZ5ERaBQPYG9rqQnfF8T8RUQ
|
||||
o4n7yKpEKSWLOr6Uz9Y1pnHZG4YiKKbTe9EKGtrRbDMIfI+Mv5f3+n600nwZrN7K
|
||||
OwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
|
|
@ -1,30 +1,30 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,D32B3B70C5CB2933
|
||||
DEK-Info: DES-EDE3-CBC,807D96406E906425
|
||||
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqTXKgkuaAyfvjkInfVic
|
||||
lJnwLvG9mCXOcxR3HPwHK/8k/E/DBx8YGIGk7NWCXQFYnAeMZZdx8v3dBmH7oGAn
|
||||
1pW+LnaAI7Csark8sbgmwYqwTd2oLHHmp/fOZ1vNDWjqvMLwi1YUllR6wPWKAvP2
|
||||
8i7q7GCT/MBHDyZ899FCR4f7HvlCW5EYNt+wjxdm79T++Ix7iqvs4iUhvllsfdty
|
||||
cVPWc+wh60qqCCbnr1Fow8d2j42a8mHoIHWgDvEF9ch+ChDOzQ+53jVmXS3CC+a2
|
||||
H6EWNvKMc/wXvIAwA/y6cIjCG+Kep9AvVgz1blHf4ReTlNJWu2OkanszuboAR6nt
|
||||
+wIDAQAB
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqF3xKdM9FySLLp0PiwIx
|
||||
2O9CFikFBo4Xfm4Z0HW69lu2X5WQFx8/GL9kmo1QjZrOwYToYDe287nidbbLs/rT
|
||||
lq3buN5wPMiD1GbVgGN/nknkkzv9KkJtrSF4RLbKrUnKo7/9C6IUmMt30wBk4GpJ
|
||||
RZ+8wFfRhUE6859/f6Xl4XbtBJofbIGwV/OBdIzO5zIgB3uBktbbqBVjJb8Oj6Oi
|
||||
YYskEIacP+TUrpa1iuC6nONj6ahI5NnEjt2B4/pLaUcEPm43kktJTabznkfNZXOa
|
||||
2nMjngY8v8EbNLBpG6Y7MqZwwLZ4wnaTOe5Bp323YN9eVONXfU2gtT2MBoWExvFV
|
||||
FwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
|
|
@ -1,30 +1,30 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,D5B52C0993E278D0
|
||||
DEK-Info: DES-EDE3-CBC,261BF7F755E46B73
|
||||
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAueyWz2lDPzyywmWegFTP
|
||||
l0DDXVNr9XIYLNffLRC24Y9ZKJCqb6eIz3XlF/3On5dCCqKFOfneHm55SxyCzoQb
|
||||
RAtr5+SmoXfsds7ZAnxv48iGLtk7aZEvHchhEJ+1HsKy5hMeqpXtOAMPJXqBfqgT
|
||||
ZzXagdCHNnbLEvhyJmRAaK88I/93s57KRNvPp6NIUbQ8EHaX1jaWt+LhGfsA3C34
|
||||
qcxlk16LXF45Wm2AgMFCtZ2BecUvSQds24b/ShxfeVRtXSSUMDd+dM+MbwclPsoP
|
||||
eTeegQATmA4pu4dDaBeYUnS/hstUZS5QPUuvVw6K0Q2JHUWv6CS2kziUjPXGI44H
|
||||
LwIDAQAB
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyw8QAC0PNyyf0AV3qzSp
|
||||
DgT74sGKC/72HIO1EskP4VEkdfh67PROHCm0YJTYLch9zH+uHIsmyyuzNr6go2Nv
|
||||
GPSRwYEP34LJlmqr699zkjSXw79T/t244keFiL8SFWTlWmQyTPDdn+N2v4acAmvW
|
||||
xSFcjTl8cVIGyGuU2s/vHrBn0zoOJ7ZIGAFzzCGAm0j6VvGvkxy3mymE+8VjzrAV
|
||||
9P1aOMdRVmlqCyPlGVW66Lvz7wkQKcp7rf0CEKkBGlYMtgTqiiagHJy0Sv6qAapw
|
||||
LXzE6ZdM40E1J1rT9GUitd0K4LhpSjW1lfipSbNQDLiZTG9R2EhnDMl5suaIaFh0
|
||||
UQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,12 +1,14 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program>
|
||||
simple_server.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
Konstantin Andrianov.
|
||||
|
||||
<Started>
|
||||
February 15, 2012
|
||||
February 15, 2012.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
|
@ -15,16 +17,23 @@
|
|||
This is a basic server that was designed to be used in conjunction with
|
||||
test_download.py to test download.py module.
|
||||
|
||||
<Referencesi>
|
||||
<Reference>
|
||||
SimpleHTTPServer:
|
||||
http://docs.python.org/library/simplehttpserver.html#module-SimpleHTTPServer
|
||||
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import random
|
||||
import SimpleHTTPServer
|
||||
import SocketServer
|
||||
|
||||
import tuf._vendor.six as six
|
||||
|
||||
PORT = 0
|
||||
|
||||
|
|
@ -36,13 +45,14 @@ def _port_gen():
|
|||
PORT = int(sys.argv[1])
|
||||
if PORT < 30000 or PORT > 45000:
|
||||
raise ValueError
|
||||
|
||||
except ValueError:
|
||||
PORT = _port_gen()
|
||||
|
||||
else:
|
||||
PORT = _port_gen()
|
||||
|
||||
Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
|
||||
httpd = SocketServer.TCPServer(("", PORT), Handler)
|
||||
Handler = six.moves.SimpleHTTPServer.SimpleHTTPRequestHandler
|
||||
httpd = six.moves.socketserver.TCPServer(('', PORT), Handler)
|
||||
|
||||
#print "PORT: ", PORT
|
||||
httpd.serve_forever()
|
||||
|
|
|
|||
|
|
@ -18,25 +18,33 @@
|
|||
interval 'DELAY'). The server is used in 'test_slow_retrieval_attack.py'.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import random
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
import tuf._vendor.six as six
|
||||
|
||||
|
||||
# Modify the HTTPServer class to pass the 'test_mode' argument to
|
||||
# do_GET() function.
|
||||
class HTTPServer_Test(HTTPServer):
|
||||
class HTTPServer_Test(six.moves.BaseHTTPServer.HTTPServer):
|
||||
def __init__(self, server_address, Handler, test_mode):
|
||||
HTTPServer.__init__(self, server_address, Handler)
|
||||
six.moves.BaseHTTPServer.HTTPServer.__init__(self, server_address, Handler)
|
||||
self.test_mode = test_mode
|
||||
|
||||
|
||||
|
||||
# HTTP request handler.
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
class Handler(six.moves.BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
|
||||
# Overwrite do_GET.
|
||||
def do_GET(self):
|
||||
|
|
@ -44,7 +52,7 @@ def do_GET(self):
|
|||
try:
|
||||
filepath = os.path.join(current_dir, self.path.lstrip('/'))
|
||||
data = None
|
||||
with open(filepath, 'rb') as fileobj:
|
||||
with open(filepath, 'r') as fileobj:
|
||||
data = fileobj.read()
|
||||
|
||||
self.send_response(200)
|
||||
|
|
@ -68,12 +76,12 @@ def do_GET(self):
|
|||
# 'tuf.conf.SLOW_START_GRACE_PERIOD' seconds before triggering a
|
||||
# potential slow retrieval error.
|
||||
for i in range(len(data)):
|
||||
self.wfile.write(data[i])
|
||||
self.wfile.write(data[i].encode('utf-8'))
|
||||
time.sleep(DELAY)
|
||||
|
||||
return
|
||||
|
||||
except IOError, e:
|
||||
except IOError as e:
|
||||
self.send_error(404, 'File Not Found!')
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,23 +25,30 @@
|
|||
There is no difference between 'updates' and 'target' files.
|
||||
"""
|
||||
|
||||
# Help with Python 3 compatability, where the print statement is a function, an
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import urllib
|
||||
import tempfile
|
||||
import random
|
||||
import time
|
||||
import shutil
|
||||
import json
|
||||
import subprocess
|
||||
import unittest
|
||||
import logging
|
||||
import sys
|
||||
|
||||
# 'unittest2' required for testing under Python < 2.7.
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest
|
||||
|
||||
else:
|
||||
import unittest2 as unittest
|
||||
|
||||
import tuf
|
||||
import tuf.formats
|
||||
|
|
@ -49,6 +56,7 @@
|
|||
import tuf.log
|
||||
import tuf.client.updater as updater
|
||||
import tuf.unittest_toolbox as unittest_toolbox
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logger = logging.getLogger('tuf.test_arbitrary_package_attack')
|
||||
|
||||
|
|
@ -81,7 +89,7 @@ def setUpClass(cls):
|
|||
|
||||
# NOTE: Following error is raised if a delay is not applied:
|
||||
# <urlopen error [Errno 111] Connection refused>
|
||||
time.sleep(.2)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
|
||||
|
|
@ -173,7 +181,7 @@ def test_without_tuf(self):
|
|||
|
||||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'targets', 'file1.txt')
|
||||
urllib.urlretrieve(url_file, client_target_path)
|
||||
six.moves.urllib.request.urlretrieve(url_file, client_target_path)
|
||||
|
||||
self.assertTrue(os.path.exists(client_target_path))
|
||||
length, hashes = tuf.util.get_file_details(client_target_path)
|
||||
|
|
@ -181,12 +189,12 @@ def test_without_tuf(self):
|
|||
self.assertEqual(fileinfo, download_fileinfo)
|
||||
|
||||
# Test: Download a target file that has been modified by an attacker.
|
||||
with open(target_path, 'wb') as file_object:
|
||||
with open(target_path, 'wt') as file_object:
|
||||
file_object.write('add malicious content.')
|
||||
length, hashes = tuf.util.get_file_details(target_path)
|
||||
malicious_fileinfo = tuf.formats.make_fileinfo(length, hashes)
|
||||
|
||||
urllib.urlretrieve(url_file, client_target_path)
|
||||
six.moves.urllib.request.urlretrieve(url_file, client_target_path)
|
||||
|
||||
length, hashes = tuf.util.get_file_details(client_target_path)
|
||||
download_fileinfo = tuf.formats.make_fileinfo(length, hashes)
|
||||
|
|
@ -211,13 +219,13 @@ def test_with_tuf(self):
|
|||
|
||||
# Modify 'file1.txt' and confirm that the TUF client rejects it.
|
||||
target_path = os.path.join(self.repository_directory, 'targets', 'file1.txt')
|
||||
with open(target_path, 'wb') as file_object:
|
||||
with open(target_path, 'wt') as file_object:
|
||||
file_object.write('add malicious content.')
|
||||
|
||||
try:
|
||||
self.repository_updater.download_target(file1_fileinfo, destination)
|
||||
|
||||
except tuf.NoWorkingMirrorError, exception:
|
||||
except tuf.NoWorkingMirrorError as exception:
|
||||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'targets', 'file1.txt')
|
||||
|
||||
|
|
@ -241,7 +249,7 @@ def test_with_tuf_and_metadata_tampering(self):
|
|||
|
||||
# An attacker modifies 'file1.txt'.
|
||||
target_path = os.path.join(self.repository_directory, 'targets', 'file1.txt')
|
||||
with open(target_path, 'wb') as file_object:
|
||||
with open(target_path, 'wt') as file_object:
|
||||
file_object.write('add malicious content.')
|
||||
|
||||
# An attacker also tries to add the malicious target's length and digest
|
||||
|
|
@ -258,7 +266,7 @@ def test_with_tuf_and_metadata_tampering(self):
|
|||
tuf.formats.check_signable_object_format(metadata)
|
||||
|
||||
with open(metadata_path, 'wb') as file_object:
|
||||
json.dump(metadata, file_object, indent=1, sort_keys=True)
|
||||
json.dumps(metadata, file_object, indent=1, sort_keys=True).encode('utf-8')
|
||||
|
||||
# Verify that the malicious 'targets.json' is not downloaded. Perform
|
||||
# a refresh of top-level metadata to demonstrate that the malicious
|
||||
|
|
@ -269,7 +277,7 @@ def test_with_tuf_and_metadata_tampering(self):
|
|||
destination = os.path.join(self.client_directory)
|
||||
self.repository_updater.download_target(file1_fileinfo, destination)
|
||||
|
||||
except tuf.NoWorkingMirrorError, exception:
|
||||
except tuf.NoWorkingMirrorError as exception:
|
||||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'targets', 'file1.txt')
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,13 @@
|
|||
Otherwise, module that launches simple server would not be found.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
|
|
@ -29,14 +35,13 @@
|
|||
import subprocess
|
||||
import time
|
||||
import unittest
|
||||
import urllib2
|
||||
|
||||
|
||||
import tuf
|
||||
import tuf.conf as conf
|
||||
import tuf.download as download
|
||||
import tuf.log
|
||||
import tuf.unittest_toolbox as unittest_toolbox
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logger = logging.getLogger('tuf.test_download')
|
||||
|
||||
|
|
@ -73,7 +78,7 @@ def setUp(self):
|
|||
|
||||
# Computing hash of target file data.
|
||||
m = hashlib.md5()
|
||||
m.update(self.target_data)
|
||||
m.update(self.target_data.encode('utf-8'))
|
||||
digest = m.hexdigest()
|
||||
self.target_hash = {'md5':digest}
|
||||
|
||||
|
|
@ -93,8 +98,8 @@ def test_download_url_to_tempfileobj(self):
|
|||
download_file = download.safe_download
|
||||
|
||||
temp_fileobj = download_file(self.url, self.target_data_length)
|
||||
self.assertEquals(self.target_data, temp_fileobj.read())
|
||||
self.assertEquals(self.target_data_length, len(temp_fileobj.read()))
|
||||
self.assertEqual(self.target_data, temp_fileobj.read().decode('utf-8'))
|
||||
self.assertEqual(self.target_data_length, len(temp_fileobj.read()))
|
||||
temp_fileobj.close_temp_file()
|
||||
|
||||
|
||||
|
|
@ -113,6 +118,7 @@ def test_download_url_to_tempfileobj_and_lengths(self):
|
|||
# STRICT_REQUIRED_LENGTH, which is True by default, mandates that we must
|
||||
# download exactly what is required.
|
||||
self.assertRaises(tuf.DownloadLengthMismatchError, download.safe_download,
|
||||
#self.assertRaises(tuf.SlowRetrievalError, download.safe_download,
|
||||
self.url, self.target_data_length + 1)
|
||||
|
||||
# NOTE: However, we do not catch a tuf.DownloadLengthMismatchError here for
|
||||
|
|
@ -120,8 +126,8 @@ def test_download_url_to_tempfileobj_and_lengths(self):
|
|||
# STRICT_REQUIRED_LENGTH.
|
||||
temp_fileobj = download.unsafe_download(self.url,
|
||||
self.target_data_length + 1)
|
||||
self.assertEquals(self.target_data, temp_fileobj.read())
|
||||
self.assertEquals(self.target_data_length, len(temp_fileobj.read()))
|
||||
self.assertEqual(self.target_data, temp_fileobj.read().decode('utf-8'))
|
||||
self.assertEqual(self.target_data_length, len(temp_fileobj.read()))
|
||||
temp_fileobj.close_temp_file()
|
||||
|
||||
|
||||
|
|
@ -139,8 +145,8 @@ def test_download_url_to_tempfileobj_and_performance(self):
|
|||
end_cpu = time.clock()
|
||||
end_real = time.time()
|
||||
|
||||
self.assertEquals(self.target_data, temp_fileobj.read())
|
||||
self.assertEquals(self.target_data_length, len(temp_fileobj.read()))
|
||||
self.assertEqual(self.target_data, temp_fileobj.read())
|
||||
self.assertEqual(self.target_data_length, len(temp_fileobj.read()))
|
||||
temp_fileobj.close_temp_file()
|
||||
|
||||
print "Performance cpu time: "+str(end_cpu - star_cpu)
|
||||
|
|
@ -162,15 +168,30 @@ def test_download_url_to_tempfileobj_and_urls(self):
|
|||
download_file,
|
||||
self.random_string(), self.target_data_length)
|
||||
|
||||
self.assertRaises(urllib2.HTTPError,
|
||||
self.assertRaises(six.moves.urllib.error.HTTPError,
|
||||
download_file,
|
||||
'http://localhost:'+str(self.PORT)+'/'+self.random_string(),
|
||||
'http://localhost:' + str(self.PORT) + '/' + self.random_string(),
|
||||
self.target_data_length)
|
||||
|
||||
self.assertRaises(urllib2.URLError,
|
||||
self.assertRaises(six.moves.urllib.error.URLError,
|
||||
download_file,
|
||||
'http://localhost:'+str(self.PORT+1)+'/'+self.random_string(),
|
||||
'http://localhost:' + str(self.PORT+1) + '/' + self.random_string(),
|
||||
self.target_data_length)
|
||||
|
||||
|
||||
|
||||
def test__get_opener(self):
|
||||
# Test normal case.
|
||||
# A simple https server should be used to test the rest of the optional
|
||||
# ssl-related functions of 'tuf.download.py'.
|
||||
fake_cacert = self.make_temp_data_file()
|
||||
|
||||
with open(fake_cacert, 'wt') as file_object:
|
||||
file_object.write('fake cacert')
|
||||
|
||||
tuf.conf.ssl_certificates = fake_cacert
|
||||
tuf.download._get_opener('https')
|
||||
|
||||
|
||||
|
||||
# Run unit test.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
#!/usr/bin/env/ python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
test_ed25519_keys.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
October 11, 2013.
|
||||
|
|
@ -15,7 +17,16 @@
|
|||
Test cases for test_ed25519_keys.py.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import logging
|
||||
|
||||
import tuf
|
||||
|
|
@ -46,7 +57,7 @@ def test_generate_public_and_private(self):
|
|||
def test_create_signature(self):
|
||||
global public
|
||||
global private
|
||||
data = 'The quick brown fox jumps over the lazy dog'
|
||||
data = b'The quick brown fox jumps over the lazy dog'
|
||||
signature, method = ed25519.create_signature(public, private, data)
|
||||
|
||||
# Verify format of returned values.
|
||||
|
|
@ -71,11 +82,24 @@ def test_create_signature(self):
|
|||
def test_verify_signature(self):
|
||||
global public
|
||||
global private
|
||||
data = 'The quick brown fox jumps over the lazy dog'
|
||||
data = b'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)
|
||||
|
||||
# Test with 'pynacl'.
|
||||
valid_signature = ed25519.verify_signature(public, method, signature, data,
|
||||
use_pynacl=True)
|
||||
self.assertEqual(True, valid_signature)
|
||||
|
||||
# Test with 'pynacl', but a bad signature is provided.
|
||||
bad_signature = os.urandom(64)
|
||||
valid_signature = ed25519.verify_signature(public, method, bad_signature,
|
||||
data, use_pynacl=True)
|
||||
self.assertEqual(False, valid_signature)
|
||||
|
||||
|
||||
|
||||
# Check for improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, ed25519.verify_signature, 123, method,
|
||||
|
|
@ -85,6 +109,10 @@ def test_verify_signature(self):
|
|||
self.assertRaises(tuf.FormatError, ed25519.verify_signature, public, 123,
|
||||
signature, data)
|
||||
|
||||
# Invalid signature method.
|
||||
self.assertRaises(tuf.UnknownMethodError, ed25519.verify_signature, public,
|
||||
'unsupported_method', signature, data)
|
||||
|
||||
# Signature not a string.
|
||||
self.assertRaises(tuf.FormatError, ed25519.verify_signature, public, method,
|
||||
123, data)
|
||||
|
|
@ -99,13 +127,13 @@ def test_verify_signature(self):
|
|||
signature, '123'))
|
||||
|
||||
# Mismatched signature.
|
||||
bad_signature = 'a'*64
|
||||
bad_signature = b'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')
|
||||
b'mismatched data')
|
||||
|
||||
self.assertEqual(False, ed25519.verify_signature(public, method,
|
||||
new_signature, data))
|
||||
|
|
|
|||
|
|
@ -28,23 +28,30 @@
|
|||
There is no difference between 'updates' and 'target' files.
|
||||
"""
|
||||
|
||||
# Help with Python 3 compatability, where the print statement is a function, an
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import urllib
|
||||
import tempfile
|
||||
import random
|
||||
import time
|
||||
import shutil
|
||||
import json
|
||||
import subprocess
|
||||
import unittest
|
||||
import logging
|
||||
import sys
|
||||
|
||||
# 'unittest2' required for testing under Python < 2.7.
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest
|
||||
|
||||
else:
|
||||
import unittest2 as unittest
|
||||
|
||||
import tuf
|
||||
import tuf.formats
|
||||
|
|
@ -52,6 +59,7 @@
|
|||
import tuf.log
|
||||
import tuf.client.updater as updater
|
||||
import tuf.unittest_toolbox as unittest_toolbox
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logger = logging.getLogger('tuf.test_endless_data_attack')
|
||||
|
||||
|
|
@ -84,7 +92,7 @@ def setUpClass(cls):
|
|||
|
||||
# NOTE: Following error is raised if a delay is not applied:
|
||||
# <urlopen error [Errno 111] Connection refused>
|
||||
time.sleep(.2)
|
||||
time.sleep(.8)
|
||||
|
||||
|
||||
|
||||
|
|
@ -178,7 +186,7 @@ def test_without_tuf(self):
|
|||
|
||||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'targets', 'file1.txt')
|
||||
urllib.urlretrieve(url_file, client_target_path)
|
||||
six.moves.urllib.request.urlretrieve(url_file, client_target_path)
|
||||
|
||||
self.assertTrue(os.path.exists(client_target_path))
|
||||
length, hashes = tuf.util.get_file_details(client_target_path)
|
||||
|
|
@ -187,7 +195,7 @@ def test_without_tuf(self):
|
|||
|
||||
# Test: Download a target file that has been modified by an attacker with
|
||||
# extra data.
|
||||
with open(target_path, 'r+b') as file_object:
|
||||
with open(target_path, 'r+t') as file_object:
|
||||
original_content = file_object.read()
|
||||
file_object.write(original_content+('append large amount of data' * 100000))
|
||||
large_length, hashes = tuf.util.get_file_details(target_path)
|
||||
|
|
@ -196,7 +204,7 @@ def test_without_tuf(self):
|
|||
# Is the modified file actually larger?
|
||||
self.assertTrue(large_length > length)
|
||||
|
||||
urllib.urlretrieve(url_file, client_target_path)
|
||||
six.moves.urllib.request.urlretrieve(url_file, client_target_path)
|
||||
|
||||
length, hashes = tuf.util.get_file_details(client_target_path)
|
||||
download_fileinfo = tuf.formats.make_fileinfo(length, hashes)
|
||||
|
|
@ -231,7 +239,7 @@ def test_with_tuf(self):
|
|||
|
||||
# Modify 'file1.txt' and confirm that the TUF client only downloads up to
|
||||
# the expected file length.
|
||||
with open(target_path, 'r+b') as file_object:
|
||||
with open(target_path, 'r+t') as file_object:
|
||||
original_content = file_object.read()
|
||||
file_object.write(original_content+('append large amount of data' * 10000))
|
||||
|
||||
|
|
@ -255,21 +263,24 @@ def test_with_tuf(self):
|
|||
|
||||
original_length, hashes = tuf.util.get_file_details(timestamp_path)
|
||||
|
||||
with open(timestamp_path, 'r+b') as file_object:
|
||||
original_content = file_object.read()
|
||||
file_object.write(original_content+('append large amount of data' * 10000))
|
||||
with open(timestamp_path, 'r+') as file_object:
|
||||
timestamp_content = tuf.util.load_json_file(timestamp_path)
|
||||
large_data = 'LargeTimestamp' * 10000
|
||||
timestamp_content['signed']['_type'] = large_data
|
||||
json.dump(timestamp_content, file_object, indent=1, sort_keys=True)
|
||||
|
||||
|
||||
modified_length, hashes = tuf.util.get_file_details(timestamp_path)
|
||||
self.assertTrue(modified_length > original_length)
|
||||
|
||||
|
||||
# Does the TUF client download the upper limit of an unsafely fetched
|
||||
# 'timestamp.json'? 'timestamp.json' must not be greater than
|
||||
# 'tuf.conf.DEFAULT_TIMESTAMP_REQUIRED_LENGTH'.
|
||||
try:
|
||||
self.repository_updater.refresh()
|
||||
|
||||
except tuf.NoWorkingMirrorError, exception:
|
||||
for mirror_url, mirror_error in exception.mirror_errors.iteritems():
|
||||
except tuf.NoWorkingMirrorError as exception:
|
||||
for mirror_url, mirror_error in six.iteritems(exception.mirror_errors):
|
||||
self.assertTrue(isinstance(mirror_error, tuf.InvalidMetadataJSONError))
|
||||
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
Zane Fisher.
|
||||
|
||||
<Started>
|
||||
August 19, 2013
|
||||
August 19, 2013.
|
||||
|
||||
April 6, 2014.
|
||||
Refactored to use the 'unittest' module (test conditions in code, rather
|
||||
|
|
@ -30,30 +30,37 @@
|
|||
There is no difference between 'updates' and 'target' files.
|
||||
"""
|
||||
|
||||
# Help with Python 3 compatability, where the print statement is a function, an
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import urllib
|
||||
import tempfile
|
||||
import random
|
||||
import time
|
||||
import shutil
|
||||
import json
|
||||
import subprocess
|
||||
import unittest
|
||||
import logging
|
||||
import sys
|
||||
|
||||
# 'unittest2' required for testing under Python < 2.7.
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest
|
||||
|
||||
else:
|
||||
import unittest2 as unittest
|
||||
|
||||
import tuf.formats
|
||||
import tuf.util
|
||||
import tuf.log
|
||||
import tuf.client.updater as updater
|
||||
import tuf.unittest_toolbox as unittest_toolbox
|
||||
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logger = logging.getLogger('tuf.test_extraneous_dependencies_attack')
|
||||
|
||||
|
|
@ -87,7 +94,7 @@ def setUpClass(cls):
|
|||
|
||||
# NOTE: Following error is raised if a delay is not applied:
|
||||
# <urlopen error [Errno 111] Connection refused>
|
||||
time.sleep(.2)
|
||||
time.sleep(.7)
|
||||
|
||||
|
||||
|
||||
|
|
@ -185,7 +192,7 @@ def test_with_tuf(self):
|
|||
|
||||
tuf.formats.check_signable_object_format(role1_metadata)
|
||||
|
||||
with open(role1_filepath, 'wb') as file_object:
|
||||
with open(role1_filepath, 'wt') as file_object:
|
||||
json.dump(role1_metadata, file_object, indent=1, sort_keys=True)
|
||||
|
||||
# Un-install the metadata of the top-level roles so that the client can
|
||||
|
|
@ -208,8 +215,8 @@ def test_with_tuf(self):
|
|||
|
||||
# Verify that the specific 'tuf.BadHashError' exception is raised by each
|
||||
# mirror.
|
||||
except tuf.NoWorkingMirrorError, exception:
|
||||
for mirror_url, mirror_error in exception.mirror_errors.iteritems():
|
||||
except tuf.NoWorkingMirrorError as exception:
|
||||
for mirror_url, mirror_error in six.iteritems(exception.mirror_errors):
|
||||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'metadata', 'targets', 'role1.json')
|
||||
|
||||
|
|
|
|||
|
|
@ -17,13 +17,21 @@
|
|||
Unit test for 'formats.py'
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import datetime
|
||||
|
||||
import tuf
|
||||
import tuf.formats
|
||||
import tuf.schema
|
||||
|
||||
import tuf._vendor.six as six
|
||||
|
||||
|
||||
class TestFormats(unittest.TestCase):
|
||||
|
|
@ -252,13 +260,13 @@ def test_schemas(self):
|
|||
|
||||
# Iterate 'valid_schemas', ensuring each 'valid_schema' correctly matches
|
||||
# its respective 'schema_type'.
|
||||
for schema_name, (schema_type, valid_schema) in valid_schemas.items():
|
||||
for schema_name, (schema_type, valid_schema) in six.iteritems(valid_schemas):
|
||||
self.assertEqual(True, schema_type.matches(valid_schema))
|
||||
|
||||
# Test conditions for invalid schemas.
|
||||
# Set the 'valid_schema' of 'valid_schemas' to an invalid
|
||||
# value and test that it does not match 'schema_type'.
|
||||
for schema_name, (schema_type, valid_schema) in valid_schemas.items():
|
||||
for schema_name, (schema_type, valid_schema) in six.iteritems(valid_schemas):
|
||||
invalid_schema = 0xBAD
|
||||
if isinstance(schema_type, tuf.schema.Integer):
|
||||
invalid_schema = 'BAD'
|
||||
|
|
@ -485,7 +493,7 @@ def test_unix_timestamp_to_datetime(self):
|
|||
# Test conditions for valid arguments.
|
||||
UNIX_TIMESTAMP_SCHEMA = tuf.formats.UNIX_TIMESTAMP_SCHEMA
|
||||
self.assertTrue(datetime.datetime, tuf.formats.unix_timestamp_to_datetime(499137720))
|
||||
datetime_object = datetime.datetime(1985, 10, 26, 01, 22)
|
||||
datetime_object = datetime.datetime(1985, 10, 26, 1, 22)
|
||||
self.assertEqual(datetime_object, tuf.formats.unix_timestamp_to_datetime(499137720))
|
||||
|
||||
# Test conditions for invalid arguments.
|
||||
|
|
@ -510,9 +518,9 @@ def test_datetime_to_unix_timestamp(self):
|
|||
|
||||
def test_format_base64(self):
|
||||
# Test conditions for valid arguments.
|
||||
data = 'updateframework'
|
||||
data = 'updateframework'.encode('utf-8')
|
||||
self.assertEqual('dXBkYXRlZnJhbWV3b3Jr', tuf.formats.format_base64(data))
|
||||
self.assertTrue(isinstance(tuf.formats.format_base64(data), basestring))
|
||||
self.assertTrue(isinstance(tuf.formats.format_base64(data), six.string_types))
|
||||
|
||||
# Test conditions for invalid arguments.
|
||||
self.assertRaises(tuf.FormatError, tuf.formats.format_base64, 123)
|
||||
|
|
@ -523,8 +531,8 @@ def test_format_base64(self):
|
|||
def test_parse_base64(self):
|
||||
# Test conditions for valid arguments.
|
||||
base64 = 'dXBkYXRlZnJhbWV3b3Jr'
|
||||
self.assertEqual('updateframework', tuf.formats.parse_base64(base64))
|
||||
self.assertTrue(isinstance(tuf.formats.parse_base64(base64), basestring))
|
||||
self.assertEqual(b'updateframework', tuf.formats.parse_base64(base64))
|
||||
self.assertTrue(isinstance(tuf.formats.parse_base64(base64), six.binary_type))
|
||||
|
||||
# Test conditions for invalid arguments.
|
||||
self.assertRaises(tuf.FormatError, tuf.formats.parse_base64, 123)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
test_hash.py
|
||||
|
|
@ -16,8 +18,15 @@
|
|||
Unit test for 'hash.py'.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import StringIO
|
||||
import logging
|
||||
import tempfile
|
||||
import unittest
|
||||
|
|
@ -25,6 +34,7 @@
|
|||
import tuf
|
||||
import tuf.log
|
||||
import tuf.hash
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logger = logging.getLogger('tuf.test_hash')
|
||||
|
||||
|
|
@ -51,13 +61,13 @@ def _do_md5_update(self, library):
|
|||
digest_object = tuf.hash.digest('md5', library)
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'd41d8cd98f00b204e9800998ecf8427e')
|
||||
digest_object.update('a')
|
||||
digest_object.update('a'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'0cc175b9c0f1b6a831c399e269772661')
|
||||
digest_object.update(u'bbb')
|
||||
digest_object.update('bbb'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'f034e93091235adbb5d2781908e2b313')
|
||||
digest_object.update('')
|
||||
digest_object.update(''.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'f034e93091235adbb5d2781908e2b313')
|
||||
|
||||
|
|
@ -71,13 +81,13 @@ def _do_sha1_update(self, library):
|
|||
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'da39a3ee5e6b4b0d3255bfef95601890afd80709')
|
||||
digest_object.update('a')
|
||||
digest_object.update('a'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'86f7e437faa5a7fce15d1ddcb9eaeaea377667b8')
|
||||
digest_object.update(u'bbb')
|
||||
digest_object.update('bbb'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'd7bfa42fc62b697bf6cf1cda9af1fb7f40a27817')
|
||||
digest_object.update('')
|
||||
digest_object.update(''.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'd7bfa42fc62b697bf6cf1cda9af1fb7f40a27817')
|
||||
|
||||
|
|
@ -91,13 +101,13 @@ def _do_sha224_update(self, library):
|
|||
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f')
|
||||
digest_object.update('a')
|
||||
digest_object.update('a'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5')
|
||||
digest_object.update(u'bbb')
|
||||
digest_object.update('bbb'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'ab1342f31c2a6f242d9a3cefb503fb49465c95eb255c16ad791d688c')
|
||||
digest_object.update('')
|
||||
digest_object.update(''.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'ab1342f31c2a6f242d9a3cefb503fb49465c95eb255c16ad791d688c')
|
||||
|
||||
|
|
@ -110,13 +120,13 @@ def _do_sha256_update(self, library):
|
|||
digest_object = tuf.hash.digest('sha256', library)
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')
|
||||
digest_object.update('a')
|
||||
digest_object.update('a'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb')
|
||||
digest_object.update(u'bbb')
|
||||
digest_object.update('bbb'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'01d162a5c95d4698c0a3e766ae80d85994b549b877ed275803725f43dadc83bd')
|
||||
digest_object.update('')
|
||||
digest_object.update(''.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'01d162a5c95d4698c0a3e766ae80d85994b549b877ed275803725f43dadc83bd')
|
||||
|
||||
|
|
@ -130,15 +140,15 @@ def _do_sha384_update(self, library):
|
|||
self.assertEqual(digest_object.hexdigest(),
|
||||
'38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe'
|
||||
'76f65fbd51ad2f14898b95b')
|
||||
digest_object.update('a')
|
||||
digest_object.update('a'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d'
|
||||
'57bc35efae0b5afd3145f31')
|
||||
digest_object.update(u'bbb')
|
||||
digest_object.update('bbb'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'f2c1438e9cc1d24bebbf3b88e60adc169db0c5c459d02054ec131438bf20ebee5ca88c17c'
|
||||
'b5f1a824fcccf8d2b20b0a9')
|
||||
digest_object.update('')
|
||||
digest_object.update(''.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'f2c1438e9cc1d24bebbf3b88e60adc169db0c5c459d02054ec131438bf20ebee5ca88c17c'
|
||||
'b5f1a824fcccf8d2b20b0a9')
|
||||
|
|
@ -154,15 +164,15 @@ def _do_sha512_update(self, library):
|
|||
self.assertEqual(digest_object.hexdigest(),
|
||||
'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5'
|
||||
'd85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e')
|
||||
digest_object.update('a')
|
||||
digest_object.update('a'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652'
|
||||
'bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75')
|
||||
digest_object.update(u'bbb')
|
||||
digest_object.update('bbb'.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'09ade82ae3c5d54f8375f348563a372106488adef16a74b63b5591849f740bff55ceab22e'
|
||||
'117b4b09349b860f8a644adb32a9ea542abdecb80bf625160604251')
|
||||
digest_object.update('')
|
||||
digest_object.update(''.encode('utf-8'))
|
||||
self.assertEqual(digest_object.hexdigest(),
|
||||
'09ade82ae3c5d54f8375f348563a372106488adef16a74b63b5591849f740bff55ceab22e'
|
||||
'117b4b09349b860f8a644adb32a9ea542abdecb80bf625160604251')
|
||||
|
|
@ -197,13 +207,14 @@ def _do_update_filename(self, library):
|
|||
data = 'abcdefgh' * 4096
|
||||
fd, filename = tempfile.mkstemp()
|
||||
try:
|
||||
os.write(fd, data)
|
||||
os.write(fd, data.encode('utf-8'))
|
||||
os.close(fd)
|
||||
for algorithm in ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']:
|
||||
digest_object_truth = tuf.hash.digest(algorithm, library)
|
||||
digest_object_truth.update(data)
|
||||
digest_object_truth.update(data.encode('utf-8'))
|
||||
digest_object = tuf.hash.digest_filename(filename, algorithm, library)
|
||||
self.assertEqual(digest_object_truth.digest(), digest_object.digest())
|
||||
|
||||
finally:
|
||||
os.remove(filename)
|
||||
|
||||
|
|
@ -214,22 +225,16 @@ def test_update_file_obj(self):
|
|||
|
||||
def _do_update_file_obj(self, library):
|
||||
data = 'abcdefgh' * 4096
|
||||
file_obj = StringIO.StringIO()
|
||||
file_obj = six.StringIO()
|
||||
file_obj.write(data)
|
||||
for algorithm in ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']:
|
||||
digest_object_truth = tuf.hash.digest(algorithm, library)
|
||||
digest_object_truth.update(data)
|
||||
digest_object_truth.update(data.encode('utf-8'))
|
||||
digest_object = tuf.hash.digest_fileobject(file_obj, algorithm, library)
|
||||
# Note: we don't seek because the update_file_obj call is supposed
|
||||
# to always seek to the beginning.
|
||||
self.assertEqual(digest_object_truth.digest(), digest_object.digest())
|
||||
|
||||
|
||||
def test_data_to_string(self):
|
||||
self.assertEqual('12', tuf.hash.data_to_string('12'))
|
||||
self.assertEqual(u'hello', tuf.hash.data_to_string(unicode('hello')))
|
||||
self.assertEqual('12', tuf.hash.data_to_string(12))
|
||||
|
||||
|
||||
def test_unsupported_digest_algorithm_and_library(self):
|
||||
self.assertRaises(tuf.UnsupportedAlgorithmError, tuf.hash.digest,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
test_indefinite_freeze_attack.py
|
||||
|
|
@ -22,23 +24,30 @@
|
|||
metadata without the client being aware.
|
||||
"""
|
||||
|
||||
# Help with Python 3 compatability, where the print statement is a function, an
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import urllib
|
||||
import tempfile
|
||||
import random
|
||||
import time
|
||||
import tempfile
|
||||
import shutil
|
||||
import json
|
||||
import subprocess
|
||||
import unittest
|
||||
import logging
|
||||
import sys
|
||||
|
||||
# 'unittest2' required for testing under Python < 2.7.
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest
|
||||
|
||||
else:
|
||||
import unittest2 as unittest
|
||||
|
||||
import tuf.formats
|
||||
import tuf.util
|
||||
|
|
@ -46,6 +55,7 @@
|
|||
import tuf.client.updater as updater
|
||||
import tuf.repository_tool as repo_tool
|
||||
import tuf.unittest_toolbox as unittest_toolbox
|
||||
import tuf._vendor.six as six
|
||||
|
||||
# The repository tool is imported and logs console messages by default. Disable
|
||||
# console log messages generated by this unit test.
|
||||
|
|
@ -82,7 +92,7 @@ def setUpClass(cls):
|
|||
|
||||
# NOTE: Following error is raised if a delay is not applied:
|
||||
# <urlopen error [Errno 111] Connection refused>
|
||||
time.sleep(.2)
|
||||
time.sleep(.8)
|
||||
|
||||
|
||||
|
||||
|
|
@ -180,8 +190,12 @@ def test_without_tuf(self):
|
|||
tuf.formats.check_signable_object_format(timestamp_metadata)
|
||||
|
||||
with open(timestamp_path, 'wb') as file_object:
|
||||
json.dump(timestamp_metadata, file_object, indent=1, sort_keys=True)
|
||||
|
||||
# Explicitly specify the JSON separators for Python 2 + 3 consistency.
|
||||
timestamp_content = \
|
||||
json.dumps(timestamp_metadata, indent=1, separators=(',', ': '),
|
||||
sort_keys=True).encode('utf-8')
|
||||
file_object.write(timestamp_content)
|
||||
|
||||
client_timestamp_path = os.path.join(self.client_directory,
|
||||
'timestamp.json')
|
||||
shutil.copy(timestamp_path, client_timestamp_path)
|
||||
|
|
@ -192,7 +206,7 @@ def test_without_tuf(self):
|
|||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'metadata', 'timestamp.json')
|
||||
|
||||
urllib.urlretrieve(url_file, client_timestamp_path)
|
||||
six.moves.urllib.request.urlretrieve(url_file, client_timestamp_path)
|
||||
|
||||
length, hashes = tuf.util.get_file_details(client_timestamp_path)
|
||||
download_fileinfo = tuf.formats.make_fileinfo(length, hashes)
|
||||
|
|
@ -240,7 +254,7 @@ def test_with_tuf(self):
|
|||
self.repository_updater.refresh()
|
||||
|
||||
except tuf.NoWorkingMirrorError as e:
|
||||
for mirror_url, mirror_error in e.mirror_errors.iteritems():
|
||||
for mirror_url, mirror_error in six.iteritems(e.mirror_errors):
|
||||
self.assertTrue(isinstance(mirror_error, tuf.ExpiredMetadataError))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
test_keydb.py
|
||||
|
|
@ -15,6 +17,14 @@
|
|||
Unit test for 'keydb.py'.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import logging
|
||||
|
||||
|
|
@ -150,6 +160,9 @@ def test_remove_key(self):
|
|||
self.assertRaises(tuf.UnknownKeyError, tuf.keydb.get_key, keyid)
|
||||
self.assertRaises(tuf.UnknownKeyError, tuf.keydb.get_key, keyid2)
|
||||
|
||||
# Test for 'keyid' not in keydb.
|
||||
self.assertRaises(tuf.UnknownKeyError, tuf.keydb.remove_key, keyid)
|
||||
|
||||
# Test condition for unknown key argument.
|
||||
self.assertRaises(tuf.UnknownKeyError, tuf.keydb.remove_key, '1')
|
||||
|
||||
|
|
@ -169,7 +182,10 @@ def test_create_keydb_from_root_metadata(self):
|
|||
keyid = KEYS[0]['keyid']
|
||||
rsakey2 = KEYS[1]
|
||||
keyid2 = KEYS[1]['keyid']
|
||||
keydict = {keyid: rsakey, keyid2: rsakey2}
|
||||
keydict = {keyid: rsakey, keyid2: rsakey2, keyid: rsakey}
|
||||
|
||||
# Add a duplicate 'keyid' to log/trigger a 'tuf.KeyAlreadyExistsError'
|
||||
# block (loading continues).
|
||||
roledict = {'Root': {'keyids': [keyid], 'threshold': 1},
|
||||
'Targets': {'keyids': [keyid2], 'threshold': 1}}
|
||||
version = 8
|
||||
|
|
@ -182,6 +198,7 @@ def test_create_keydb_from_root_metadata(self):
|
|||
keydict, roledict,
|
||||
consistent_snapshot)
|
||||
self.assertEqual(None, tuf.keydb.create_keydb_from_root_metadata(root_metadata))
|
||||
|
||||
tuf.keydb.create_keydb_from_root_metadata(root_metadata)
|
||||
|
||||
# Ensure 'keyid' and 'keyid2' were added to the keydb database.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
test_keys.py
|
||||
|
|
@ -16,6 +18,14 @@
|
|||
TODO: test case for ed25519 key generation and refactor.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import logging
|
||||
|
||||
|
|
@ -183,7 +193,7 @@ def test_verify_signature(self):
|
|||
# in creating the 'rsa_signature'. Function should return 'False'.
|
||||
|
||||
# Modifying 'DATA'.
|
||||
_DATA = '1111'+DATA+'1111'
|
||||
_DATA = '1111' + DATA + '1111'
|
||||
|
||||
# Verifying the 'signature' of modified '_DATA'.
|
||||
verified = KEYS.verify_signature(self.rsakey_dict, rsa_signature, _DATA)
|
||||
|
|
@ -199,6 +209,51 @@ def test_verify_signature(self):
|
|||
|
||||
# Passing incorrect number of arguments.
|
||||
self.assertRaises(TypeError, KEYS.verify_signature)
|
||||
|
||||
|
||||
|
||||
def test_create_rsa_encrypted_pem(self):
|
||||
# Test valid arguments.
|
||||
private = self.rsakey_dict['keyval']['private']
|
||||
passphrase = 'secret'
|
||||
encrypted_pem = KEYS.create_rsa_encrypted_pem(private, passphrase)
|
||||
self.assertTrue(tuf.formats.PEMRSA_SCHEMA.matches(encrypted_pem))
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, KEYS.create_rsa_encrypted_pem,
|
||||
8, passphrase)
|
||||
|
||||
self.assertRaises(tuf.FormatError, KEYS.create_rsa_encrypted_pem,
|
||||
private, 8)
|
||||
|
||||
# Test for missing required library.
|
||||
KEYS._RSA_CRYPTO_LIBRARY = 'invalid'
|
||||
self.assertRaises(tuf.UnsupportedLibraryError, KEYS.create_rsa_encrypted_pem,
|
||||
private, passphrase)
|
||||
KEYS._RSA_CRYPTO_LIBRARY = 'pycrypto'
|
||||
|
||||
|
||||
|
||||
def test_decrypt_key(self):
|
||||
# Test valid arguments.
|
||||
passphrase = 'secret'
|
||||
encrypted_key = KEYS.encrypt_key(self.rsakey_dict, passphrase).encode('utf-8')
|
||||
decrypted_key = KEYS.decrypt_key(encrypted_key, passphrase)
|
||||
|
||||
self.assertTrue(tuf.formats.ANYKEY_SCHEMA.matches(decrypted_key))
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, KEYS.decrypt_key,
|
||||
8, passphrase)
|
||||
|
||||
self.assertRaises(tuf.FormatError, KEYS.decrypt_key,
|
||||
encrypted_key, 8)
|
||||
|
||||
# Test for missing required library.
|
||||
KEYS._GENERAL_CRYPTO_LIBRARY = 'invalid'
|
||||
self.assertRaises(tuf.UnsupportedLibraryError, KEYS.decrypt_key,
|
||||
encrypted_key, passphrase)
|
||||
KEYS._GENERAL_CRYPTO_LIBRARY = 'pycrypto'
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
|
|
@ -29,13 +30,17 @@
|
|||
|
||||
|
||||
class TestLog(unittest.TestCase):
|
||||
|
||||
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
tuf.log.remove_console_handler()
|
||||
|
||||
|
||||
|
||||
def test_set_log_level(self):
|
||||
# Test normal case.
|
||||
global log_levels
|
||||
global logger
|
||||
|
||||
tuf.log.set_log_level()
|
||||
self.assertTrue(logger.isEnabledFor(logging.DEBUG))
|
||||
|
|
@ -53,22 +58,74 @@ def test_set_log_level(self):
|
|||
|
||||
|
||||
def test_set_filehandler_log_level(self):
|
||||
pass
|
||||
# Normal case. Default log level.
|
||||
tuf.log.set_filehandler_log_level()
|
||||
|
||||
# Expected log levels.
|
||||
for level in log_levels:
|
||||
tuf.log.set_log_level(level)
|
||||
|
||||
# Test for improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError, tuf.log.set_filehandler_log_level, '123')
|
||||
|
||||
# Test for invalid argument.
|
||||
self.assertRaises(tuf.FormatError, tuf.log.set_filehandler_log_level, 51)
|
||||
|
||||
|
||||
|
||||
|
||||
def test_set_console_log_level(self):
|
||||
pass
|
||||
# Test setting a console log level without first adding one.
|
||||
self.assertRaises(tuf.Error, tuf.log.set_console_log_level)
|
||||
|
||||
# Normal case. Default log level. Setting the console log level first
|
||||
# requires adding a console logger.
|
||||
tuf.log.add_console_handler()
|
||||
tuf.log.set_console_log_level()
|
||||
|
||||
# Expected log levels.
|
||||
for level in log_levels:
|
||||
tuf.log.set_console_log_level(level)
|
||||
|
||||
# Test for improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError, tuf.log.set_console_log_level, '123')
|
||||
|
||||
# Test for invalid argument.
|
||||
self.assertRaises(tuf.FormatError, tuf.log.set_console_log_level, 51)
|
||||
|
||||
|
||||
|
||||
def test_add_console_handler(self):
|
||||
pass
|
||||
# Normal case. Default log level.
|
||||
tuf.log.add_console_handler()
|
||||
|
||||
# Adding a console handler when one has already been added.
|
||||
tuf.log.add_console_handler()
|
||||
|
||||
# Expected log levels.
|
||||
for level in log_levels:
|
||||
tuf.log.set_console_log_level(level)
|
||||
|
||||
# Test for improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError, tuf.log.add_console_handler, '123')
|
||||
|
||||
# Test for invalid argument.
|
||||
self.assertRaises(tuf.FormatError, tuf.log.add_console_handler, 51)
|
||||
|
||||
try:
|
||||
raise TypeError('Test exception output in the console.')
|
||||
|
||||
except TypeError as e:
|
||||
logger.error(e)
|
||||
|
||||
|
||||
def test_remove_console_handler(self):
|
||||
pass
|
||||
# Normal case.
|
||||
tuf.log.remove_console_handler()
|
||||
|
||||
# Removing a console handler that has not been added. Logs a warning.
|
||||
tuf.log.remove_console_handler()
|
||||
|
||||
|
||||
|
||||
# Run unit test.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program>
|
||||
test_mirrors.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
Konstantin Andrianov.
|
||||
|
||||
<Started>
|
||||
March 26, 2012
|
||||
March 26, 2012.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
|
@ -15,7 +17,13 @@
|
|||
Unit test for 'mirrors.py'.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
|
||||
|
|
@ -23,7 +31,7 @@
|
|||
import tuf.formats as formats
|
||||
import tuf.mirrors as mirrors
|
||||
import tuf.unittest_toolbox as unittest_toolbox
|
||||
|
||||
import tuf._vendor.six as six
|
||||
|
||||
|
||||
class TestMirrors(unittest_toolbox.Modified_TestCase):
|
||||
|
|
@ -53,18 +61,18 @@ def setUp(self):
|
|||
def test_get_list_of_mirrors(self):
|
||||
# Test: Normal case.
|
||||
mirror_list = mirrors.get_list_of_mirrors('meta', 'release.txt', self.mirrors)
|
||||
self.assertEquals(len(mirror_list), 3)
|
||||
for mirror, mirror_info in self.mirrors.items():
|
||||
self.assertEqual(len(mirror_list), 3)
|
||||
for mirror, mirror_info in six.iteritems(self.mirrors):
|
||||
url = mirror_info['url_prefix']+'/metadata/release.txt'
|
||||
self.assertTrue(url in mirror_list)
|
||||
|
||||
mirror_list = mirrors.get_list_of_mirrors('target', 'a.txt', self.mirrors)
|
||||
self.assertEquals(len(mirror_list), 1)
|
||||
self.assertEqual(len(mirror_list), 1)
|
||||
self.assertTrue(self.mirrors['mirror1']['url_prefix']+'/targets/a.txt' in \
|
||||
mirror_list)
|
||||
|
||||
mirror_list = mirrors.get_list_of_mirrors('target', 'a/b', self.mirrors)
|
||||
self.assertEquals(len(mirror_list), 1)
|
||||
self.assertEqual(len(mirror_list), 1)
|
||||
self.assertTrue(self.mirrors['mirror1']['url_prefix']+'/targets/a/b' in \
|
||||
mirror_list)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,23 +27,30 @@
|
|||
Note: There is no difference between 'updates' and 'target' files.
|
||||
"""
|
||||
|
||||
# Help with Python 3 compatability, where the print statement is a function, an
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import urllib
|
||||
import tempfile
|
||||
import random
|
||||
import time
|
||||
import shutil
|
||||
import json
|
||||
import subprocess
|
||||
import unittest
|
||||
import logging
|
||||
import sys
|
||||
|
||||
# 'unittest2' required for testing under Python < 2.7.
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest
|
||||
|
||||
else:
|
||||
import unittest2 as unittest
|
||||
|
||||
import tuf.formats
|
||||
import tuf.util
|
||||
|
|
@ -51,6 +58,7 @@
|
|||
import tuf.client.updater as updater
|
||||
import tuf.repository_tool as repo_tool
|
||||
import tuf.unittest_toolbox as unittest_toolbox
|
||||
import tuf._vendor.six as six
|
||||
|
||||
# The repository tool is imported and logs console messages by default. Disable
|
||||
# console log messages generated by this unit test.
|
||||
|
|
@ -88,7 +96,7 @@ def setUpClass(cls):
|
|||
|
||||
# NOTE: Following error is raised if a delay is not applied:
|
||||
# <urlopen error [Errno 111] Connection refused>
|
||||
time.sleep(.2)
|
||||
time.sleep(.8)
|
||||
|
||||
|
||||
|
||||
|
|
@ -206,8 +214,8 @@ def test_with_tuf(self):
|
|||
|
||||
# Modify a 'role1.json' target file, and add it to its metadata so that a
|
||||
# new version is generated.
|
||||
with open(file3_path, 'wb') as file_object:
|
||||
file_object.write('update file3')
|
||||
with open(file3_path, 'wt') as file_object:
|
||||
file_object.write('This is role2\'s target file.')
|
||||
repository.targets('role1').add_target(file3_path)
|
||||
|
||||
repository.write()
|
||||
|
|
@ -221,7 +229,7 @@ def test_with_tuf(self):
|
|||
shutil.move(backup_role1, role1_path)
|
||||
|
||||
# Verify that the TUF client detects unexpected metadata (previously valid,
|
||||
# but not up-to-date with the latest snashot of the repository) and refuses
|
||||
# but not up-to-date with the latest snapshot of the repository) and refuses
|
||||
# to continue the update process.
|
||||
# Refresh top-level metadata so that the client is aware of the latest
|
||||
# snapshot of the repository.
|
||||
|
|
@ -232,8 +240,8 @@ def test_with_tuf(self):
|
|||
|
||||
# Verify that the specific 'tuf.BadHashError' exception is raised by each
|
||||
# mirror.
|
||||
except tuf.NoWorkingMirrorError, exception:
|
||||
for mirror_url, mirror_error in exception.mirror_errors.iteritems():
|
||||
except tuf.NoWorkingMirrorError as exception:
|
||||
for mirror_url, mirror_error in six.iteritems(exception.mirror_errors):
|
||||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'metadata', 'targets', 'role1.json')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
test_pycrypto_keys.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
October 10, 2013.
|
||||
|
|
@ -15,6 +17,14 @@
|
|||
Test cases for test_pycrypto_keys.py.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import logging
|
||||
|
||||
|
|
@ -56,7 +66,7 @@ def test_generate_rsa_public_and_private(self):
|
|||
|
||||
def test_create_rsa_signature(self):
|
||||
global private_rsa
|
||||
data = 'The quick brown fox jumps over the lazy dog'
|
||||
data = 'The quick brown fox jumps over the lazy dog'.encode('utf-8')
|
||||
signature, method = pycrypto.create_rsa_signature(private_rsa, data)
|
||||
|
||||
# Verify format of returned values.
|
||||
|
|
@ -65,9 +75,12 @@ def test_create_rsa_signature(self):
|
|||
FORMAT_ERROR_MSG)
|
||||
self.assertEqual('RSASSA-PSS', method)
|
||||
|
||||
# Check for improperly formatted argument.
|
||||
# Check for improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
pycrypto.create_rsa_signature, 123, data)
|
||||
|
||||
self.assertRaises(TypeError,
|
||||
pycrypto.create_rsa_signature, '', data)
|
||||
|
||||
# Check for invalid 'data'.
|
||||
self.assertRaises(tuf.CryptoError,
|
||||
|
|
@ -77,7 +90,7 @@ def test_create_rsa_signature(self):
|
|||
def test_verify_rsa_signature(self):
|
||||
global public_rsa
|
||||
global private_rsa
|
||||
data = 'The quick brown fox jumps over the lazy dog'
|
||||
data = 'The quick brown fox jumps over the lazy dog'.encode('utf-8')
|
||||
signature, method = pycrypto.create_rsa_signature(private_rsa, data)
|
||||
|
||||
valid_signature = pycrypto.verify_rsa_signature(signature, method, public_rsa,
|
||||
|
|
@ -94,21 +107,25 @@ def test_verify_rsa_signature(self):
|
|||
self.assertRaises(tuf.FormatError, pycrypto.verify_rsa_signature, 123, method,
|
||||
public_rsa, data)
|
||||
|
||||
self.assertRaises(tuf.UnknownMethodError, pycrypto.verify_rsa_signature,
|
||||
signature,
|
||||
'invalid_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'))
|
||||
public_rsa, b'mismatched data'))
|
||||
|
||||
mismatched_signature, method = pycrypto.create_rsa_signature(private_rsa,
|
||||
'mismatched data')
|
||||
b'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
|
||||
|
|
@ -134,8 +151,13 @@ def test_create_rsa_encrypted_pem(self):
|
|||
pycrypto.create_rsa_encrypted_pem, 1, passphrase)
|
||||
self.assertRaises(tuf.FormatError,
|
||||
pycrypto.create_rsa_encrypted_pem, private_rsa, ['pw'])
|
||||
|
||||
|
||||
|
||||
self.assertRaises(tuf.CryptoError, pycrypto.create_rsa_encrypted_pem,
|
||||
'abc', passphrase)
|
||||
self.assertRaises(TypeError, pycrypto.create_rsa_encrypted_pem, '', passphrase)
|
||||
|
||||
|
||||
|
||||
def test_create_rsa_public_and_private_from_encrypted_pem(self):
|
||||
global private_rsa
|
||||
passphrase = 'pw'
|
||||
|
|
@ -190,7 +212,83 @@ def test_create_rsa_public_and_private_from_encrypted_pem(self):
|
|||
self.assertRaises(tuf.CryptoError,
|
||||
pycrypto.create_rsa_public_and_private_from_encrypted_pem,
|
||||
'invalid_pem', passphrase)
|
||||
|
||||
|
||||
|
||||
|
||||
def test_encrypt_key(self):
|
||||
# Test for valid arguments.
|
||||
global public_rsa
|
||||
global private_rsa
|
||||
passphrase = 'pw'
|
||||
|
||||
rsa_key = {'keytype': 'rsa',
|
||||
'keyid': 'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d',
|
||||
'keyval': {'public': public_rsa, 'private': private_rsa}}
|
||||
|
||||
encrypted_rsa_key = tuf.pycrypto_keys.encrypt_key(rsa_key, passphrase)
|
||||
|
||||
# Test for invalid arguments.
|
||||
rsa_key['keyval']['private'] = ''
|
||||
self.assertRaises(tuf.FormatError, tuf.pycrypto_keys.encrypt_key, rsa_key,
|
||||
'passphrase')
|
||||
|
||||
|
||||
def test_decrypt_key(self):
|
||||
# Test for valid arguments.
|
||||
global public_rsa
|
||||
global private_rsa
|
||||
passphrase = 'pw'
|
||||
|
||||
rsa_key = {'keytype': 'rsa',
|
||||
'keyid': 'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d',
|
||||
'keyval': {'public': public_rsa, 'private': private_rsa}}
|
||||
|
||||
encrypted_rsa_key = tuf.pycrypto_keys.encrypt_key(rsa_key, passphrase).encode('utf-8')
|
||||
|
||||
decrypted_rsa_key = tuf.pycrypto_keys.decrypt_key(encrypted_rsa_key, passphrase)
|
||||
|
||||
|
||||
# Test for invalid arguments.
|
||||
self.assertRaises(tuf.CryptoError, tuf.pycrypto_keys.decrypt_key, b'bad',
|
||||
passphrase)
|
||||
|
||||
# Test for invalid encrypted content (i.e., invalid hmac and ciphertext.)
|
||||
encryption_delimiter = tuf.pycrypto_keys._ENCRYPTION_DELIMITER
|
||||
salt, iterations, hmac, iv, ciphertext = \
|
||||
encrypted_rsa_key.decode('utf-8').split(encryption_delimiter)
|
||||
|
||||
# Set an invalid hmac. The decryption routine sould raise a tuf.CryptoError
|
||||
# exception because 'hmac' does not match the hmac calculated by the
|
||||
# decryption routine.
|
||||
bad_hmac = '12345abcd'
|
||||
invalid_encrypted_rsa_key = \
|
||||
salt + encryption_delimiter + iterations + encryption_delimiter + \
|
||||
bad_hmac + encryption_delimiter + iv + encryption_delimiter + ciphertext
|
||||
|
||||
self.assertRaises(tuf.CryptoError, tuf.pycrypto_keys.decrypt_key,
|
||||
invalid_encrypted_rsa_key.encode('utf-8'), passphrase)
|
||||
|
||||
# Test for invalid 'ciphertext'
|
||||
bad_ciphertext = '12345abcde'
|
||||
invalid_encrypted_rsa_key = \
|
||||
salt + encryption_delimiter + iterations + encryption_delimiter + \
|
||||
hmac + encryption_delimiter + iv + encryption_delimiter + bad_ciphertext
|
||||
|
||||
self.assertRaises(tuf.CryptoError, tuf.pycrypto_keys.decrypt_key,
|
||||
invalid_encrypted_rsa_key.encode('utf-8'), passphrase)
|
||||
|
||||
|
||||
|
||||
def test__decrypt_key(self):
|
||||
# Test for invalid arguments.
|
||||
salt, iterations, derived_key = tuf.pycrypto_keys._generate_derived_key('pw')
|
||||
derived_key_information = {'salt': salt, 'derived_key': derived_key,
|
||||
'iterations': iterations}
|
||||
|
||||
self.assertRaises(tuf.CryptoError, tuf.pycrypto_keys._encrypt,
|
||||
8, derived_key_information)
|
||||
|
||||
|
||||
|
||||
|
||||
# Run the unit tests.
|
||||
|
|
|
|||
|
|
@ -27,15 +27,15 @@
|
|||
Note: There is no difference between 'updates' and 'target' files.
|
||||
"""
|
||||
|
||||
# Help with Python 3 compatability, where the print statement is a function, an
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import urllib
|
||||
import tempfile
|
||||
import random
|
||||
import time
|
||||
|
|
@ -43,8 +43,15 @@
|
|||
import shutil
|
||||
import json
|
||||
import subprocess
|
||||
import unittest
|
||||
import logging
|
||||
import sys
|
||||
|
||||
# 'unittest2' required for testing under Python < 2.7.
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest
|
||||
|
||||
else:
|
||||
import unittest2 as unittest
|
||||
|
||||
import tuf.formats
|
||||
import tuf.util
|
||||
|
|
@ -52,6 +59,7 @@
|
|||
import tuf.client.updater as updater
|
||||
import tuf.repository_tool as repo_tool
|
||||
import tuf.unittest_toolbox as unittest_toolbox
|
||||
import tuf._vendor.six as six
|
||||
|
||||
# The repository tool is imported and logs console messages by default. Disable
|
||||
# console log messages generated by this unit test.
|
||||
|
|
@ -89,7 +97,7 @@ def setUpClass(cls):
|
|||
|
||||
# NOTE: Following error is raised if a delay is not applied:
|
||||
# <urlopen error [Errno 111] Connection refused>
|
||||
time.sleep(.2)
|
||||
time.sleep(.8)
|
||||
|
||||
|
||||
|
||||
|
|
@ -207,7 +215,7 @@ def test_without_tuf(self):
|
|||
|
||||
# Set an arbitrary expiration so that the repository tool generates a new
|
||||
# version.
|
||||
repository.timestamp.expiration = datetime.datetime(2030, 01, 01, 12, 12)
|
||||
repository.timestamp.expiration = datetime.datetime(2030, 1, 1, 12, 12)
|
||||
repository.write()
|
||||
|
||||
# Move the staged metadata to the "live" metadata.
|
||||
|
|
@ -225,7 +233,7 @@ def test_without_tuf(self):
|
|||
client_timestamp_path = os.path.join(self.client_directory, 'metadata',
|
||||
'current', 'timestamp.json')
|
||||
|
||||
urllib.urlretrieve(url_file, client_timestamp_path)
|
||||
six.moves.urllib.request.urlretrieve(url_file, client_timestamp_path)
|
||||
|
||||
length, hashes = tuf.util.get_file_details(client_timestamp_path)
|
||||
download_fileinfo = tuf.formats.make_fileinfo(length, hashes)
|
||||
|
|
@ -237,7 +245,7 @@ def test_without_tuf(self):
|
|||
# and verify that the non-TUF client downloads it (expected, but not ideal).
|
||||
shutil.move(backup_timestamp, timestamp_path)
|
||||
|
||||
urllib.urlretrieve(url_file, client_timestamp_path)
|
||||
six.moves.urllib.request.urlretrieve(url_file, client_timestamp_path)
|
||||
|
||||
length, hashes = tuf.util.get_file_details(client_timestamp_path)
|
||||
download_fileinfo = tuf.formats.make_fileinfo(length, hashes)
|
||||
|
|
@ -278,7 +286,7 @@ def test_with_tuf(self):
|
|||
|
||||
# Set an arbitrary expiration so that the repository tool generates a new
|
||||
# version.
|
||||
repository.timestamp.expiration = datetime.datetime(2030, 01, 01, 12, 12)
|
||||
repository.timestamp.expiration = datetime.datetime(2030, 1, 1, 12, 12)
|
||||
repository.write()
|
||||
|
||||
# Move the staged metadata to the "live" metadata.
|
||||
|
|
@ -314,8 +322,8 @@ def test_with_tuf(self):
|
|||
|
||||
# Verify that the specific 'tuf.ReplayedMetadataError' is raised by each
|
||||
# mirror.
|
||||
except tuf.NoWorkingMirrorError, exception:
|
||||
for mirror_url, mirror_error in exception.mirror_errors.iteritems():
|
||||
except tuf.NoWorkingMirrorError as exception:
|
||||
for mirror_url, mirror_error in six.iteritems(exception.mirror_errors):
|
||||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'metadata', 'timestamp.json')
|
||||
|
||||
|
|
|
|||
729
tests/test_repository_lib.py
Executable file
729
tests/test_repository_lib.py
Executable file
|
|
@ -0,0 +1,729 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
test_repository_lib.py
|
||||
|
||||
<Author>
|
||||
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
||||
|
||||
<Started>
|
||||
June 1, 2014.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Unit test for 'repository_lib.py'.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import time
|
||||
import datetime
|
||||
import logging
|
||||
import tempfile
|
||||
import json
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
# 'unittest2' required for testing under Python < 2.7.
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest
|
||||
|
||||
else:
|
||||
import unittest2 as unittest
|
||||
|
||||
import tuf
|
||||
import tuf.log
|
||||
import tuf.formats
|
||||
import tuf.roledb
|
||||
import tuf.keydb
|
||||
import tuf.hash
|
||||
import tuf.repository_lib as repo_lib
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logger = logging.getLogger('tuf.test_repository_lib')
|
||||
|
||||
repo_lib.disable_console_log_messages()
|
||||
|
||||
|
||||
|
||||
class TestRepositoryToolFunctions(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
# setUpClass() is called before tests in an individual class are executed.
|
||||
|
||||
# Create a temporary directory to store the repository, metadata, and target
|
||||
# files. 'temporary_directory' must be deleted in TearDownClass() so that
|
||||
# temporary files are always removed, even when exceptions occur.
|
||||
cls.temporary_directory = tempfile.mkdtemp(dir=os.getcwd())
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
# tearDownModule() is called after all the tests have run.
|
||||
# http://docs.python.org/2/library/unittest.html#class-and-module-fixtures
|
||||
|
||||
# Remove the temporary repository directory, which should contain all the
|
||||
# metadata, targets, and key files generated for the test cases.
|
||||
shutil.rmtree(cls.temporary_directory)
|
||||
|
||||
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def test_generate_and_write_rsa_keypair(self):
|
||||
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
test_keypath = os.path.join(temporary_directory, 'rsa_key')
|
||||
|
||||
repo_lib.generate_and_write_rsa_keypair(test_keypath, password='pw')
|
||||
self.assertTrue(os.path.exists(test_keypath))
|
||||
self.assertTrue(os.path.exists(test_keypath + '.pub'))
|
||||
|
||||
# Ensure the generated key files are importable.
|
||||
imported_pubkey = \
|
||||
repo_lib.import_rsa_publickey_from_file(test_keypath + '.pub')
|
||||
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(imported_pubkey))
|
||||
|
||||
imported_privkey = \
|
||||
repo_lib.import_rsa_privatekey_from_file(test_keypath, 'pw')
|
||||
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(imported_privkey))
|
||||
|
||||
# Custom 'bits' argument.
|
||||
os.remove(test_keypath)
|
||||
os.remove(test_keypath + '.pub')
|
||||
repo_lib.generate_and_write_rsa_keypair(test_keypath, bits=2048,
|
||||
password='pw')
|
||||
self.assertTrue(os.path.exists(test_keypath))
|
||||
self.assertTrue(os.path.exists(test_keypath + '.pub'))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_and_write_rsa_keypair,
|
||||
3, bits=2048, password='pw')
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_and_write_rsa_keypair,
|
||||
test_keypath, bits='bad', password='pw')
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_and_write_rsa_keypair,
|
||||
test_keypath, bits=2048, password=3)
|
||||
|
||||
|
||||
# Test invalid 'bits' argument.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_and_write_rsa_keypair,
|
||||
test_keypath, bits=1024, password='pw')
|
||||
|
||||
|
||||
|
||||
def test_import_rsa_privatekey_from_file(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
|
||||
# Load one of the pre-generated key files from 'tuf/tests/repository_data'.
|
||||
# 'password' unlocks the pre-generated key files.
|
||||
key_filepath = os.path.join('repository_data', 'keystore',
|
||||
'root_key')
|
||||
self.assertTrue(os.path.exists(key_filepath))
|
||||
|
||||
imported_rsa_key = repo_lib.import_rsa_privatekey_from_file(key_filepath,
|
||||
'password')
|
||||
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(imported_rsa_key))
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_lib.import_rsa_privatekey_from_file, 3, 'pw')
|
||||
|
||||
|
||||
# Test invalid argument.
|
||||
# Non-existent key file.
|
||||
nonexistent_keypath = os.path.join(temporary_directory,
|
||||
'nonexistent_keypath')
|
||||
self.assertRaises(IOError, repo_lib.import_rsa_privatekey_from_file,
|
||||
nonexistent_keypath, 'pw')
|
||||
|
||||
# Invalid key file argument.
|
||||
invalid_keyfile = os.path.join(temporary_directory, 'invalid_keyfile')
|
||||
with open(invalid_keyfile, 'wb') as file_object:
|
||||
file_object.write(b'bad keyfile')
|
||||
self.assertRaises(tuf.CryptoError, repo_lib.import_rsa_privatekey_from_file,
|
||||
invalid_keyfile, 'pw')
|
||||
|
||||
|
||||
|
||||
def test_import_rsa_publickey_from_file(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
|
||||
# Load one of the pre-generated key files from 'tuf/tests/repository_data'.
|
||||
key_filepath = os.path.join('repository_data', 'keystore',
|
||||
'root_key.pub')
|
||||
self.assertTrue(os.path.exists(key_filepath))
|
||||
|
||||
imported_rsa_key = repo_lib.import_rsa_publickey_from_file(key_filepath)
|
||||
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(imported_rsa_key))
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_lib.import_rsa_privatekey_from_file, 3)
|
||||
|
||||
|
||||
# Test invalid argument.
|
||||
# Non-existent key file.
|
||||
nonexistent_keypath = os.path.join(temporary_directory,
|
||||
'nonexistent_keypath')
|
||||
self.assertRaises(IOError, repo_lib.import_rsa_publickey_from_file,
|
||||
nonexistent_keypath)
|
||||
|
||||
# Invalid key file argument.
|
||||
invalid_keyfile = os.path.join(temporary_directory, 'invalid_keyfile')
|
||||
with open(invalid_keyfile, 'wb') as file_object:
|
||||
file_object.write(b'bad keyfile')
|
||||
self.assertRaises(tuf.Error, repo_lib.import_rsa_publickey_from_file,
|
||||
invalid_keyfile)
|
||||
|
||||
|
||||
|
||||
def test_generate_and_write_ed25519_keypair(self):
|
||||
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
test_keypath = os.path.join(temporary_directory, 'ed25519_key')
|
||||
|
||||
repo_lib.generate_and_write_ed25519_keypair(test_keypath, password='pw')
|
||||
self.assertTrue(os.path.exists(test_keypath))
|
||||
self.assertTrue(os.path.exists(test_keypath + '.pub'))
|
||||
|
||||
# Ensure the generated key files are importable.
|
||||
imported_pubkey = \
|
||||
repo_lib.import_ed25519_publickey_from_file(test_keypath + '.pub')
|
||||
self.assertTrue(tuf.formats.ED25519KEY_SCHEMA.matches(imported_pubkey))
|
||||
|
||||
imported_privkey = \
|
||||
repo_lib.import_ed25519_privatekey_from_file(test_keypath, 'pw')
|
||||
self.assertTrue(tuf.formats.ED25519KEY_SCHEMA.matches(imported_privkey))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_lib.generate_and_write_ed25519_keypair,
|
||||
3, password='pw')
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_and_write_rsa_keypair,
|
||||
test_keypath, password=3)
|
||||
|
||||
|
||||
|
||||
def test_import_ed25519_publickey_from_file(self):
|
||||
# Test normal case.
|
||||
# Generate ed25519 keys that can be imported.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
ed25519_keypath = os.path.join(temporary_directory, 'ed25519_key')
|
||||
repo_lib.generate_and_write_ed25519_keypair(ed25519_keypath, password='pw')
|
||||
|
||||
imported_ed25519_key = \
|
||||
repo_lib.import_ed25519_publickey_from_file(ed25519_keypath + '.pub')
|
||||
self.assertTrue(tuf.formats.ED25519KEY_SCHEMA.matches(imported_ed25519_key))
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_lib.import_ed25519_publickey_from_file, 3)
|
||||
|
||||
|
||||
# Test invalid argument.
|
||||
# Non-existent key file.
|
||||
nonexistent_keypath = os.path.join(temporary_directory,
|
||||
'nonexistent_keypath')
|
||||
self.assertRaises(IOError, repo_lib.import_ed25519_publickey_from_file,
|
||||
nonexistent_keypath)
|
||||
|
||||
# Invalid key file argument.
|
||||
invalid_keyfile = os.path.join(temporary_directory, 'invalid_keyfile')
|
||||
with open(invalid_keyfile, 'wb') as file_object:
|
||||
file_object.write(b'bad keyfile')
|
||||
|
||||
self.assertRaises(tuf.Error, repo_lib.import_ed25519_publickey_from_file,
|
||||
invalid_keyfile)
|
||||
|
||||
# Invalid public key imported (contains unexpected keytype.)
|
||||
keytype = imported_ed25519_key['keytype']
|
||||
keyval = imported_ed25519_key['keyval']
|
||||
ed25519key_metadata_format = \
|
||||
tuf.keys.format_keyval_to_metadata(keytype, keyval, private=False)
|
||||
|
||||
ed25519key_metadata_format['keytype'] = 'invalid_keytype'
|
||||
with open(ed25519_keypath + '.pub', 'wb') as file_object:
|
||||
file_object.write(json.dumps(ed25519key_metadata_format).encode('utf-8'))
|
||||
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_lib.import_ed25519_publickey_from_file,
|
||||
ed25519_keypath + '.pub')
|
||||
|
||||
|
||||
|
||||
def test_import_ed25519_privatekey_from_file(self):
|
||||
# Test normal case.
|
||||
# Generate ed25519 keys that can be imported.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
ed25519_keypath = os.path.join(temporary_directory, 'ed25519_key')
|
||||
repo_lib.generate_and_write_ed25519_keypair(ed25519_keypath, password='pw')
|
||||
|
||||
imported_ed25519_key = \
|
||||
repo_lib.import_ed25519_privatekey_from_file(ed25519_keypath, 'pw')
|
||||
self.assertTrue(tuf.formats.ED25519KEY_SCHEMA.matches(imported_ed25519_key))
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_lib.import_ed25519_privatekey_from_file, 3, 'pw')
|
||||
|
||||
|
||||
# Test invalid argument.
|
||||
# Non-existent key file.
|
||||
nonexistent_keypath = os.path.join(temporary_directory,
|
||||
'nonexistent_keypath')
|
||||
self.assertRaises(IOError, repo_lib.import_ed25519_privatekey_from_file,
|
||||
nonexistent_keypath, 'pw')
|
||||
|
||||
# Invalid key file argument.
|
||||
invalid_keyfile = os.path.join(temporary_directory, 'invalid_keyfile')
|
||||
with open(invalid_keyfile, 'wb') as file_object:
|
||||
file_object.write(b'bad keyfile')
|
||||
|
||||
self.assertRaises(tuf.Error, repo_lib.import_ed25519_privatekey_from_file,
|
||||
invalid_keyfile, 'pw')
|
||||
|
||||
# Invalid private key imported (contains unexpected keytype.)
|
||||
imported_ed25519_key['keytype'] = 'invalid_keytype'
|
||||
|
||||
# Use 'pycrypto_keys.py' to bypass the key format validation performed by
|
||||
# 'keys.py'.
|
||||
salt, iterations, derived_key = \
|
||||
tuf.pycrypto_keys._generate_derived_key('pw')
|
||||
|
||||
# Store the derived key info in a dictionary, the object expected
|
||||
# by the non-public _encrypt() routine.
|
||||
derived_key_information = {'salt': salt, 'iterations': iterations,
|
||||
'derived_key': derived_key}
|
||||
|
||||
# Convert the key object to json string format and encrypt it with the
|
||||
# derived key.
|
||||
encrypted_key = \
|
||||
tuf.pycrypto_keys._encrypt(json.dumps(imported_ed25519_key),
|
||||
derived_key_information)
|
||||
|
||||
with open(ed25519_keypath, 'wb') as file_object:
|
||||
file_object.write(encrypted_key.encode('utf-8'))
|
||||
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_lib.import_ed25519_privatekey_from_file,
|
||||
ed25519_keypath, 'pw')
|
||||
|
||||
|
||||
|
||||
def test_get_metadata_filenames(self):
|
||||
|
||||
# Test normal case.
|
||||
metadata_directory = os.path.join('metadata/')
|
||||
filenames = {'root.json': metadata_directory + 'root.json',
|
||||
'targets.json': metadata_directory + 'targets.json',
|
||||
'snapshot.json': metadata_directory + 'snapshot.json',
|
||||
'timestamp.json': metadata_directory + 'timestamp.json'}
|
||||
|
||||
self.assertEqual(filenames, repo_lib.get_metadata_filenames('metadata/'))
|
||||
|
||||
# If a directory argument is not specified, the current working directory
|
||||
# is used.
|
||||
metadata_directory = os.getcwd()
|
||||
filenames = {'root.json': os.path.join(metadata_directory, 'root.json'),
|
||||
'targets.json': os.path.join(metadata_directory, 'targets.json'),
|
||||
'snapshot.json': os.path.join(metadata_directory, 'snapshot.json'),
|
||||
'timestamp.json': os.path.join(metadata_directory, 'timestamp.json')}
|
||||
self.assertEqual(filenames, repo_lib.get_metadata_filenames())
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.get_metadata_filenames, 3)
|
||||
|
||||
|
||||
|
||||
def test_get_metadata_fileinfo(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
test_filepath = os.path.join(temporary_directory, 'file.txt')
|
||||
|
||||
with open(test_filepath, 'wt') as file_object:
|
||||
file_object.write('test file')
|
||||
|
||||
# Generate test fileinfo object. It is assumed SHA256 hashes are computed
|
||||
# by get_metadata_fileinfo().
|
||||
file_length = os.path.getsize(test_filepath)
|
||||
digest_object = tuf.hash.digest_filename(test_filepath)
|
||||
file_hashes = {'sha256': digest_object.hexdigest()}
|
||||
fileinfo = {'length': file_length, 'hashes': file_hashes}
|
||||
self.assertTrue(tuf.formats.FILEINFO_SCHEMA.matches(fileinfo))
|
||||
|
||||
self.assertEqual(fileinfo, repo_lib.get_metadata_fileinfo(test_filepath))
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.get_metadata_fileinfo, 3)
|
||||
|
||||
|
||||
# Test non-existent file.
|
||||
nonexistent_filepath = os.path.join(temporary_directory, 'oops.txt')
|
||||
self.assertRaises(tuf.Error, repo_lib.get_metadata_fileinfo,
|
||||
nonexistent_filepath)
|
||||
|
||||
|
||||
|
||||
def test_get_target_hash(self):
|
||||
# Test normal case.
|
||||
expected_target_hashes = {
|
||||
'/file1.txt': 'e3a3d89eb3b70ce3fbce6017d7b8c12d4abd5635427a0e8a238f53157df85b3d',
|
||||
'/README.txt': '8faee106f1bb69f34aaf1df1e3c2e87d763c4d878cb96b91db13495e32ceb0b0',
|
||||
'/packages/file2.txt': 'c9c4a5cdd84858dd6a23d98d7e6e6b2aec45034946c16b2200bc317c75415e92'
|
||||
}
|
||||
for filepath, target_hash in six.iteritems(expected_target_hashes):
|
||||
self.assertTrue(tuf.formats.RELPATH_SCHEMA.matches(filepath))
|
||||
self.assertTrue(tuf.formats.HASH_SCHEMA.matches(target_hash))
|
||||
self.assertEqual(repo_lib.get_target_hash(filepath), target_hash)
|
||||
|
||||
# Test for improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.get_target_hash, 8)
|
||||
|
||||
|
||||
|
||||
def test_generate_root_metadata(self):
|
||||
# Test normal case.
|
||||
# Load the root metadata provided in 'tuf/tests/repository_data/'.
|
||||
root_filepath = os.path.join('repository_data', 'repository',
|
||||
'metadata', 'root.json')
|
||||
root_signable = tuf.util.load_json_file(root_filepath)
|
||||
|
||||
# generate_root_metadata() expects the top-level roles and keys to be
|
||||
# available in 'tuf.keydb' and 'tuf.roledb'.
|
||||
tuf.roledb.create_roledb_from_root_metadata(root_signable['signed'])
|
||||
tuf.keydb.create_keydb_from_root_metadata(root_signable['signed'])
|
||||
expires = '1985-10-21T01:22:00Z'
|
||||
|
||||
root_metadata = repo_lib.generate_root_metadata(1, expires,
|
||||
consistent_snapshot=False)
|
||||
self.assertTrue(tuf.formats.ROOT_SCHEMA.matches(root_metadata))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_root_metadata,
|
||||
'3', expires, False)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_root_metadata,
|
||||
1, '3', False)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_root_metadata,
|
||||
1, expires, 3)
|
||||
|
||||
# Test for missing required roles and keys.
|
||||
tuf.roledb.clear_roledb()
|
||||
tuf.keydb.clear_keydb()
|
||||
self.assertRaises(tuf.Error, repo_lib.generate_root_metadata,
|
||||
1, expires, False)
|
||||
|
||||
|
||||
|
||||
def test_generate_targets_metadata(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
targets_directory = os.path.join(temporary_directory, 'targets')
|
||||
file1_path = os.path.join(targets_directory, 'file.txt')
|
||||
tuf.util.ensure_parent_dir(file1_path)
|
||||
|
||||
with open(file1_path, 'wt') as file_object:
|
||||
file_object.write('test file.')
|
||||
|
||||
# Set valid generate_targets_metadata() arguments.
|
||||
version = 1
|
||||
datetime_object = datetime.datetime(2030, 1, 1, 12, 0)
|
||||
expiration_date = datetime_object.isoformat() + 'Z'
|
||||
target_files = ['file.txt']
|
||||
|
||||
delegations = {"keys": {
|
||||
"a394c28384648328b16731f81440d72243c77bb44c07c040be99347f0df7d7bf": {
|
||||
"keytype": "ed25519",
|
||||
"keyval": {
|
||||
"public": "3eb81026ded5af2c61fb3d4b272ac53cd1049a810ee88f4df1fc35cdaf918157"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": [
|
||||
{
|
||||
"keyids": [
|
||||
"a394c28384648328b16731f81440d72243c77bb44c07c040be99347f0df7d7bf"
|
||||
],
|
||||
"name": "targets/warehouse",
|
||||
"paths": [
|
||||
"/file1.txt", "/README.txt", '/warehouse/'
|
||||
],
|
||||
"threshold": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
targets_metadata = \
|
||||
repo_lib.generate_targets_metadata(targets_directory, target_files,
|
||||
version, expiration_date, delegations,
|
||||
False)
|
||||
self.assertTrue(tuf.formats.TARGETS_SCHEMA.matches(targets_metadata))
|
||||
|
||||
# Verify that 'digest.filename' file is saved to 'targets_directory' if
|
||||
# the 'write_consistent_targets' argument is True.
|
||||
list_targets_directory = os.listdir(targets_directory)
|
||||
targets_metadata = \
|
||||
repo_lib.generate_targets_metadata(targets_directory, target_files,
|
||||
version, expiration_date, delegations,
|
||||
write_consistent_targets=True)
|
||||
new_list_targets_directory = os.listdir(targets_directory)
|
||||
|
||||
# Verify that 'targets_directory' contains only one extra item.
|
||||
self.assertTrue(len(list_targets_directory) + 1,
|
||||
len(new_list_targets_directory))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_targets_metadata,
|
||||
3, target_files, version, expiration_date)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_targets_metadata,
|
||||
targets_directory, 3, version, expiration_date)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_targets_metadata,
|
||||
targets_directory, target_files, '3', expiration_date)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_targets_metadata,
|
||||
targets_directory, target_files, version, '3')
|
||||
|
||||
# Improperly formatted 'delegations' and 'write_consistent_targets'
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_targets_metadata,
|
||||
targets_directory, target_files, version, expiration_date,
|
||||
3, False)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_targets_metadata,
|
||||
targets_directory, target_files, version, expiration_date,
|
||||
delegations, 3)
|
||||
|
||||
|
||||
# Test invalid 'target_files' argument.
|
||||
self.assertRaises(tuf.Error, repo_lib.generate_targets_metadata,
|
||||
targets_directory, ['nonexistent_file.txt'], version,
|
||||
expiration_date)
|
||||
|
||||
|
||||
|
||||
|
||||
def test_generate_snapshot_metadata(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
original_repository_path = os.path.join('repository_data',
|
||||
'repository')
|
||||
repository_directory = os.path.join(temporary_directory, 'repository')
|
||||
shutil.copytree(original_repository_path, repository_directory)
|
||||
metadata_directory = os.path.join(repository_directory,
|
||||
repo_lib.METADATA_STAGED_DIRECTORY_NAME)
|
||||
root_filename = os.path.join(metadata_directory, repo_lib.ROOT_FILENAME)
|
||||
targets_filename = os.path.join(metadata_directory,
|
||||
repo_lib.TARGETS_FILENAME)
|
||||
version = 1
|
||||
expiration_date = '1985-10-21T13:20:00Z'
|
||||
|
||||
snapshot_metadata = \
|
||||
repo_lib.generate_snapshot_metadata(metadata_directory, version,
|
||||
expiration_date, root_filename,
|
||||
targets_filename,
|
||||
consistent_snapshot=False)
|
||||
self.assertTrue(tuf.formats.SNAPSHOT_SCHEMA.matches(snapshot_metadata))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_snapshot_metadata,
|
||||
3, version, expiration_date,
|
||||
root_filename, targets_filename, consistent_snapshot=False)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_snapshot_metadata,
|
||||
metadata_directory, '3', expiration_date,
|
||||
root_filename, targets_filename, consistent_snapshot=False)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_snapshot_metadata,
|
||||
metadata_directory, version, '3',
|
||||
root_filename, targets_filename, consistent_snapshot=False)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_snapshot_metadata,
|
||||
metadata_directory, version, expiration_date,
|
||||
3, targets_filename, consistent_snapshot=False)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_snapshot_metadata,
|
||||
metadata_directory, version, expiration_date,
|
||||
root_filename, 3, consistent_snapshot=False)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_snapshot_metadata,
|
||||
metadata_directory, version, expiration_date,
|
||||
root_filename, targets_filename, 3)
|
||||
|
||||
|
||||
|
||||
def test_generate_timestamp_metadata(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
original_repository_path = os.path.join('repository_data',
|
||||
'repository')
|
||||
repository_directory = os.path.join(temporary_directory, 'repository')
|
||||
shutil.copytree(original_repository_path, repository_directory)
|
||||
metadata_directory = os.path.join(repository_directory,
|
||||
repo_lib.METADATA_STAGED_DIRECTORY_NAME)
|
||||
snapshot_filename = os.path.join(metadata_directory,
|
||||
repo_lib.SNAPSHOT_FILENAME)
|
||||
|
||||
# Set valid generate_timestamp_metadata() arguments.
|
||||
version = 1
|
||||
expiration_date = '1985-10-21T13:20:00Z'
|
||||
|
||||
compressions = ['gz']
|
||||
|
||||
snapshot_metadata = \
|
||||
repo_lib.generate_timestamp_metadata(snapshot_filename, version,
|
||||
expiration_date, compressions)
|
||||
self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches(snapshot_metadata))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_timestamp_metadata,
|
||||
3, version, expiration_date, compressions)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_timestamp_metadata,
|
||||
snapshot_filename, '3', expiration_date, compressions)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_timestamp_metadata,
|
||||
snapshot_filename, version, '3', compressions)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_timestamp_metadata,
|
||||
snapshot_filename, version, expiration_date, 3)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.generate_timestamp_metadata,
|
||||
snapshot_filename, version, expiration_date, ['compress'])
|
||||
|
||||
|
||||
|
||||
|
||||
def test_sign_metadata(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
metadata_path = os.path.join('repository_data',
|
||||
'repository', 'metadata')
|
||||
keystore_path = os.path.join('repository_data',
|
||||
'keystore')
|
||||
root_filename = os.path.join(metadata_path, 'root.json')
|
||||
root_metadata = tuf.util.load_json_file(root_filename)['signed']
|
||||
|
||||
tuf.keydb.create_keydb_from_root_metadata(root_metadata)
|
||||
tuf.roledb.create_roledb_from_root_metadata(root_metadata)
|
||||
root_keyids = tuf.roledb.get_role_keyids('root')
|
||||
|
||||
root_private_keypath = os.path.join(keystore_path, 'root_key')
|
||||
root_private_key = \
|
||||
repo_lib.import_rsa_privatekey_from_file(root_private_keypath,
|
||||
'password')
|
||||
|
||||
# sign_metadata() expects the private key 'root_metadata' to be in
|
||||
# 'tuf.keydb'. Remove any public keys that may be loaded before
|
||||
# adding private key, otherwise a 'tuf.KeyAlreadyExists' exception is
|
||||
# raised.
|
||||
tuf.keydb.remove_key(root_private_key['keyid'])
|
||||
tuf.keydb.add_key(root_private_key)
|
||||
|
||||
root_signable = repo_lib.sign_metadata(root_metadata, root_keyids,
|
||||
root_filename)
|
||||
self.assertTrue(tuf.formats.SIGNABLE_SCHEMA.matches(root_signable))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.sign_metadata, 3, root_keyids,
|
||||
'root.json')
|
||||
self.assertRaises(tuf.FormatError, repo_lib.sign_metadata, root_metadata,
|
||||
3, 'root.json')
|
||||
self.assertRaises(tuf.FormatError, repo_lib.sign_metadata, root_metadata,
|
||||
root_keyids, 3)
|
||||
|
||||
|
||||
|
||||
def test_write_metadata_file(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
metadata_directory = os.path.join('repository_data',
|
||||
'repository', 'metadata')
|
||||
root_filename = os.path.join(metadata_directory, 'root.json')
|
||||
root_signable = tuf.util.load_json_file(root_filename)
|
||||
|
||||
output_filename = os.path.join(temporary_directory, 'root.json')
|
||||
compressions = ['gz']
|
||||
|
||||
self.assertFalse(os.path.exists(output_filename))
|
||||
repo_lib.write_metadata_file(root_signable, output_filename, compressions,
|
||||
consistent_snapshot=False)
|
||||
self.assertTrue(os.path.exists(output_filename))
|
||||
self.assertTrue(os.path.exists(output_filename + '.gz'))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.write_metadata_file,
|
||||
3, output_filename, compressions, False)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.write_metadata_file,
|
||||
root_signable, 3, compressions, False)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.write_metadata_file,
|
||||
root_signable, output_filename, 3, False)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.write_metadata_file,
|
||||
root_signable, output_filename, compressions, 3)
|
||||
|
||||
|
||||
|
||||
def test_create_tuf_client_directory(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
repository_directory = os.path.join('repository_data',
|
||||
'repository')
|
||||
client_directory = os.path.join(temporary_directory, 'client')
|
||||
|
||||
repo_lib.create_tuf_client_directory(repository_directory, client_directory)
|
||||
|
||||
self.assertTrue(os.path.exists(client_directory))
|
||||
metadata_directory = os.path.join(client_directory, 'metadata')
|
||||
current_directory = os.path.join(metadata_directory, 'current')
|
||||
previous_directory = os.path.join(metadata_directory, 'previous')
|
||||
self.assertTrue(os.path.exists(client_directory))
|
||||
self.assertTrue(os.path.exists(metadata_directory))
|
||||
self.assertTrue(os.path.exists(current_directory))
|
||||
self.assertTrue(os.path.exists(previous_directory))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_lib.create_tuf_client_directory,
|
||||
3, client_directory)
|
||||
self.assertRaises(tuf.FormatError, repo_lib.create_tuf_client_directory,
|
||||
repository_directory, 3)
|
||||
|
||||
|
||||
# Test invalid argument (i.e., client directory already exists.)
|
||||
self.assertRaises(tuf.RepositoryError, repo_lib.create_tuf_client_directory,
|
||||
repository_directory, client_directory)
|
||||
|
||||
|
||||
def test__check_directory(self):
|
||||
# Test for non-existent directory.
|
||||
self.assertRaises(tuf.Error, repo_lib._check_directory, 'non-existent')
|
||||
|
||||
|
||||
|
||||
# Run the test cases.
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
test_repository_tool.py
|
||||
|
|
@ -15,6 +17,14 @@
|
|||
Unit test for 'repository_tool.py'.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import time
|
||||
import datetime
|
||||
|
|
@ -22,6 +32,14 @@
|
|||
import logging
|
||||
import tempfile
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
# 'unittest2' required for testing under Python < 2.7.
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest
|
||||
|
||||
else:
|
||||
import unittest2 as unittest
|
||||
|
||||
import tuf
|
||||
import tuf.log
|
||||
|
|
@ -30,6 +48,7 @@
|
|||
import tuf.keydb
|
||||
import tuf.hash
|
||||
import tuf.repository_tool as repo_tool
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logger = logging.getLogger('tuf.test_repository_tool')
|
||||
|
||||
|
|
@ -222,11 +241,14 @@ def test_write_and_write_partial(self):
|
|||
# Verify that an exception is *not* raised for multiple repository.write().
|
||||
repository.write()
|
||||
|
||||
# Verify the status() does not raise an exception.
|
||||
repository.status()
|
||||
|
||||
# Verify that a write() fails if a repository is loaded and a change
|
||||
# is made to a role.
|
||||
repo_tool.load_repository(repository_directory)
|
||||
|
||||
repository.timestamp.expiration = datetime.datetime(2030, 01, 01, 12, 00)
|
||||
repository.timestamp.expiration = datetime.datetime(2030, 1, 1, 12, 0)
|
||||
self.assertRaises(tuf.UnsignedMetadataError, repository.write)
|
||||
|
||||
# Verify that a write_partial() is allowed.
|
||||
|
|
@ -370,7 +392,7 @@ def test_expiration(self):
|
|||
self.assertTrue(isinstance(expiration, datetime.datetime))
|
||||
|
||||
# Test expiration setter.
|
||||
self.metadata.expiration = datetime.datetime(2030, 01, 01, 12, 00)
|
||||
self.metadata.expiration = datetime.datetime(2030, 1, 1, 12, 0)
|
||||
expiration = self.metadata.expiration
|
||||
self.assertTrue(isinstance(expiration, datetime.datetime))
|
||||
|
||||
|
|
@ -856,7 +878,8 @@ def test_delegations(self):
|
|||
threshold = 1
|
||||
|
||||
self.targets_object.delegate(rolename, public_keys, list_of_targets,
|
||||
threshold, restricted_paths=None,
|
||||
threshold, backtrack=True,
|
||||
restricted_paths=None,
|
||||
path_hash_prefixes=None)
|
||||
|
||||
# Test that a valid Targets() object is returned by delegations().
|
||||
|
|
@ -990,8 +1013,9 @@ def test_delegate(self):
|
|||
path_hash_prefixes = ['e3a3', '8fae', 'd543']
|
||||
|
||||
self.targets_object.delegate(rolename, public_keys, list_of_targets,
|
||||
threshold, restricted_paths,
|
||||
path_hash_prefixes)
|
||||
threshold, backtrack=True,
|
||||
restricted_paths=restricted_paths,
|
||||
path_hash_prefixes=path_hash_prefixes)
|
||||
|
||||
self.assertEqual(self.targets_object.get_delegated_rolenames(),
|
||||
['targets/tuf'])
|
||||
|
|
@ -1356,594 +1380,6 @@ def test_load_repository(self):
|
|||
|
||||
|
||||
|
||||
def test_generate_and_write_rsa_keypair(self):
|
||||
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
test_keypath = os.path.join(temporary_directory, 'rsa_key')
|
||||
|
||||
repo_tool.generate_and_write_rsa_keypair(test_keypath, password='pw')
|
||||
self.assertTrue(os.path.exists(test_keypath))
|
||||
self.assertTrue(os.path.exists(test_keypath + '.pub'))
|
||||
|
||||
# Ensure the generated key files are importable.
|
||||
imported_pubkey = \
|
||||
repo_tool.import_rsa_publickey_from_file(test_keypath + '.pub')
|
||||
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(imported_pubkey))
|
||||
|
||||
imported_privkey = \
|
||||
repo_tool.import_rsa_privatekey_from_file(test_keypath, 'pw')
|
||||
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(imported_privkey))
|
||||
|
||||
# Custom 'bits' argument.
|
||||
os.remove(test_keypath)
|
||||
os.remove(test_keypath + '.pub')
|
||||
repo_tool.generate_and_write_rsa_keypair(test_keypath, bits=2048,
|
||||
password='pw')
|
||||
self.assertTrue(os.path.exists(test_keypath))
|
||||
self.assertTrue(os.path.exists(test_keypath + '.pub'))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_and_write_rsa_keypair,
|
||||
3, bits=2048, password='pw')
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_and_write_rsa_keypair,
|
||||
test_keypath, bits='bad', password='pw')
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_and_write_rsa_keypair,
|
||||
test_keypath, bits=2048, password=3)
|
||||
|
||||
|
||||
# Test invalid 'bits' argument.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_and_write_rsa_keypair,
|
||||
test_keypath, bits=1024, password='pw')
|
||||
|
||||
|
||||
|
||||
def test_import_rsa_privatekey_from_file(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
|
||||
# Load one of the pre-generated key files from 'tuf/tests/repository_data'.
|
||||
# 'password' unlocks the pre-generated key files.
|
||||
key_filepath = os.path.join('repository_data', 'keystore',
|
||||
'root_key')
|
||||
self.assertTrue(os.path.exists(key_filepath))
|
||||
|
||||
imported_rsa_key = repo_tool.import_rsa_privatekey_from_file(key_filepath,
|
||||
'password')
|
||||
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(imported_rsa_key))
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_tool.import_rsa_privatekey_from_file, 3, 'pw')
|
||||
|
||||
|
||||
# Test invalid argument.
|
||||
# Non-existent key file.
|
||||
nonexistent_keypath = os.path.join(temporary_directory,
|
||||
'nonexistent_keypath')
|
||||
self.assertRaises(IOError, repo_tool.import_rsa_privatekey_from_file,
|
||||
nonexistent_keypath, 'pw')
|
||||
|
||||
# Invalid key file argument.
|
||||
invalid_keyfile = os.path.join(temporary_directory, 'invalid_keyfile')
|
||||
with open(invalid_keyfile, 'wb') as file_object:
|
||||
file_object.write('bad keyfile')
|
||||
self.assertRaises(tuf.CryptoError, repo_tool.import_rsa_privatekey_from_file,
|
||||
invalid_keyfile, 'pw')
|
||||
|
||||
|
||||
|
||||
def test_import_rsa_publickey_from_file(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
|
||||
# Load one of the pre-generated key files from 'tuf/tests/repository_data'.
|
||||
key_filepath = os.path.join('repository_data', 'keystore',
|
||||
'root_key.pub')
|
||||
self.assertTrue(os.path.exists(key_filepath))
|
||||
|
||||
imported_rsa_key = repo_tool.import_rsa_publickey_from_file(key_filepath)
|
||||
self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(imported_rsa_key))
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_tool.import_rsa_privatekey_from_file, 3)
|
||||
|
||||
|
||||
# Test invalid argument.
|
||||
# Non-existent key file.
|
||||
nonexistent_keypath = os.path.join(temporary_directory,
|
||||
'nonexistent_keypath')
|
||||
self.assertRaises(IOError, repo_tool.import_rsa_publickey_from_file,
|
||||
nonexistent_keypath)
|
||||
|
||||
# Invalid key file argument.
|
||||
invalid_keyfile = os.path.join(temporary_directory, 'invalid_keyfile')
|
||||
with open(invalid_keyfile, 'wb') as file_object:
|
||||
file_object.write('bad keyfile')
|
||||
self.assertRaises(tuf.Error, repo_tool.import_rsa_publickey_from_file,
|
||||
invalid_keyfile)
|
||||
|
||||
|
||||
|
||||
def test_generate_and_write_ed25519_keypair(self):
|
||||
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
test_keypath = os.path.join(temporary_directory, 'ed25519_key')
|
||||
|
||||
repo_tool.generate_and_write_ed25519_keypair(test_keypath, password='pw')
|
||||
self.assertTrue(os.path.exists(test_keypath))
|
||||
self.assertTrue(os.path.exists(test_keypath + '.pub'))
|
||||
|
||||
# Ensure the generated key files are importable.
|
||||
imported_pubkey = \
|
||||
repo_tool.import_ed25519_publickey_from_file(test_keypath + '.pub')
|
||||
self.assertTrue(tuf.formats.ED25519KEY_SCHEMA.matches(imported_pubkey))
|
||||
|
||||
imported_privkey = \
|
||||
repo_tool.import_ed25519_privatekey_from_file(test_keypath, 'pw')
|
||||
self.assertTrue(tuf.formats.ED25519KEY_SCHEMA.matches(imported_privkey))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_tool.generate_and_write_ed25519_keypair,
|
||||
3, password='pw')
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_and_write_rsa_keypair,
|
||||
test_keypath, password=3)
|
||||
|
||||
|
||||
|
||||
def test_import_ed25519_publickey_from_file(self):
|
||||
# Test normal case.
|
||||
# Generate ed25519 keys that can be imported.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
ed25519_keypath = os.path.join(temporary_directory, 'ed25519_key')
|
||||
repo_tool.generate_and_write_ed25519_keypair(ed25519_keypath, password='pw')
|
||||
|
||||
imported_ed25519_key = \
|
||||
repo_tool.import_ed25519_publickey_from_file(ed25519_keypath + '.pub')
|
||||
self.assertTrue(tuf.formats.ED25519KEY_SCHEMA.matches(imported_ed25519_key))
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_tool.import_ed25519_publickey_from_file, 3)
|
||||
|
||||
|
||||
# Test invalid argument.
|
||||
# Non-existent key file.
|
||||
nonexistent_keypath = os.path.join(temporary_directory,
|
||||
'nonexistent_keypath')
|
||||
self.assertRaises(IOError, repo_tool.import_ed25519_publickey_from_file,
|
||||
nonexistent_keypath)
|
||||
|
||||
# Invalid key file argument.
|
||||
invalid_keyfile = os.path.join(temporary_directory, 'invalid_keyfile')
|
||||
with open(invalid_keyfile, 'wb') as file_object:
|
||||
file_object.write('bad keyfile')
|
||||
|
||||
self.assertRaises(tuf.Error, repo_tool.import_ed25519_publickey_from_file,
|
||||
invalid_keyfile)
|
||||
|
||||
|
||||
|
||||
def test_import_ed25519_privatekey_from_file(self):
|
||||
# Test normal case.
|
||||
# Generate ed25519 keys that can be imported.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
ed25519_keypath = os.path.join(temporary_directory, 'ed25519_key')
|
||||
repo_tool.generate_and_write_ed25519_keypair(ed25519_keypath, password='pw')
|
||||
|
||||
imported_ed25519_key = \
|
||||
repo_tool.import_ed25519_privatekey_from_file(ed25519_keypath, 'pw')
|
||||
self.assertTrue(tuf.formats.ED25519KEY_SCHEMA.matches(imported_ed25519_key))
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
repo_tool.import_ed25519_privatekey_from_file, 3, 'pw')
|
||||
|
||||
|
||||
# Test invalid argument.
|
||||
# Non-existent key file.
|
||||
nonexistent_keypath = os.path.join(temporary_directory,
|
||||
'nonexistent_keypath')
|
||||
self.assertRaises(IOError, repo_tool.import_ed25519_privatekey_from_file,
|
||||
nonexistent_keypath, 'pw')
|
||||
|
||||
# Invalid key file argument.
|
||||
invalid_keyfile = os.path.join(temporary_directory, 'invalid_keyfile')
|
||||
with open(invalid_keyfile, 'wb') as file_object:
|
||||
file_object.write('bad keyfile')
|
||||
|
||||
self.assertRaises(tuf.Error, repo_tool.import_ed25519_privatekey_from_file,
|
||||
invalid_keyfile, 'pw')
|
||||
|
||||
|
||||
|
||||
def test_get_metadata_filenames(self):
|
||||
|
||||
# Test normal case.
|
||||
metadata_directory = os.path.join('metadata/')
|
||||
filenames = {'root.json': metadata_directory + 'root.json',
|
||||
'targets.json': metadata_directory + 'targets.json',
|
||||
'snapshot.json': metadata_directory + 'snapshot.json',
|
||||
'timestamp.json': metadata_directory + 'timestamp.json'}
|
||||
|
||||
self.assertEqual(filenames, repo_tool.get_metadata_filenames('metadata/'))
|
||||
|
||||
# If a directory argument is not specified, the current working directory
|
||||
# is used.
|
||||
metadata_directory = os.getcwd()
|
||||
filenames = {'root.json': os.path.join(metadata_directory, 'root.json'),
|
||||
'targets.json': os.path.join(metadata_directory, 'targets.json'),
|
||||
'snapshot.json': os.path.join(metadata_directory, 'snapshot.json'),
|
||||
'timestamp.json': os.path.join(metadata_directory, 'timestamp.json')}
|
||||
self.assertEqual(filenames, repo_tool.get_metadata_filenames())
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.get_metadata_filenames, 3)
|
||||
|
||||
|
||||
|
||||
def test_get_metadata_fileinfo(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
test_filepath = os.path.join(temporary_directory, 'file.txt')
|
||||
|
||||
with open(test_filepath, 'wb') as file_object:
|
||||
file_object.write('test file')
|
||||
|
||||
# Generate test fileinfo object. It is assumed SHA256 hashes are computed
|
||||
# by get_metadata_fileinfo().
|
||||
file_length = os.path.getsize(test_filepath)
|
||||
digest_object = tuf.hash.digest_filename(test_filepath)
|
||||
file_hashes = {'sha256': digest_object.hexdigest()}
|
||||
fileinfo = {'length': file_length, 'hashes': file_hashes}
|
||||
self.assertTrue(tuf.formats.FILEINFO_SCHEMA.matches(fileinfo))
|
||||
|
||||
self.assertEqual(fileinfo, repo_tool.get_metadata_fileinfo(test_filepath))
|
||||
|
||||
|
||||
# Test improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.get_metadata_fileinfo, 3)
|
||||
|
||||
|
||||
# Test non-existent file.
|
||||
nonexistent_filepath = os.path.join(temporary_directory, 'oops.txt')
|
||||
self.assertRaises(tuf.Error, repo_tool.get_metadata_fileinfo,
|
||||
nonexistent_filepath)
|
||||
|
||||
|
||||
|
||||
def test_get_target_hash(self):
|
||||
# Test normal case.
|
||||
expected_target_hashes = {
|
||||
'/file1.txt': 'e3a3d89eb3b70ce3fbce6017d7b8c12d4abd5635427a0e8a238f53157df85b3d',
|
||||
'/README.txt': '8faee106f1bb69f34aaf1df1e3c2e87d763c4d878cb96b91db13495e32ceb0b0',
|
||||
'/packages/file2.txt': 'c9c4a5cdd84858dd6a23d98d7e6e6b2aec45034946c16b2200bc317c75415e92'
|
||||
}
|
||||
for filepath, target_hash in expected_target_hashes.items():
|
||||
self.assertTrue(tuf.formats.RELPATH_SCHEMA.matches(filepath))
|
||||
self.assertTrue(tuf.formats.HASH_SCHEMA.matches(target_hash))
|
||||
self.assertEqual(repo_tool.get_target_hash(filepath), target_hash)
|
||||
|
||||
# Test for improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.get_target_hash, 8)
|
||||
|
||||
|
||||
|
||||
def test_generate_root_metadata(self):
|
||||
# Test normal case.
|
||||
# Load the root metadata provided in 'tuf/tests/repository_data/'.
|
||||
root_filepath = os.path.join('repository_data', 'repository',
|
||||
'metadata', 'root.json')
|
||||
root_signable = tuf.util.load_json_file(root_filepath)
|
||||
|
||||
# generate_root_metadata() expects the top-level roles and keys to be
|
||||
# available in 'tuf.keydb' and 'tuf.roledb'.
|
||||
tuf.roledb.create_roledb_from_root_metadata(root_signable['signed'])
|
||||
tuf.keydb.create_keydb_from_root_metadata(root_signable['signed'])
|
||||
expires = '1985-10-21T01:22:00Z'
|
||||
|
||||
root_metadata = repo_tool.generate_root_metadata(1, expires,
|
||||
consistent_snapshot=False)
|
||||
self.assertTrue(tuf.formats.ROOT_SCHEMA.matches(root_metadata))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_root_metadata,
|
||||
'3', expires, False)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_root_metadata,
|
||||
1, '3', False)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_root_metadata,
|
||||
1, expires, 3)
|
||||
|
||||
# Test for missing required roles and keys.
|
||||
tuf.roledb.clear_roledb()
|
||||
tuf.keydb.clear_keydb()
|
||||
self.assertRaises(tuf.Error, repo_tool.generate_root_metadata,
|
||||
1, expires, False)
|
||||
|
||||
|
||||
|
||||
def test_generate_targets_metadata(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
targets_directory = os.path.join(temporary_directory, 'targets')
|
||||
file1_path = os.path.join(targets_directory, 'file.txt')
|
||||
tuf.util.ensure_parent_dir(file1_path)
|
||||
|
||||
with open(file1_path, 'wb') as file_object:
|
||||
file_object.write('test file.')
|
||||
|
||||
# Set valid generate_targets_metadata() arguments.
|
||||
version = 1
|
||||
datetime_object = datetime.datetime(2030, 01, 01, 12, 00)
|
||||
expiration_date = datetime_object.isoformat() + 'Z'
|
||||
target_files = ['file.txt']
|
||||
|
||||
delegations = {"keys": {
|
||||
"a394c28384648328b16731f81440d72243c77bb44c07c040be99347f0df7d7bf": {
|
||||
"keytype": "ed25519",
|
||||
"keyval": {
|
||||
"public": "3eb81026ded5af2c61fb3d4b272ac53cd1049a810ee88f4df1fc35cdaf918157"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": [
|
||||
{
|
||||
"keyids": [
|
||||
"a394c28384648328b16731f81440d72243c77bb44c07c040be99347f0df7d7bf"
|
||||
],
|
||||
"name": "targets/warehouse",
|
||||
"paths": [
|
||||
"/file1.txt", "/README.txt", '/warehouse/'
|
||||
],
|
||||
"threshold": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
targets_metadata = \
|
||||
repo_tool.generate_targets_metadata(targets_directory, target_files,
|
||||
version, expiration_date, delegations,
|
||||
False)
|
||||
self.assertTrue(tuf.formats.TARGETS_SCHEMA.matches(targets_metadata))
|
||||
|
||||
# Verify that 'digest.filename' file is saved to 'targets_directory' if
|
||||
# the 'write_consistent_targets' argument is True.
|
||||
list_targets_directory = os.listdir(targets_directory)
|
||||
targets_metadata = \
|
||||
repo_tool.generate_targets_metadata(targets_directory, target_files,
|
||||
version, expiration_date, delegations,
|
||||
write_consistent_targets=True)
|
||||
new_list_targets_directory = os.listdir(targets_directory)
|
||||
|
||||
# Verify that 'targets_directory' contains only one extra item.
|
||||
self.assertTrue(len(list_targets_directory) + 1,
|
||||
len(new_list_targets_directory))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_targets_metadata,
|
||||
3, target_files, version, expiration_date)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_targets_metadata,
|
||||
targets_directory, 3, version, expiration_date)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_targets_metadata,
|
||||
targets_directory, target_files, '3', expiration_date)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_targets_metadata,
|
||||
targets_directory, target_files, version, '3')
|
||||
|
||||
# Improperly formatted 'delegations' and 'write_consistent_targets'
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_targets_metadata,
|
||||
targets_directory, target_files, version, expiration_date,
|
||||
3, False)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_targets_metadata,
|
||||
targets_directory, target_files, version, expiration_date,
|
||||
delegations, 3)
|
||||
|
||||
|
||||
# Test invalid 'target_files' argument.
|
||||
self.assertRaises(tuf.Error, repo_tool.generate_targets_metadata,
|
||||
targets_directory, ['nonexistent_file.txt'], version,
|
||||
expiration_date)
|
||||
|
||||
|
||||
|
||||
|
||||
def test_generate_snapshot_metadata(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
original_repository_path = os.path.join('repository_data',
|
||||
'repository')
|
||||
repository_directory = os.path.join(temporary_directory, 'repository')
|
||||
shutil.copytree(original_repository_path, repository_directory)
|
||||
metadata_directory = os.path.join(repository_directory,
|
||||
repo_tool.METADATA_STAGED_DIRECTORY_NAME)
|
||||
root_filename = os.path.join(metadata_directory, repo_tool.ROOT_FILENAME)
|
||||
targets_filename = os.path.join(metadata_directory,
|
||||
repo_tool.TARGETS_FILENAME)
|
||||
version = 1
|
||||
expiration_date = '1985-10-21T13:20:00Z'
|
||||
|
||||
snapshot_metadata = \
|
||||
repo_tool.generate_snapshot_metadata(metadata_directory, version,
|
||||
expiration_date, root_filename,
|
||||
targets_filename,
|
||||
consistent_snapshot=False)
|
||||
self.assertTrue(tuf.formats.SNAPSHOT_SCHEMA.matches(snapshot_metadata))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_snapshot_metadata,
|
||||
3, version, expiration_date,
|
||||
root_filename, targets_filename, consistent_snapshot=False)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_snapshot_metadata,
|
||||
metadata_directory, '3', expiration_date,
|
||||
root_filename, targets_filename, consistent_snapshot=False)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_snapshot_metadata,
|
||||
metadata_directory, version, '3',
|
||||
root_filename, targets_filename, consistent_snapshot=False)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_snapshot_metadata,
|
||||
metadata_directory, version, expiration_date,
|
||||
3, targets_filename, consistent_snapshot=False)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_snapshot_metadata,
|
||||
metadata_directory, version, expiration_date,
|
||||
root_filename, 3, consistent_snapshot=False)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_snapshot_metadata,
|
||||
metadata_directory, version, expiration_date,
|
||||
root_filename, targets_filename, 3)
|
||||
|
||||
|
||||
|
||||
def test_generate_timestamp_metadata(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
original_repository_path = os.path.join('repository_data',
|
||||
'repository')
|
||||
repository_directory = os.path.join(temporary_directory, 'repository')
|
||||
shutil.copytree(original_repository_path, repository_directory)
|
||||
metadata_directory = os.path.join(repository_directory,
|
||||
repo_tool.METADATA_STAGED_DIRECTORY_NAME)
|
||||
snapshot_filename = os.path.join(metadata_directory,
|
||||
repo_tool.SNAPSHOT_FILENAME)
|
||||
|
||||
# Set valid generate_timestamp_metadata() arguments.
|
||||
version = 1
|
||||
expiration_date = '1985-10-21T13:20:00Z'
|
||||
|
||||
compressions = ['gz']
|
||||
|
||||
snapshot_metadata = \
|
||||
repo_tool.generate_timestamp_metadata(snapshot_filename, version,
|
||||
expiration_date, compressions)
|
||||
self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches(snapshot_metadata))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_timestamp_metadata,
|
||||
3, version, expiration_date, compressions)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_timestamp_metadata,
|
||||
snapshot_filename, '3', expiration_date, compressions)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_timestamp_metadata,
|
||||
snapshot_filename, version, '3', compressions)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_timestamp_metadata,
|
||||
snapshot_filename, version, expiration_date, 3)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.generate_timestamp_metadata,
|
||||
snapshot_filename, version, expiration_date, ['compress'])
|
||||
|
||||
|
||||
|
||||
|
||||
def test_sign_metadata(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
metadata_path = os.path.join('repository_data',
|
||||
'repository', 'metadata')
|
||||
keystore_path = os.path.join('repository_data',
|
||||
'keystore')
|
||||
root_filename = os.path.join(metadata_path, 'root.json')
|
||||
root_metadata = tuf.util.load_json_file(root_filename)['signed']
|
||||
|
||||
tuf.keydb.create_keydb_from_root_metadata(root_metadata)
|
||||
tuf.roledb.create_roledb_from_root_metadata(root_metadata)
|
||||
root_keyids = tuf.roledb.get_role_keyids('root')
|
||||
|
||||
root_private_keypath = os.path.join(keystore_path, 'root_key')
|
||||
root_private_key = \
|
||||
repo_tool.import_rsa_privatekey_from_file(root_private_keypath,
|
||||
'password')
|
||||
|
||||
# sign_metadata() expects the private key 'root_metadata' to be in
|
||||
# 'tuf.keydb'. Remove any public keys that may be loaded before
|
||||
# adding private key, otherwise a 'tuf.KeyAlreadyExists' exception is
|
||||
# raised.
|
||||
tuf.keydb.remove_key(root_private_key['keyid'])
|
||||
tuf.keydb.add_key(root_private_key)
|
||||
|
||||
root_signable = repo_tool.sign_metadata(root_metadata, root_keyids,
|
||||
root_filename)
|
||||
self.assertTrue(tuf.formats.SIGNABLE_SCHEMA.matches(root_signable))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.sign_metadata, 3, root_keyids,
|
||||
'root.json')
|
||||
self.assertRaises(tuf.FormatError, repo_tool.sign_metadata, root_metadata,
|
||||
3, 'root.json')
|
||||
self.assertRaises(tuf.FormatError, repo_tool.sign_metadata, root_metadata,
|
||||
root_keyids, 3)
|
||||
|
||||
|
||||
|
||||
def test_write_metadata_file(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
metadata_directory = os.path.join('repository_data',
|
||||
'repository', 'metadata')
|
||||
root_filename = os.path.join(metadata_directory, 'root.json')
|
||||
root_signable = tuf.util.load_json_file(root_filename)
|
||||
|
||||
output_filename = os.path.join(temporary_directory, 'root.json')
|
||||
compressions = ['gz']
|
||||
|
||||
self.assertFalse(os.path.exists(output_filename))
|
||||
repo_tool.write_metadata_file(root_signable, output_filename, compressions,
|
||||
consistent_snapshot=False)
|
||||
self.assertTrue(os.path.exists(output_filename))
|
||||
self.assertTrue(os.path.exists(output_filename + '.gz'))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.write_metadata_file,
|
||||
3, output_filename, compressions, False)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.write_metadata_file,
|
||||
root_signable, 3, compressions, False)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.write_metadata_file,
|
||||
root_signable, output_filename, 3, False)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.write_metadata_file,
|
||||
root_signable, output_filename, compressions, 3)
|
||||
|
||||
|
||||
|
||||
def test_create_tuf_client_directory(self):
|
||||
# Test normal case.
|
||||
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
|
||||
repository_directory = os.path.join('repository_data',
|
||||
'repository')
|
||||
client_directory = os.path.join(temporary_directory, 'client')
|
||||
|
||||
repo_tool.create_tuf_client_directory(repository_directory, client_directory)
|
||||
|
||||
self.assertTrue(os.path.exists(client_directory))
|
||||
metadata_directory = os.path.join(client_directory, 'metadata')
|
||||
current_directory = os.path.join(metadata_directory, 'current')
|
||||
previous_directory = os.path.join(metadata_directory, 'previous')
|
||||
self.assertTrue(os.path.exists(client_directory))
|
||||
self.assertTrue(os.path.exists(metadata_directory))
|
||||
self.assertTrue(os.path.exists(current_directory))
|
||||
self.assertTrue(os.path.exists(previous_directory))
|
||||
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, repo_tool.create_tuf_client_directory,
|
||||
3, client_directory)
|
||||
self.assertRaises(tuf.FormatError, repo_tool.create_tuf_client_directory,
|
||||
repository_directory, 3)
|
||||
|
||||
|
||||
# Test invalid argument (i.e., client directory already exists.)
|
||||
self.assertRaises(tuf.RepositoryError, repo_tool.create_tuf_client_directory,
|
||||
repository_directory, client_directory)
|
||||
|
||||
|
||||
# Run the test cases.
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
test_roledb.py
|
||||
|
|
@ -15,6 +17,13 @@
|
|||
Unit test for 'roledb.py'.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import logging
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
test_schema.py
|
||||
|
|
@ -13,10 +15,18 @@
|
|||
|
||||
<Purpose>
|
||||
Unit test for 'schema.py'
|
||||
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import re
|
||||
import logging
|
||||
|
||||
import tuf
|
||||
|
|
@ -109,7 +119,6 @@ def test_AnyString(self):
|
|||
|
||||
self.assertTrue(anystring_schema.matches(''))
|
||||
self.assertTrue(anystring_schema.matches('a string'))
|
||||
self.assertTrue(anystring_schema.matches(u'a unicode string'))
|
||||
|
||||
# Test conditions for invalid arguments.
|
||||
self.assertFalse(anystring_schema.matches(['a']))
|
||||
|
|
@ -203,7 +212,6 @@ def test_Integer(self):
|
|||
integer_schema = tuf.schema.Integer()
|
||||
|
||||
self.assertTrue(integer_schema.matches(99))
|
||||
self.assertTrue(integer_schema.matches(0L))
|
||||
self.assertTrue(tuf.schema.Integer(lo=10, hi=30).matches(25))
|
||||
|
||||
# Test conditions for invalid arguments.
|
||||
|
|
@ -273,6 +281,11 @@ def test_Object(self):
|
|||
self.assertRaises(tuf.FormatError, tuf.schema.Object, a=tuf.schema.AnyString(),
|
||||
b=1)
|
||||
|
||||
# Test condition for invalid non-dict arguments.
|
||||
self.assertFalse(object_schema.matches([{'a':'XYZ'}]))
|
||||
self.assertFalse(object_schema.matches(8))
|
||||
|
||||
|
||||
|
||||
def test_Struct(self):
|
||||
# Test conditions for valid arguments.
|
||||
|
|
@ -318,16 +331,95 @@ def test_Struct(self):
|
|||
|
||||
|
||||
def test_RegularExpression(self):
|
||||
# Test conditions for valid arguments.
|
||||
# Test conditions for valid arguments.
|
||||
# RegularExpression(pattern, modifiers, re_object, re_name).
|
||||
re_schema = tuf.schema.RegularExpression('h.*d')
|
||||
|
||||
self.assertTrue(re_schema.matches('hello world'))
|
||||
|
||||
# Provide a pattern that contains the trailing '$'
|
||||
re_schema_2 = tuf.schema.RegularExpression(pattern='abc$',
|
||||
modifiers=0,
|
||||
re_object=None,
|
||||
re_name='my_re')
|
||||
|
||||
self.assertTrue(re_schema_2.matches('abc'))
|
||||
|
||||
# Test for valid optional arguments.
|
||||
compiled_re = re.compile('^[a-z].*')
|
||||
re_schema_optional = tuf.schema.RegularExpression(pattern='abc',
|
||||
modifiers=0,
|
||||
re_object=compiled_re,
|
||||
re_name='my_re')
|
||||
self.assertTrue(re_schema_optional.matches('abc'))
|
||||
|
||||
# Valid arguments, but the 'pattern' argument is unset (required if the
|
||||
# 're_object' is 'None'.)
|
||||
self.assertRaises(tuf.FormatError, tuf.schema.RegularExpression, None, 0,
|
||||
None, None)
|
||||
|
||||
# Valid arguments, 're_name' is unset, and 'pattern' is None. An exception
|
||||
# is not raised, but 're_name' is set to 'pattern'.
|
||||
re_schema_optional = tuf.schema.RegularExpression(pattern=None,
|
||||
modifiers=0,
|
||||
re_object=compiled_re,
|
||||
re_name=None)
|
||||
|
||||
self.assertTrue(re_schema_optional.matches('abc'))
|
||||
self.assertTrue(re_schema_optional._re_name == 'pattern')
|
||||
|
||||
# Test conditions for invalid arguments.
|
||||
self.assertFalse(re_schema.matches('Hello World'))
|
||||
self.assertFalse(re_schema.matches('hello world!'))
|
||||
self.assertFalse(re_schema.matches([33, 'Hello']))
|
||||
|
||||
self.assertRaises(tuf.FormatError, tuf.schema.RegularExpression, 8)
|
||||
|
||||
|
||||
|
||||
def test_LengthString(self):
|
||||
# Test conditions for valid arguments.
|
||||
length_string = tuf.schema.LengthString(11)
|
||||
|
||||
self.assertTrue(length_string.matches('Hello World'))
|
||||
self.assertTrue(length_string.matches('Hello Marty'))
|
||||
|
||||
# Test conditions for invalid arguments.
|
||||
self.assertRaises(tuf.FormatError, tuf.schema.LengthString, 'hello')
|
||||
|
||||
self.assertFalse(length_string.matches('hello'))
|
||||
self.assertFalse(length_string.matches(8))
|
||||
|
||||
|
||||
|
||||
def test_LengthBytes(self):
|
||||
# Test conditions for valid arguments.
|
||||
length_bytes = tuf.schema.LengthBytes(11)
|
||||
|
||||
self.assertTrue(length_bytes.matches(b'Hello World'))
|
||||
self.assertTrue(length_bytes.matches(b'Hello Marty'))
|
||||
|
||||
# Test conditions for invalid arguments.
|
||||
self.assertRaises(tuf.FormatError, tuf.schema.LengthBytes, 'hello')
|
||||
self.assertRaises(tuf.FormatError, tuf.schema.LengthBytes, True)
|
||||
|
||||
self.assertFalse(length_bytes.matches(b'hello'))
|
||||
self.assertFalse(length_bytes.matches(8))
|
||||
|
||||
|
||||
|
||||
def test_AnyBytes(self):
|
||||
# Test conditions for valid arguments.
|
||||
anybytes_schema = tuf.schema.AnyBytes()
|
||||
|
||||
self.assertTrue(anybytes_schema.matches(b''))
|
||||
self.assertTrue(anybytes_schema.matches(b'a string'))
|
||||
|
||||
# Test conditions for invalid arguments.
|
||||
self.assertFalse(anybytes_schema.matches('a string'))
|
||||
self.assertFalse(anybytes_schema.matches(['a']))
|
||||
self.assertFalse(anybytes_schema.matches(3))
|
||||
self.assertFalse(anybytes_schema.matches({'a': 'string'}))
|
||||
|
||||
|
||||
# Run the unit tests.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
test_sig.py
|
||||
|
|
@ -16,6 +18,14 @@
|
|||
Test cases for for sig.py.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import logging
|
||||
|
||||
|
|
|
|||
|
|
@ -30,24 +30,31 @@
|
|||
Note: There is no difference between 'updates' and 'target' files.
|
||||
"""
|
||||
|
||||
# Help with Python 3 compatability, where the print statement is a function, an
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
import urllib
|
||||
import tempfile
|
||||
import random
|
||||
import time
|
||||
import shutil
|
||||
import json
|
||||
import subprocess
|
||||
import unittest
|
||||
import logging
|
||||
import sys
|
||||
|
||||
# 'unittest2' required for testing under Python < 2.7.
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest
|
||||
|
||||
else:
|
||||
import unittest2 as unittest
|
||||
|
||||
import tuf.formats
|
||||
import tuf.util
|
||||
|
|
@ -55,6 +62,7 @@
|
|||
import tuf.client.updater as updater
|
||||
import tuf.unittest_toolbox as unittest_toolbox
|
||||
import tuf.repository_tool as repo_tool
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logger = logging.getLogger('tuf.test_slow_retrieval_attack')
|
||||
repo_tool.disable_console_log_messages()
|
||||
|
|
@ -102,7 +110,7 @@ def _start_slow_server(self, mode):
|
|||
|
||||
# NOTE: Following error is raised if a delay is not applied:
|
||||
# <urlopen error [Errno 111] Connection refused>
|
||||
time.sleep(.2)
|
||||
time.sleep(.5)
|
||||
|
||||
return server_process
|
||||
|
||||
|
|
@ -157,9 +165,9 @@ def setUp(self):
|
|||
repository = repo_tool.load_repository(self.repository_directory)
|
||||
file1_filepath = os.path.join(self.repository_directory, 'targets',
|
||||
'file1.txt')
|
||||
|
||||
with open(file1_filepath, 'wb') as file_object:
|
||||
file_object.write('a' * total_bytes)
|
||||
data = 'a' * total_bytes
|
||||
file_object.write(data.encode('utf-8'))
|
||||
|
||||
key_file = os.path.join(self.keystore_directory, 'timestamp_key')
|
||||
timestamp_private = repo_tool.import_rsa_privatekey_from_file(key_file,
|
||||
|
|
@ -210,69 +218,6 @@ def tearDown(self):
|
|||
|
||||
|
||||
|
||||
def test_without_tuf_mode_1(self):
|
||||
# Simulate a slow retrieval attack.
|
||||
# 'mode_1': When download begins,the server blocks the download
|
||||
# for a long time by doing nothing before it sends the first byte of data.
|
||||
|
||||
# Retrieve 'file1.txt' provided by the pre-generated repository.
|
||||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'targets', 'file1.txt')
|
||||
client_filepath = os.path.join(self.client_directory, 'file1.txt')
|
||||
|
||||
# Generate the fileinfo of 'file.txt' to compare it to what is downloaded.
|
||||
# The download should complete, albeit slowly (the slow retrieval server
|
||||
# sets a limit on the delay.)
|
||||
filepath = os.path.join(self.repository_directory, 'targets', 'file1.txt')
|
||||
length, hashes = tuf.util.get_file_details(filepath)
|
||||
fileinfo = tuf.formats.make_fileinfo(length, hashes)
|
||||
|
||||
try:
|
||||
server_process = self._start_slow_server('mode_1')
|
||||
urllib.urlretrieve(url_file, client_filepath)
|
||||
|
||||
# Verify the expected file size and hash of the downloaded file.
|
||||
length, hashes = tuf.util.get_file_details(client_filepath)
|
||||
download_fileinfo = tuf.formats.make_fileinfo(length, hashes)
|
||||
self.assertEqual(fileinfo, download_fileinfo)
|
||||
|
||||
finally:
|
||||
# Terminate the slow retrieval (mode 1) server.
|
||||
self._stop_slow_server(server_process)
|
||||
|
||||
|
||||
|
||||
def test_without_tuf_mode_2(self):
|
||||
# Simulate a slow retrieval attack.
|
||||
# 'mode_1': When download begins, the server blocks the download for a long
|
||||
# time by doing nothing before it sends the first byte of data.
|
||||
|
||||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'targets', 'file1.txt')
|
||||
client_filepath = os.path.join(self.client_directory, 'file1.txt')
|
||||
|
||||
# Generate the fileinfo of 'file.txt' to compare it to what is downloaded.
|
||||
# The download should complete, albeit slowly (the slow retrieval server
|
||||
# sets a limit on the delay.)
|
||||
filepath = os.path.join(self.repository_directory, 'targets', 'file1.txt')
|
||||
length, hashes = tuf.util.get_file_details(filepath)
|
||||
fileinfo = tuf.formats.make_fileinfo(length, hashes)
|
||||
|
||||
try:
|
||||
server_process = self._start_slow_server('mode_2')
|
||||
urllib.urlretrieve(url_file, client_filepath)
|
||||
|
||||
# Verify the expected file size and hash of the downloaded file.
|
||||
length, hashes = tuf.util.get_file_details(client_filepath)
|
||||
download_fileinfo = tuf.formats.make_fileinfo(length, hashes)
|
||||
self.assertEqual(fileinfo, download_fileinfo)
|
||||
|
||||
finally:
|
||||
# Terminate the slow retrieval (mode 2) server.
|
||||
self._stop_slow_server(server_process)
|
||||
|
||||
|
||||
|
||||
def test_with_tuf_mode_1(self):
|
||||
# Simulate a slow retrieval attack.
|
||||
# 'mode_1': When download begins,the server blocks the download for a long
|
||||
|
|
@ -285,18 +230,18 @@ def test_with_tuf_mode_1(self):
|
|||
client_filepath = os.path.join(self.client_directory, 'file1.txt')
|
||||
try:
|
||||
file1_target = self.repository_updater.target('file1.txt')
|
||||
self.repository_updater.download_target(file1_target, client_filepath)
|
||||
self.repository_updater.download_target(file1_target, self.client_directory)
|
||||
|
||||
# Verify that the specific 'tuf.SlowRetrievalError' exception is raised by
|
||||
# each mirror.
|
||||
except tuf.NoWorkingMirrorError, exception:
|
||||
for mirror_url, mirror_error in exception.mirror_errors.iteritems():
|
||||
except tuf.NoWorkingMirrorError as exception:
|
||||
for mirror_url, mirror_error in six.iteritems(exception.mirror_errors):
|
||||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'targets', 'file1.txt')
|
||||
|
||||
# Verify that 'file1.txt' is the culprit.
|
||||
self.assertEqual(url_file, mirror_url)
|
||||
self.assertTrue(isinstance(mirror_error, tuf.SlowRetrievalError))
|
||||
self.assertTrue(isinstance(mirror_error, tuf.DownloadLengthMismatchError))
|
||||
|
||||
else:
|
||||
self.fail('TUF did not prevent a slow retrieval attack.')
|
||||
|
|
@ -316,20 +261,20 @@ def test_with_tuf_mode_2(self):
|
|||
client_filepath = os.path.join(self.client_directory, 'file1.txt')
|
||||
try:
|
||||
file1_target = self.repository_updater.target('file1.txt')
|
||||
self.repository_updater.download_target(file1_target, client_filepath)
|
||||
self.repository_updater.download_target(file1_target, self.client_directory)
|
||||
|
||||
# Verify that the specific 'tuf.SlowRetrievalError' exception is raised by
|
||||
# each mirror. 'file1.txt' should be large enough to trigger a slow
|
||||
# retrieval attack, otherwise the expected exception may not be consistently
|
||||
# raised.
|
||||
except tuf.NoWorkingMirrorError, exception:
|
||||
for mirror_url, mirror_error in exception.mirror_errors.iteritems():
|
||||
except tuf.NoWorkingMirrorError as exception:
|
||||
for mirror_url, mirror_error in six.iteritems(exception.mirror_errors):
|
||||
url_prefix = self.repository_mirrors['mirror1']['url_prefix']
|
||||
url_file = os.path.join(url_prefix, 'targets', 'file1.txt')
|
||||
|
||||
# Verify that 'file1.txt' is the culprit.
|
||||
self.assertEqual(url_file, mirror_url)
|
||||
self.assertTrue(isinstance(mirror_error, tuf.SlowRetrievalError))
|
||||
self.assertTrue(isinstance(mirror_error, tuf.DownloadLengthMismatchError))
|
||||
|
||||
else:
|
||||
# Another possibility is to check for a successfully downloaded
|
||||
|
|
|
|||
|
|
@ -38,7 +38,13 @@
|
|||
less dependent than 2.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import time
|
||||
|
|
@ -46,9 +52,16 @@
|
|||
import copy
|
||||
import tempfile
|
||||
import logging
|
||||
import unittest
|
||||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# 'unittest2' required for testing under Python < 2.7.
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest
|
||||
|
||||
else:
|
||||
import unittest2 as unittest
|
||||
|
||||
import tuf
|
||||
import tuf.util
|
||||
|
|
@ -60,6 +73,7 @@
|
|||
import tuf.repository_tool as repo_tool
|
||||
import tuf.unittest_toolbox as unittest_toolbox
|
||||
import tuf.client.updater as updater
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logger = logging.getLogger('tuf.test_updater')
|
||||
repo_tool.disable_console_log_messages()
|
||||
|
|
@ -337,7 +351,7 @@ def test_1__update_fileinfo(self):
|
|||
root_filepath = os.path.join(self.client_metadata_current, 'root.json')
|
||||
length, hashes = tuf.util.get_file_details(root_filepath)
|
||||
root_fileinfo = tuf.formats.make_fileinfo(length, hashes)
|
||||
self.assertTrue('root.json' in fileinfo_dict.keys())
|
||||
self.assertTrue('root.json' in fileinfo_dict)
|
||||
self.assertEqual(fileinfo_dict['root.json'], root_fileinfo)
|
||||
|
||||
# Verify that 'self.fileinfo' is incremented if another role is updated.
|
||||
|
|
@ -554,8 +568,8 @@ def test_3__update_metadata(self):
|
|||
self.repository_updater._update_metadata('targets',
|
||||
targets_compressed_fileinfo)
|
||||
|
||||
except tuf.NoWorkingMirrorError, e:
|
||||
for mirror_error in e.mirror_errors.values():
|
||||
except tuf.NoWorkingMirrorError as e:
|
||||
for mirror_error in six.itervalues(e.mirror_errors):
|
||||
assert isinstance(mirror_error, tuf.BadHashError)
|
||||
|
||||
# Invalid fileinfo for the compressed version of 'targets.json'
|
||||
|
|
@ -572,8 +586,8 @@ def test_3__update_metadata(self):
|
|||
targets_compressed_fileinfo,
|
||||
'gzip', targets_fileinfo)
|
||||
|
||||
except tuf.NoWorkingMirrorError, e:
|
||||
for mirror_error in e.mirror_errors.values():
|
||||
except tuf.NoWorkingMirrorError as e:
|
||||
for mirror_error in six.itervalues(e.mirror_errors):
|
||||
assert isinstance(mirror_error, tuf.DownloadLengthMismatchError)
|
||||
|
||||
|
||||
|
|
@ -648,7 +662,7 @@ def test_3__targets_of_role(self):
|
|||
# target files.
|
||||
self.assertTrue(tuf.formats.TARGETFILES_SCHEMA.matches(targets_list))
|
||||
for target in targets_list:
|
||||
self.assertTrue((target['filepath'], target['fileinfo']) in targets_in_metadata.items())
|
||||
self.assertTrue((target['filepath'], target['fileinfo']) in six.iteritems(targets_in_metadata))
|
||||
|
||||
|
||||
|
||||
|
|
@ -681,7 +695,7 @@ def test_4_refresh(self):
|
|||
# Reference 'self.Repository.metadata['current']['targets']'. Ensure
|
||||
# 'target3' is not already specified.
|
||||
targets_metadata = self.repository_updater.metadata['current']['targets']
|
||||
self.assertFalse(target3 in targets_metadata['targets'].keys())
|
||||
self.assertFalse(target3 in targets_metadata['targets'])
|
||||
|
||||
# Verify the expected version numbers of the roles to be modified.
|
||||
self.assertTrue(self.repository_updater.metadata['current']['targets']\
|
||||
|
|
@ -700,7 +714,7 @@ def test_4_refresh(self):
|
|||
targets_metadata = self.repository_updater.metadata['current']['targets']
|
||||
targets_directory = os.path.join(self.repository_directory, 'targets')
|
||||
target3 = target3[len(targets_directory):]
|
||||
self.assertTrue(target3 in targets_metadata['targets'].keys())
|
||||
self.assertTrue(target3 in targets_metadata['targets'])
|
||||
|
||||
# Verify the expected version numbers of the updated roles.
|
||||
self.assertTrue(self.repository_updater.metadata['current']['targets']\
|
||||
|
|
@ -795,7 +809,7 @@ def test_5_targets_of_role(self):
|
|||
# target files.
|
||||
self.assertTrue(tuf.formats.TARGETFILES_SCHEMA.matches(targets_list))
|
||||
for target in targets_list:
|
||||
self.assertTrue((target['filepath'], target['fileinfo']) in expected_targets.items())
|
||||
self.assertTrue((target['filepath'], target['fileinfo']) in six.iteritems(expected_targets))
|
||||
|
||||
|
||||
# Test: Invalid arguments.
|
||||
|
|
@ -829,7 +843,73 @@ def test_6_target(self):
|
|||
# Test: invalid target path.
|
||||
self.assertRaises(tuf.UnknownTargetError, self.repository_updater.target,
|
||||
self.random_path())
|
||||
|
||||
# Test updater.target() backtracking behavior (enabled by default.)
|
||||
targets_directory = os.path.join(self.repository_directory, 'targets')
|
||||
foo_directory = os.path.join(targets_directory, 'foo')
|
||||
os.makedirs(foo_directory)
|
||||
|
||||
foo_package = os.path.join(foo_directory, 'foo1.1.tar.gz')
|
||||
with open(foo_package, 'wb') as file_object:
|
||||
file_object.write(b'new release')
|
||||
|
||||
# Modify delegations on the remote repository to test backtracking behavior.
|
||||
repository = repo_tool.load_repository(self.repository_directory)
|
||||
|
||||
|
||||
repository.targets.delegate('role2', [self.role_keys['targets']['public']],
|
||||
[], restricted_paths=[foo_directory])
|
||||
|
||||
repository.targets.delegate('role3', [self.role_keys['targets']['public']],
|
||||
[foo_package], restricted_paths=[foo_directory])
|
||||
repository.targets.load_signing_key(self.role_keys['targets']['private'])
|
||||
repository.targets('role2').load_signing_key(self.role_keys['targets']['private'])
|
||||
repository.targets('role3').load_signing_key(self.role_keys['targets']['private'])
|
||||
repository.snapshot.load_signing_key(self.role_keys['snapshot']['private'])
|
||||
repository.timestamp.load_signing_key(self.role_keys['timestamp']['private'])
|
||||
repository.write()
|
||||
|
||||
# Move the staged metadata to the "live" metadata.
|
||||
shutil.rmtree(os.path.join(self.repository_directory, 'metadata'))
|
||||
shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'),
|
||||
os.path.join(self.repository_directory, 'metadata'))
|
||||
|
||||
# updater.target() should find 'foo1.1.tar.gz' by backtracking to
|
||||
# 'targets/role3'. 'targets/role2' allows backtracking.
|
||||
self.repository_updater.refresh()
|
||||
self.repository_updater.target('foo/foo1.1.tar.gz')
|
||||
|
||||
|
||||
# Test when 'targets/role2' does *not* allow backtracking. If
|
||||
# 'foo/foo1.1.tar.gz' is not provided by the authoritative 'target/role2',
|
||||
# updater.target() should return a 'tuf.UnknownTargetError' exception.
|
||||
repository = repo_tool.load_repository(self.repository_directory)
|
||||
|
||||
repository.targets.revoke('role2')
|
||||
repository.targets.revoke('role3')
|
||||
|
||||
# Ensure we delegate in trusted order (i.e., 'role2' has higher priority.)
|
||||
repository.targets.delegate('role2', [self.role_keys['targets']['public']],
|
||||
[], backtrack=False, restricted_paths=[foo_directory])
|
||||
repository.targets.delegate('role3', [self.role_keys['targets']['public']],
|
||||
[foo_package], restricted_paths=[foo_directory])
|
||||
|
||||
repository.targets('role2').load_signing_key(self.role_keys['targets']['private'])
|
||||
repository.targets('role3').load_signing_key(self.role_keys['targets']['private'])
|
||||
repository.targets.load_signing_key(self.role_keys['targets']['private'])
|
||||
repository.snapshot.load_signing_key(self.role_keys['snapshot']['private'])
|
||||
repository.timestamp.load_signing_key(self.role_keys['timestamp']['private'])
|
||||
repository.write()
|
||||
|
||||
# Move the staged metadata to the "live" metadata.
|
||||
shutil.rmtree(os.path.join(self.repository_directory, 'metadata'))
|
||||
shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'),
|
||||
os.path.join(self.repository_directory, 'metadata'))
|
||||
|
||||
# Verify that 'tuf.UnknownTargetError' is raised by updater.target().
|
||||
self.repository_updater.refresh()
|
||||
self.assertRaises(tuf.UnknownTargetError, self.repository_updater.target,
|
||||
'foo/foo1.1.tar.gz')
|
||||
|
||||
|
||||
|
||||
|
|
@ -840,7 +920,7 @@ def test_6_download_target(self):
|
|||
# that will be passed as an argument to 'download_target()'.
|
||||
destination_directory = self.make_temp_directory()
|
||||
target_filepaths = \
|
||||
self.repository_updater.metadata['current']['targets']['targets'].keys()
|
||||
list(self.repository_updater.metadata['current']['targets']['targets'].keys())
|
||||
|
||||
|
||||
# Test: normal case.
|
||||
|
|
@ -872,14 +952,14 @@ def test_6_download_target(self):
|
|||
# field contains at least one confined target and excludes needed target
|
||||
# file.
|
||||
mirrors = self.repository_updater.mirrors
|
||||
for mirror_name, mirror_info in mirrors.items():
|
||||
for mirror_name, mirror_info in six.iteritems(mirrors):
|
||||
mirrors[mirror_name]['confined_target_dirs'] = [self.random_path()]
|
||||
|
||||
try:
|
||||
self.repository_updater.download_target(target_fileinfo,
|
||||
destination_directory)
|
||||
|
||||
except tuf.NoWorkingMirrorError, exception:
|
||||
except tuf.NoWorkingMirrorError as exception:
|
||||
# Ensure that no mirrors were found due to mismatch in confined target
|
||||
# directories. get_list_of_mirrors() returns an empty list in this case,
|
||||
# which does not generate specific exception errors.
|
||||
|
|
@ -1023,6 +1103,25 @@ def test_8_remove_obsolete_targets(self):
|
|||
# in 'destination_directory' remains the same.
|
||||
self.repository_updater.remove_obsolete_targets(destination_directory)
|
||||
self.assertTrue(os.listdir(destination_directory), 1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def test_9__get_target_hash(self):
|
||||
# Test normal case.
|
||||
# Test target filepaths with ascii and non-ascii characters.
|
||||
expected_target_hashes = {
|
||||
'/file1.txt': 'e3a3d89eb3b70ce3fbce6017d7b8c12d4abd5635427a0e8a238f53157df85b3d',
|
||||
'/Jalape\xc3\xb1o': '78bfd5c314680545eb48ecad508aceb861f8d6e680f4fe1b791da45c298cda88'
|
||||
}
|
||||
for filepath, target_hash in six.iteritems(expected_target_hashes):
|
||||
self.assertTrue(tuf.formats.RELPATH_SCHEMA.matches(filepath))
|
||||
self.assertTrue(tuf.formats.HASH_SCHEMA.matches(target_hash))
|
||||
self.assertEqual(self.repository_updater._get_target_hash(filepath), target_hash)
|
||||
|
||||
# Test for improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError, tuf.util.get_target_hash, 8)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
Konstantin Andrianov.
|
||||
|
||||
<Started>
|
||||
February 1, 2013
|
||||
February 1, 2013.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
|
@ -16,7 +16,14 @@
|
|||
<Purpose>
|
||||
Unit test for 'util.py'
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
|
@ -29,8 +36,9 @@
|
|||
import tuf
|
||||
import tuf.log
|
||||
import tuf.hash
|
||||
import tuf.util as util
|
||||
import tuf.util
|
||||
import tuf.unittest_toolbox as unittest_toolbox
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logger = logging.getLogger('tuf.test_util')
|
||||
|
||||
|
|
@ -39,7 +47,7 @@ class TestUtil(unittest_toolbox.Modified_TestCase):
|
|||
|
||||
def setUp(self):
|
||||
unittest_toolbox.Modified_TestCase.setUp(self)
|
||||
self.temp_fileobj = util.TempFile()
|
||||
self.temp_fileobj = tuf.util.TempFile()
|
||||
|
||||
|
||||
|
||||
|
|
@ -57,13 +65,16 @@ def test_A1_tempfile_close_temp_file(self):
|
|||
|
||||
|
||||
def _extract_tempfile_directory(self, config_temp_dir=None):
|
||||
"""[Helper] Takes a directory (essentially specified in the config.py as
|
||||
'temporary_directory') and substitutes tempfile.TemporaryFile() with
|
||||
tempfile.mkstemp() in order to extract actual directory of the stored
|
||||
tempfile. Returns the config's temporary directory (or default temp
|
||||
directory) and actual directory."""
|
||||
"""
|
||||
Takes a directory (essentially specified in the conf.py as
|
||||
'temporary_directory') and substitutes tempfile.TemporaryFile() with
|
||||
tempfile.mkstemp() in order to extract actual directory of the stored
|
||||
tempfile. Returns the config's temporary directory (or default temp
|
||||
directory) and actual directory.
|
||||
"""
|
||||
|
||||
# Patching 'tuf.conf.temporary_directory'.
|
||||
util.tuf.conf.temporary_directory = config_temp_dir
|
||||
tuf.conf.temporary_directory = config_temp_dir
|
||||
|
||||
if config_temp_dir is None:
|
||||
# 'config_temp_dir' needs to be set to default.
|
||||
|
|
@ -72,10 +83,10 @@ def _extract_tempfile_directory(self, config_temp_dir=None):
|
|||
# Patching 'tempfile.TemporaryFile()' (by substituting
|
||||
# temfile.TemporaryFile() with tempfile.mkstemp()) in order to get the
|
||||
# directory of the stored tempfile object.
|
||||
saved_tempfile_TemporaryFile = util.tempfile.NamedTemporaryFile
|
||||
util.tempfile.NamedTemporaryFile = tempfile.mkstemp
|
||||
_temp_fileobj = util.TempFile()
|
||||
util.tempfile.NamedTemporaryFile = saved_tempfile_TemporaryFile
|
||||
saved_tempfile_TemporaryFile = tuf.util.tempfile.NamedTemporaryFile
|
||||
tuf.util.tempfile.NamedTemporaryFile = tempfile.mkstemp
|
||||
_temp_fileobj = tuf.util.TempFile()
|
||||
tuf.util.tempfile.NamedTemporaryFile = saved_tempfile_TemporaryFile
|
||||
junk, _tempfilepath = _temp_fileobj.temporary_file
|
||||
_tempfile_dir = os.path.dirname(_tempfilepath)
|
||||
|
||||
|
|
@ -90,21 +101,30 @@ def _extract_tempfile_directory(self, config_temp_dir=None):
|
|||
|
||||
|
||||
def test_A2_tempfile_init(self):
|
||||
# Goal: Verify that tempfile is stored in an appropriate temp directory.
|
||||
# Goal: Verify that temporary files are stored in the appropriate temp
|
||||
# directory. The location of the temporary files is set in 'tuf.conf.py'.
|
||||
|
||||
# Test: Expected input verification.
|
||||
config_temp_dirs = [None, self.make_temp_directory()]
|
||||
for config_temp_dir in config_temp_dirs:
|
||||
config_temp_dir, actual_dir = \
|
||||
self._extract_tempfile_directory(config_temp_dir)
|
||||
self.assertEquals(config_temp_dir, actual_dir)
|
||||
|
||||
# Assumed 'tuf.conf.temporary_directory' is 'None' initially.
|
||||
temp_file = tuf.util.TempFile()
|
||||
temp_file_directory = os.path.dirname(temp_file.temporary_file.name)
|
||||
self.assertEqual(tempfile.gettempdir(), temp_file_directory)
|
||||
|
||||
saved_temporary_directory = tuf.conf.temporary_directory
|
||||
temp_directory = self.make_temp_directory()
|
||||
tuf.conf.temporary_directory = temp_directory
|
||||
temp_file = tuf.util.TempFile()
|
||||
temp_file_directory = os.path.dirname(temp_file.temporary_file.name)
|
||||
self.assertEqual(temp_directory, temp_file_directory)
|
||||
|
||||
tuf.conf.temporary_directory = saved_temporary_directory
|
||||
|
||||
# Test: Unexpected input handling.
|
||||
config_temp_dirs = [self.random_string(), 123, ['a'], {'a':1}]
|
||||
for config_temp_dir in config_temp_dirs:
|
||||
config_temp_dir, actual_dir = \
|
||||
self._extract_tempfile_directory(config_temp_dir)
|
||||
self.assertEquals(tempfile.gettempdir(), actual_dir)
|
||||
self.assertEqual(tempfile.gettempdir(), actual_dir)
|
||||
|
||||
|
||||
|
||||
|
|
@ -116,8 +136,8 @@ def test_A3_tempfile_read(self):
|
|||
self.temp_fileobj.temporary_file = fileobj
|
||||
|
||||
# Test: Expected input.
|
||||
self.assertEquals(self.temp_fileobj.read(), '1234567890')
|
||||
self.assertEquals(self.temp_fileobj.read(4), '1234')
|
||||
self.assertEqual(self.temp_fileobj.read().decode('utf-8'), '1234567890')
|
||||
self.assertEqual(self.temp_fileobj.read(4).decode('utf-8'), '1234')
|
||||
|
||||
# Test: Unexpected input.
|
||||
for bogus_arg in ['abcd', ['abcd'], {'a':'a'}, -100]:
|
||||
|
|
@ -127,8 +147,11 @@ def test_A3_tempfile_read(self):
|
|||
|
||||
def test_A4_tempfile_write(self):
|
||||
data = self.random_string()
|
||||
self.temp_fileobj.write(data)
|
||||
self.assertEquals(data, self.temp_fileobj.read())
|
||||
self.temp_fileobj.write(data.encode('utf-8'))
|
||||
self.assertEqual(data, self.temp_fileobj.read().decode('utf-8'))
|
||||
|
||||
self.temp_fileobj.write(data.encode('utf-8'), auto_flush=False)
|
||||
self.assertEqual(data, self.temp_fileobj.read().decode('utf-8'))
|
||||
|
||||
|
||||
|
||||
|
|
@ -136,15 +159,18 @@ def test_A5_tempfile_move(self):
|
|||
# Destination directory to save the temporary file in.
|
||||
dest_temp_dir = self.make_temp_directory()
|
||||
dest_path = os.path.join(dest_temp_dir, self.random_string())
|
||||
self.temp_fileobj.write(self.random_string())
|
||||
self.temp_fileobj.write(self.random_string().encode('utf-8'))
|
||||
self.temp_fileobj.move(dest_path)
|
||||
self.assertTrue(dest_path)
|
||||
|
||||
|
||||
|
||||
def _compress_existing_file(self, filepath):
|
||||
"""[Helper]Compresses file 'filepath' and returns file path of
|
||||
the compresses file."""
|
||||
"""
|
||||
[Helper]Compresses file 'filepath' and returns file path of
|
||||
the compresses file.
|
||||
"""
|
||||
|
||||
# NOTE: DO NOT forget to remove the newly created compressed file!
|
||||
if os.path.exists(filepath):
|
||||
compressed_filepath = filepath+'.gz'
|
||||
|
|
@ -153,9 +179,11 @@ def _compress_existing_file(self, filepath):
|
|||
f_out.writelines(f_in)
|
||||
f_out.close()
|
||||
f_in.close()
|
||||
|
||||
return compressed_filepath
|
||||
|
||||
else:
|
||||
print 'Compression of '+repr(filepath)+' failed. Path does not exist.'
|
||||
logger.error('Compression of '+repr(filepath)+' failed. Path does not exist.')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
|
@ -167,9 +195,10 @@ def _decompress_file(self, compressed_filepath):
|
|||
file_content = f.read()
|
||||
f.close()
|
||||
return file_content
|
||||
|
||||
else:
|
||||
print 'Decompression of '+repr(compressed_filepath)+' failed. '+\
|
||||
'Path does not exist.'
|
||||
logger.error('Decompression of '+repr(compressed_filepath)+' failed. '+\
|
||||
'Path does not exist.')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
|
@ -191,19 +220,26 @@ def test_A6_tempfile_decompress_temp_file_object(self):
|
|||
self.assertRaises(tuf.Error,
|
||||
self.temp_fileobj.decompress_temp_file_object, arg)
|
||||
self.temp_fileobj.decompress_temp_file_object('gzip')
|
||||
self.assertEquals(self.temp_fileobj.read(), fileobj.read())
|
||||
self.assertEqual(self.temp_fileobj.read(), fileobj.read())
|
||||
|
||||
# Checking the content of the TempFile's '_orig_file' instance.
|
||||
_orig_data_file = \
|
||||
self.make_temp_data_file(data=self.temp_fileobj._orig_file.read())
|
||||
data_in_orig_file = self._decompress_file(_orig_data_file)
|
||||
check_compressed_original = self.make_temp_file()
|
||||
with open(check_compressed_original, 'wb') as file_object:
|
||||
file_object.write(self.temp_fileobj._orig_file.read())
|
||||
data_in_orig_file = self._decompress_file(check_compressed_original)
|
||||
fileobj.seek(0)
|
||||
self.assertEquals(data_in_orig_file, fileobj.read())
|
||||
|
||||
self.assertEqual(data_in_orig_file, fileobj.read())
|
||||
|
||||
# Try decompressing once more.
|
||||
self.assertRaises(tuf.Error,
|
||||
self.temp_fileobj.decompress_temp_file_object,'gzip')
|
||||
self.temp_fileobj.decompress_temp_file_object, 'gzip')
|
||||
|
||||
# Test decompression of invalid gzip file.
|
||||
temp_file = tuf.util.TempFile()
|
||||
fileobj.seek(0)
|
||||
temp_file.write(fileobj.read())
|
||||
temp_file.decompress_temp_file_object('gzip')
|
||||
|
||||
|
||||
|
||||
def test_B1_get_file_details(self):
|
||||
|
|
@ -218,16 +254,17 @@ def test_B1_get_file_details(self):
|
|||
file_length = os.path.getsize(filepath)
|
||||
|
||||
# Test: Expected input.
|
||||
self.assertEquals(util.get_file_details(filepath), (file_length, file_hash))
|
||||
self.assertEqual(tuf.util.get_file_details(filepath), (file_length, file_hash))
|
||||
|
||||
# Test: Incorrect input.
|
||||
bogus_inputs = [self.random_string(), 1234, [self.random_string()],
|
||||
{'a': 'b'}, None]
|
||||
|
||||
for bogus_input in bogus_inputs:
|
||||
if isinstance(bogus_input, basestring):
|
||||
self.assertRaises(tuf.Error, util.get_file_details, bogus_input)
|
||||
if isinstance(bogus_input, six.string_types):
|
||||
self.assertRaises(tuf.Error, tuf.util.get_file_details, bogus_input)
|
||||
else:
|
||||
self.assertRaises(tuf.FormatError, util.get_file_details, bogus_input)
|
||||
self.assertRaises(tuf.FormatError, tuf.util.get_file_details, bogus_input)
|
||||
|
||||
|
||||
|
||||
|
|
@ -236,11 +273,11 @@ def test_B2_ensure_parent_dir(self):
|
|||
non_existing_parent_dir = os.path.join(existing_parent_dir, 'a', 'b')
|
||||
|
||||
for parent_dir in [existing_parent_dir, non_existing_parent_dir, 12, [3]]:
|
||||
if isinstance(parent_dir, basestring):
|
||||
util.ensure_parent_dir(os.path.join(parent_dir, 'a.txt'))
|
||||
if isinstance(parent_dir, six.string_types):
|
||||
tuf.util.ensure_parent_dir(os.path.join(parent_dir, 'a.txt'))
|
||||
self.assertTrue(os.path.isdir(parent_dir))
|
||||
else:
|
||||
self.assertRaises(tuf.FormatError, util.ensure_parent_dir, parent_dir)
|
||||
self.assertRaises(tuf.FormatError, tuf.util.ensure_parent_dir, parent_dir)
|
||||
|
||||
|
||||
|
||||
|
|
@ -283,26 +320,31 @@ def test_B4_import_json(self):
|
|||
def test_B5_load_json_string(self):
|
||||
# Test normal case.
|
||||
data = ['a', {'b': ['c', None, 30.3, 29]}]
|
||||
json_string = util.json.dumps(data)
|
||||
self.assertEquals(data, util.load_json_string(json_string))
|
||||
json_string = tuf.util.json.dumps(data)
|
||||
self.assertEqual(data, tuf.util.load_json_string(json_string))
|
||||
|
||||
# Test invalid arguments.
|
||||
self.assertRaises(tuf.Error, util.load_json_string, 8)
|
||||
self.assertRaises(tuf.Error, tuf.util.load_json_string, 8)
|
||||
invalid_json_string = {'a': tuf.FormatError}
|
||||
self.assertRaises(tuf.Error, util.load_json_string, invalid_json_string)
|
||||
self.assertRaises(tuf.Error, tuf.util.load_json_string, invalid_json_string)
|
||||
|
||||
|
||||
|
||||
def test_B6_load_json_file(self):
|
||||
data = ['a', {'b': ['c', None, 30.3, 29]}]
|
||||
filepath = self.make_temp_file()
|
||||
fileobj = open(filepath, 'wb')
|
||||
util.json.dump(data, fileobj)
|
||||
fileobj = open(filepath, 'wt')
|
||||
tuf.util.json.dump(data, fileobj)
|
||||
fileobj.close()
|
||||
self.assertEquals(data, util.load_json_file(filepath))
|
||||
self.assertEqual(data, tuf.util.load_json_file(filepath))
|
||||
|
||||
# Test a gzipped file.
|
||||
compressed_filepath = self._compress_existing_file(filepath)
|
||||
self.assertEqual(data, tuf.util.load_json_file(compressed_filepath))
|
||||
|
||||
Errors = (tuf.FormatError, IOError)
|
||||
for bogus_arg in ['a', 1, ['a'], {'a':'b'}]:
|
||||
self.assertRaises(Errors, util.load_json_file, bogus_arg)
|
||||
for bogus_arg in [b'a', 1, [b'a'], {'a':b'b'}]:
|
||||
self.assertRaises(Errors, tuf.util.load_json_file, bogus_arg)
|
||||
|
||||
|
||||
|
||||
|
|
@ -313,13 +355,13 @@ def test_C1_get_target_hash(self):
|
|||
'/README.txt': '8faee106f1bb69f34aaf1df1e3c2e87d763c4d878cb96b91db13495e32ceb0b0',
|
||||
'/warehouse/file2.txt': 'd543a573a2cec67026eff06e75702303559e64e705eba06f65799baaf0424417'
|
||||
}
|
||||
for filepath, target_hash in expected_target_hashes.items():
|
||||
for filepath, target_hash in six.iteritems(expected_target_hashes):
|
||||
self.assertTrue(tuf.formats.RELPATH_SCHEMA.matches(filepath))
|
||||
self.assertTrue(tuf.formats.HASH_SCHEMA.matches(target_hash))
|
||||
self.assertEqual(util.get_target_hash(filepath), target_hash)
|
||||
self.assertEqual(tuf.util.get_target_hash(filepath), target_hash)
|
||||
|
||||
# Test for improperly formatted argument.
|
||||
self.assertRaises(tuf.FormatError, util.get_target_hash, 8)
|
||||
self.assertRaises(tuf.FormatError, tuf.util.get_target_hash, 8)
|
||||
|
||||
|
||||
|
||||
|
|
@ -350,20 +392,20 @@ def test_C2_find_delegated_role(self):
|
|||
]
|
||||
|
||||
self.assertTrue(tuf.formats.ROLELIST_SCHEMA.matches(role_list))
|
||||
self.assertEqual(util.find_delegated_role(role_list, 'targets/tuf'), 1)
|
||||
self.assertEqual(util.find_delegated_role(role_list, 'targets/warehouse'), 0)
|
||||
self.assertEqual(tuf.util.find_delegated_role(role_list, 'targets/tuf'), 1)
|
||||
self.assertEqual(tuf.util.find_delegated_role(role_list, 'targets/warehouse'), 0)
|
||||
# Test for non-existent role. 'find_delegated_role()' returns 'None'
|
||||
# if the role is not found.
|
||||
self.assertEqual(util.find_delegated_role(role_list, 'targets/non-existent'),
|
||||
self.assertEqual(tuf.util.find_delegated_role(role_list, 'targets/non-existent'),
|
||||
None)
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, util.find_delegated_role, 8, role_list)
|
||||
self.assertRaises(tuf.FormatError, util.find_delegated_role, 8, 'targets/tuf')
|
||||
self.assertRaises(tuf.FormatError, tuf.util.find_delegated_role, 8, role_list)
|
||||
self.assertRaises(tuf.FormatError, tuf.util.find_delegated_role, 8, 'targets/tuf')
|
||||
|
||||
# Test duplicate roles.
|
||||
role_list.append(role_list[1])
|
||||
self.assertRaises(tuf.RepositoryError, util.find_delegated_role, role_list,
|
||||
self.assertRaises(tuf.RepositoryError, tuf.util.find_delegated_role, role_list,
|
||||
'targets/tuf')
|
||||
|
||||
# Test missing 'name' attribute (optional, but required by
|
||||
|
|
@ -371,7 +413,7 @@ def test_C2_find_delegated_role(self):
|
|||
# Delete the duplicate role, and the remaining role's 'name' attribute.
|
||||
del role_list[2]
|
||||
del role_list[0]['name']
|
||||
self.assertRaises(tuf.RepositoryError, util.find_delegated_role, role_list,
|
||||
self.assertRaises(tuf.RepositoryError, tuf.util.find_delegated_role, role_list,
|
||||
'targets/warehouse')
|
||||
|
||||
|
||||
|
|
@ -384,38 +426,38 @@ def test_C3_paths_are_consistent_with_hash_prefixes(self):
|
|||
# Ensure the paths of 'list_of_targets' each have the epected path hash
|
||||
# prefix listed in 'path_hash_prefixes'.
|
||||
for filepath in list_of_targets:
|
||||
self.assertTrue(util.get_target_hash(filepath)[0:4] in path_hash_prefixes)
|
||||
self.assertTrue(tuf.util.get_target_hash(filepath)[0:4] in path_hash_prefixes)
|
||||
|
||||
self.assertTrue(util.paths_are_consistent_with_hash_prefixes(list_of_targets,
|
||||
self.assertTrue(tuf.util.paths_are_consistent_with_hash_prefixes(list_of_targets,
|
||||
path_hash_prefixes))
|
||||
|
||||
extra_invalid_prefix = ['e3a3', '8fae', 'd543', '0000']
|
||||
self.assertTrue(util.paths_are_consistent_with_hash_prefixes(list_of_targets,
|
||||
self.assertTrue(tuf.util.paths_are_consistent_with_hash_prefixes(list_of_targets,
|
||||
extra_invalid_prefix))
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError,
|
||||
util.paths_are_consistent_with_hash_prefixes, 8,
|
||||
tuf.util.paths_are_consistent_with_hash_prefixes, 8,
|
||||
path_hash_prefixes)
|
||||
|
||||
self.assertRaises(tuf.FormatError,
|
||||
util.paths_are_consistent_with_hash_prefixes,
|
||||
tuf.util.paths_are_consistent_with_hash_prefixes,
|
||||
list_of_targets, 8)
|
||||
|
||||
self.assertRaises(tuf.FormatError,
|
||||
util.paths_are_consistent_with_hash_prefixes,
|
||||
tuf.util.paths_are_consistent_with_hash_prefixes,
|
||||
list_of_targets, ['zza1'])
|
||||
|
||||
# Test invalid list of targets.
|
||||
bad_target_path = '/file5.txt'
|
||||
self.assertTrue(util.get_target_hash(bad_target_path)[0:4] not in
|
||||
self.assertTrue(tuf.util.get_target_hash(bad_target_path)[0:4] not in
|
||||
path_hash_prefixes)
|
||||
self.assertFalse(util.paths_are_consistent_with_hash_prefixes([bad_target_path],
|
||||
self.assertFalse(tuf.util.paths_are_consistent_with_hash_prefixes([bad_target_path],
|
||||
path_hash_prefixes))
|
||||
|
||||
# Add invalid target path to 'list_of_targets'.
|
||||
list_of_targets.append(bad_target_path)
|
||||
self.assertFalse(util.paths_are_consistent_with_hash_prefixes(list_of_targets,
|
||||
self.assertFalse(tuf.util.paths_are_consistent_with_hash_prefixes(list_of_targets,
|
||||
path_hash_prefixes))
|
||||
|
||||
|
||||
|
|
@ -449,42 +491,42 @@ def test_C4_ensure_all_targets_allowed(self):
|
|||
}
|
||||
self.assertTrue(tuf.formats.DELEGATIONS_SCHEMA.matches(parent_delegations))
|
||||
|
||||
util.ensure_all_targets_allowed(rolename, list_of_targets,
|
||||
tuf.util.ensure_all_targets_allowed(rolename, list_of_targets,
|
||||
parent_delegations)
|
||||
|
||||
# The target files of 'targets' are always allowed. 'list_of_targets' and
|
||||
# 'parent_delegations' are not checked in this case.
|
||||
util.ensure_all_targets_allowed('targets', list_of_targets,
|
||||
tuf.util.ensure_all_targets_allowed('targets', list_of_targets,
|
||||
parent_delegations)
|
||||
|
||||
# Test improperly formatted arguments.
|
||||
self.assertRaises(tuf.FormatError, util.ensure_all_targets_allowed,
|
||||
self.assertRaises(tuf.FormatError, tuf.util.ensure_all_targets_allowed,
|
||||
8, list_of_targets, parent_delegations)
|
||||
|
||||
self.assertRaises(tuf.FormatError, util.ensure_all_targets_allowed,
|
||||
self.assertRaises(tuf.FormatError, tuf.util.ensure_all_targets_allowed,
|
||||
rolename, 8, parent_delegations)
|
||||
|
||||
self.assertRaises(tuf.FormatError, util.ensure_all_targets_allowed,
|
||||
self.assertRaises(tuf.FormatError, tuf.util.ensure_all_targets_allowed,
|
||||
rolename, list_of_targets, 8)
|
||||
|
||||
# Test for invalid 'rolename', which has not been delegated by its parent,
|
||||
# 'targets'.
|
||||
self.assertRaises(tuf.RepositoryError, util.ensure_all_targets_allowed,
|
||||
self.assertRaises(tuf.RepositoryError, tuf.util.ensure_all_targets_allowed,
|
||||
'targets/non-delegated_rolename', list_of_targets,
|
||||
parent_delegations)
|
||||
|
||||
# Test for target file that is not allowed by the parent role.
|
||||
self.assertRaises(tuf.ForbiddenTargetError, util.ensure_all_targets_allowed,
|
||||
self.assertRaises(tuf.ForbiddenTargetError, tuf.util.ensure_all_targets_allowed,
|
||||
'targets/warehouse', ['file8.txt'], parent_delegations)
|
||||
|
||||
self.assertRaises(tuf.ForbiddenTargetError, util.ensure_all_targets_allowed,
|
||||
self.assertRaises(tuf.ForbiddenTargetError, tuf.util.ensure_all_targets_allowed,
|
||||
'targets/warehouse', ['file1.txt', 'bad-README.txt'],
|
||||
parent_delegations)
|
||||
|
||||
# Test for required attributes.
|
||||
# Missing 'paths' attribute.
|
||||
del parent_delegations['roles'][0]['paths']
|
||||
self.assertRaises(tuf.FormatError, util.ensure_all_targets_allowed,
|
||||
self.assertRaises(tuf.FormatError, tuf.util.ensure_all_targets_allowed,
|
||||
'targets/warehouse', list_of_targets, parent_delegations)
|
||||
|
||||
# Test 'path_hash_prefixes' attribute.
|
||||
|
|
@ -492,16 +534,33 @@ def test_C4_ensure_all_targets_allowed(self):
|
|||
parent_delegations['roles'][0]['path_hash_prefixes'] = path_hash_prefixes
|
||||
|
||||
# Test normal case for 'path_hash_prefixes'.
|
||||
util.ensure_all_targets_allowed('targets/warehouse', list_of_targets,
|
||||
tuf.util.ensure_all_targets_allowed('targets/warehouse', list_of_targets,
|
||||
parent_delegations)
|
||||
|
||||
# Test target file with a path_hash_prefix that is not allowed in its
|
||||
# parent role.
|
||||
path_hash_prefix = util.get_target_hash('file5.txt')[0:4]
|
||||
path_hash_prefix = tuf.util.get_target_hash('file5.txt')[0:4]
|
||||
self.assertTrue(path_hash_prefix not in parent_delegations['roles'][0]
|
||||
['path_hash_prefixes'])
|
||||
self.assertRaises(tuf.ForbiddenTargetError, util.ensure_all_targets_allowed,
|
||||
self.assertRaises(tuf.ForbiddenTargetError, tuf.util.ensure_all_targets_allowed,
|
||||
'targets/warehouse', ['file5.txt'], parent_delegations)
|
||||
|
||||
|
||||
|
||||
def test_C5_unittest_toolbox_make_temp_directory(self):
|
||||
# Verify that the tearDown function does not fail when
|
||||
# unittest_toolbox.make_temp_directory deletes the generated temp directory
|
||||
# here.
|
||||
temp_directory = self.make_temp_directory()
|
||||
os.rmdir(temp_directory)
|
||||
|
||||
|
||||
|
||||
def test_c6_get_compressed_length(self):
|
||||
self.temp_fileobj.write(b'hello world')
|
||||
self.assertTrue(self.temp_fileobj.get_compressed_length() == 11)
|
||||
|
||||
temp_file = tuf.util.TempFile()
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
8
tox.ini
8
tox.ini
|
|
@ -4,7 +4,7 @@
|
|||
# and then run "tox" from this directory.
|
||||
|
||||
[tox]
|
||||
envlist = py27
|
||||
envlist = py26, py27, py32, py33, py34
|
||||
|
||||
|
||||
[testenv]
|
||||
|
|
@ -18,3 +18,9 @@ deps =
|
|||
coverage
|
||||
pynacl
|
||||
pycrypto
|
||||
|
||||
|
||||
[testenv:py26]
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
unittest2
|
||||
|
|
|
|||
|
|
@ -20,10 +20,18 @@
|
|||
provide that reason in those cases.
|
||||
"""
|
||||
|
||||
import urlparse
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
import tuf.log
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logging = logging.getLogger('tuf.__init__')
|
||||
|
||||
|
|
@ -68,7 +76,7 @@ def __init__(self, exception):
|
|||
|
||||
def __str__(self):
|
||||
# Show the original exception.
|
||||
return str(self.exception)
|
||||
return repr(self.exception)
|
||||
|
||||
|
||||
|
||||
|
|
@ -90,8 +98,8 @@ def __init__(self, expected_hash, observed_hash):
|
|||
self.observed_hash = observed_hash
|
||||
|
||||
def __str__(self):
|
||||
return 'Observed hash ('+str(self.observed_hash)+\
|
||||
') != expected hash ('+str(self.expected_hash)+')'
|
||||
return 'Observed hash (' + repr(self.observed_hash)+\
|
||||
') != expected hash (' + repr(self.expected_hash)+')'
|
||||
|
||||
|
||||
|
||||
|
|
@ -155,9 +163,9 @@ def __init__(self, metadata_role, previous_version, current_version):
|
|||
|
||||
|
||||
def __str__(self):
|
||||
return 'Downloaded '+str(self.metadata_role)+' is older ('+\
|
||||
str(self.previous_version)+') than the version currently '+\
|
||||
'installed ('+repr(self.current_version)+').'
|
||||
return 'Downloaded ' + repr(self.metadata_role)+' is older ('+\
|
||||
repr(self.previous_version) + ') than the version currently '+\
|
||||
'installed (' + repr(self.current_version) + ').'
|
||||
|
||||
|
||||
|
||||
|
|
@ -178,7 +186,7 @@ def __init__(self, metadata_role_name):
|
|||
self.metadata_role_name = metadata_role_name
|
||||
|
||||
def __str__(self):
|
||||
return str(self.metadata_role_name)+' metadata has bad signature!'
|
||||
return repr(self.metadata_role_name) + ' metadata has bad signature.'
|
||||
|
||||
|
||||
|
||||
|
|
@ -209,7 +217,7 @@ def __init__(self, exception):
|
|||
|
||||
def __str__(self):
|
||||
# Show the original exception.
|
||||
return str(self.exception)
|
||||
return repr(self.exception)
|
||||
|
||||
|
||||
|
||||
|
|
@ -231,8 +239,8 @@ def __init__(self, expected_length, observed_length):
|
|||
self.observed_length = observed_length #bytes
|
||||
|
||||
def __str__(self):
|
||||
return 'Observed length ('+str(self.observed_length)+\
|
||||
') <= expected length ('+str(self.expected_length)+')'
|
||||
return 'Observed length (' + repr(self.observed_length)+\
|
||||
') <= expected length (' + repr(self.expected_length) + ').'
|
||||
|
||||
|
||||
|
||||
|
|
@ -245,8 +253,8 @@ def __init__(self, average_download_speed):
|
|||
self.__average_download_speed = average_download_speed #bytes/second
|
||||
|
||||
def __str__(self):
|
||||
return "Download was too slow. Average speed: "+\
|
||||
str(self.__average_download_speed)+" bytes/second"
|
||||
return 'Download was too slow. Average speed: ' +\
|
||||
repr(self.__average_download_speed) + ' bytes per second.'
|
||||
|
||||
|
||||
|
||||
|
|
@ -294,17 +302,26 @@ class InvalidNameError(Error):
|
|||
|
||||
class UnsignedMetadataError(Error):
|
||||
"""Indicate metadata object with insufficient threshold of signatures."""
|
||||
|
||||
def __init__(self, message, signable):
|
||||
self.exception_message = message
|
||||
self.signable = signable
|
||||
|
||||
def __str__(self):
|
||||
return self.exception_message
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class NoWorkingMirrorError(Error):
|
||||
"""An updater will throw this exception in case it could not download a
|
||||
metadata or target file.
|
||||
"""
|
||||
An updater will throw this exception in case it could not download a
|
||||
metadata or target file.
|
||||
|
||||
A dictionary of Exception instances indexed by every mirror URL will also be
|
||||
provided."""
|
||||
A dictionary of Exception instances indexed by every mirror URL will also be
|
||||
provided.
|
||||
"""
|
||||
|
||||
def __init__(self, mirror_errors):
|
||||
# Dictionary of URL strings to Exception instances
|
||||
|
|
@ -316,15 +333,15 @@ def __str__(self):
|
|||
for mirror_url, mirror_error in self.mirror_errors.iteritems():
|
||||
try:
|
||||
# http://docs.python.org/2/library/urlparse.html#urlparse.urlparse
|
||||
mirror_url_tokens = urlparse.urlparse(mirror_url)
|
||||
mirror_url_tokens = six.moves.urllib.parse.urlparse(mirror_url)
|
||||
|
||||
except:
|
||||
logging.exception('Failed to parse mirror URL: '+str(mirror_url))
|
||||
logging.exception('Failed to parse mirror URL: ' + repr(mirror_url))
|
||||
mirror_netloc = mirror_url
|
||||
|
||||
else:
|
||||
mirror_netloc = mirror_url_tokens.netloc
|
||||
|
||||
all_errors += '\n '+str(mirror_netloc)+': '+str(mirror_error)
|
||||
all_errors += '\n ' + repr(mirror_netloc) + ': ' + repr(mirror_error)
|
||||
|
||||
return all_errors
|
||||
|
|
|
|||
646
tuf/_vendor/six.py
Normal file
646
tuf/_vendor/six.py
Normal file
|
|
@ -0,0 +1,646 @@
|
|||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
# Copyright (c) 2010-2014 Benjamin Peterson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import operator
|
||||
import sys
|
||||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.6.1"
|
||||
|
||||
|
||||
# Useful for very coarse version differentiation.
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
string_types = str,
|
||||
integer_types = int,
|
||||
class_types = type,
|
||||
text_type = str
|
||||
binary_type = bytes
|
||||
|
||||
MAXSIZE = sys.maxsize
|
||||
else:
|
||||
string_types = basestring,
|
||||
integer_types = (int, long)
|
||||
class_types = (type, types.ClassType)
|
||||
text_type = unicode
|
||||
binary_type = str
|
||||
|
||||
if sys.platform.startswith("java"):
|
||||
# Jython always uses 32 bits.
|
||||
MAXSIZE = int((1 << 31) - 1)
|
||||
else:
|
||||
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
|
||||
class X(object):
|
||||
def __len__(self):
|
||||
return 1 << 31
|
||||
try:
|
||||
len(X())
|
||||
except OverflowError:
|
||||
# 32-bit
|
||||
MAXSIZE = int((1 << 31) - 1)
|
||||
else:
|
||||
# 64-bit
|
||||
MAXSIZE = int((1 << 63) - 1)
|
||||
del X
|
||||
|
||||
|
||||
def _add_doc(func, doc):
|
||||
"""Add documentation to a function."""
|
||||
func.__doc__ = doc
|
||||
|
||||
|
||||
def _import_module(name):
|
||||
"""Import module, returning the module after the last dot."""
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
|
||||
|
||||
class _LazyDescr(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, obj, tp):
|
||||
try:
|
||||
result = self._resolve()
|
||||
except ImportError:
|
||||
# See the nice big comment in MovedModule.__getattr__.
|
||||
raise AttributeError("%s could not be imported " % self.name)
|
||||
setattr(obj, self.name, result) # Invokes __set__.
|
||||
# This is a bit ugly, but it avoids running this again.
|
||||
delattr(obj.__class__, self.name)
|
||||
return result
|
||||
|
||||
|
||||
class MovedModule(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old, new=None):
|
||||
super(MovedModule, self).__init__(name)
|
||||
if PY3:
|
||||
if new is None:
|
||||
new = name
|
||||
self.mod = new
|
||||
else:
|
||||
self.mod = old
|
||||
|
||||
def _resolve(self):
|
||||
return _import_module(self.mod)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# It turns out many Python frameworks like to traverse sys.modules and
|
||||
# try to load various attributes. This causes problems if this is a
|
||||
# platform-specific module on the wrong platform, like _winreg on
|
||||
# Unixes. Therefore, we silently pretend unimportable modules do not
|
||||
# have any attributes. See issues #51, #53, #56, and #63 for the full
|
||||
# tales of woe.
|
||||
#
|
||||
# First, if possible, avoid loading the module just to look at __file__,
|
||||
# __name__, or __path__.
|
||||
if (attr in ("__file__", "__name__", "__path__") and
|
||||
self.mod not in sys.modules):
|
||||
raise AttributeError(attr)
|
||||
try:
|
||||
_module = self._resolve()
|
||||
except ImportError:
|
||||
raise AttributeError(attr)
|
||||
value = getattr(_module, attr)
|
||||
setattr(self, attr, value)
|
||||
return value
|
||||
|
||||
|
||||
class _LazyModule(types.ModuleType):
|
||||
|
||||
def __init__(self, name):
|
||||
super(_LazyModule, self).__init__(name)
|
||||
self.__doc__ = self.__class__.__doc__
|
||||
|
||||
def __dir__(self):
|
||||
attrs = ["__doc__", "__name__"]
|
||||
attrs += [attr.name for attr in self._moved_attributes]
|
||||
return attrs
|
||||
|
||||
# Subclasses should override this
|
||||
_moved_attributes = []
|
||||
|
||||
|
||||
class MovedAttribute(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
|
||||
super(MovedAttribute, self).__init__(name)
|
||||
if PY3:
|
||||
if new_mod is None:
|
||||
new_mod = name
|
||||
self.mod = new_mod
|
||||
if new_attr is None:
|
||||
if old_attr is None:
|
||||
new_attr = name
|
||||
else:
|
||||
new_attr = old_attr
|
||||
self.attr = new_attr
|
||||
else:
|
||||
self.mod = old_mod
|
||||
if old_attr is None:
|
||||
old_attr = name
|
||||
self.attr = old_attr
|
||||
|
||||
def _resolve(self):
|
||||
module = _import_module(self.mod)
|
||||
return getattr(module, self.attr)
|
||||
|
||||
|
||||
|
||||
class _MovedItems(_LazyModule):
|
||||
"""Lazy loading of moved objects"""
|
||||
|
||||
|
||||
_moved_attributes = [
|
||||
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
|
||||
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
|
||||
MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
|
||||
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
|
||||
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
|
||||
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
|
||||
MovedAttribute("reduce", "__builtin__", "functools"),
|
||||
MovedAttribute("StringIO", "StringIO", "io"),
|
||||
MovedAttribute("UserString", "UserString", "collections"),
|
||||
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
|
||||
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
|
||||
|
||||
MovedModule("builtins", "__builtin__"),
|
||||
MovedModule("configparser", "ConfigParser"),
|
||||
MovedModule("copyreg", "copy_reg"),
|
||||
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
|
||||
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
|
||||
MovedModule("http_cookies", "Cookie", "http.cookies"),
|
||||
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
|
||||
MovedModule("html_parser", "HTMLParser", "html.parser"),
|
||||
MovedModule("http_client", "httplib", "http.client"),
|
||||
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
|
||||
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
|
||||
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
|
||||
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
|
||||
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
|
||||
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
|
||||
MovedModule("cPickle", "cPickle", "pickle"),
|
||||
MovedModule("queue", "Queue"),
|
||||
MovedModule("reprlib", "repr"),
|
||||
MovedModule("socketserver", "SocketServer"),
|
||||
MovedModule("_thread", "thread", "_thread"),
|
||||
MovedModule("tkinter", "Tkinter"),
|
||||
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
|
||||
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
|
||||
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
|
||||
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
|
||||
MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
|
||||
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
|
||||
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
|
||||
MovedModule("tkinter_colorchooser", "tkColorChooser",
|
||||
"tkinter.colorchooser"),
|
||||
MovedModule("tkinter_commondialog", "tkCommonDialog",
|
||||
"tkinter.commondialog"),
|
||||
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
|
||||
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
|
||||
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
|
||||
"tkinter.simpledialog"),
|
||||
MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
|
||||
MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
|
||||
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
|
||||
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
|
||||
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
|
||||
MovedModule("xmlrpc_server", "xmlrpclib", "xmlrpc.server"),
|
||||
MovedModule("winreg", "_winreg"),
|
||||
]
|
||||
for attr in _moved_attributes:
|
||||
setattr(_MovedItems, attr.name, attr)
|
||||
if isinstance(attr, MovedModule):
|
||||
sys.modules[__name__ + ".moves." + attr.name] = attr
|
||||
del attr
|
||||
|
||||
_MovedItems._moved_attributes = _moved_attributes
|
||||
|
||||
moves = sys.modules[__name__ + ".moves"] = _MovedItems(__name__ + ".moves")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_parse(_LazyModule):
|
||||
"""Lazy loading of moved objects in six.moves.urllib_parse"""
|
||||
|
||||
|
||||
_urllib_parse_moved_attributes = [
|
||||
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("quote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("urlencode", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splitquery", "urllib", "urllib.parse"),
|
||||
]
|
||||
for attr in _urllib_parse_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_parse, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
|
||||
|
||||
sys.modules[__name__ + ".moves.urllib_parse"] = sys.modules[__name__ + ".moves.urllib.parse"] = Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_error(_LazyModule):
|
||||
"""Lazy loading of moved objects in six.moves.urllib_error"""
|
||||
|
||||
|
||||
_urllib_error_moved_attributes = [
|
||||
MovedAttribute("URLError", "urllib2", "urllib.error"),
|
||||
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
|
||||
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
|
||||
]
|
||||
for attr in _urllib_error_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_error, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
|
||||
|
||||
sys.modules[__name__ + ".moves.urllib_error"] = sys.modules[__name__ + ".moves.urllib.error"] = Module_six_moves_urllib_error(__name__ + ".moves.urllib.error")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_request(_LazyModule):
|
||||
"""Lazy loading of moved objects in six.moves.urllib_request"""
|
||||
|
||||
|
||||
_urllib_request_moved_attributes = [
|
||||
MovedAttribute("urlopen", "urllib2", "urllib.request"),
|
||||
MovedAttribute("install_opener", "urllib2", "urllib.request"),
|
||||
MovedAttribute("build_opener", "urllib2", "urllib.request"),
|
||||
MovedAttribute("pathname2url", "urllib", "urllib.request"),
|
||||
MovedAttribute("url2pathname", "urllib", "urllib.request"),
|
||||
MovedAttribute("getproxies", "urllib", "urllib.request"),
|
||||
MovedAttribute("Request", "urllib2", "urllib.request"),
|
||||
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
|
||||
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
|
||||
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
|
||||
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
|
||||
MovedAttribute("URLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
|
||||
]
|
||||
for attr in _urllib_request_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_request, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
|
||||
|
||||
sys.modules[__name__ + ".moves.urllib_request"] = sys.modules[__name__ + ".moves.urllib.request"] = Module_six_moves_urllib_request(__name__ + ".moves.urllib.request")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_response(_LazyModule):
|
||||
"""Lazy loading of moved objects in six.moves.urllib_response"""
|
||||
|
||||
|
||||
_urllib_response_moved_attributes = [
|
||||
MovedAttribute("addbase", "urllib", "urllib.response"),
|
||||
MovedAttribute("addclosehook", "urllib", "urllib.response"),
|
||||
MovedAttribute("addinfo", "urllib", "urllib.response"),
|
||||
MovedAttribute("addinfourl", "urllib", "urllib.response"),
|
||||
]
|
||||
for attr in _urllib_response_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_response, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
|
||||
|
||||
sys.modules[__name__ + ".moves.urllib_response"] = sys.modules[__name__ + ".moves.urllib.response"] = Module_six_moves_urllib_response(__name__ + ".moves.urllib.response")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_robotparser(_LazyModule):
|
||||
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
|
||||
|
||||
|
||||
_urllib_robotparser_moved_attributes = [
|
||||
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
|
||||
]
|
||||
for attr in _urllib_robotparser_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
|
||||
|
||||
sys.modules[__name__ + ".moves.urllib_robotparser"] = sys.modules[__name__ + ".moves.urllib.robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser")
|
||||
|
||||
|
||||
class Module_six_moves_urllib(types.ModuleType):
|
||||
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
|
||||
parse = sys.modules[__name__ + ".moves.urllib_parse"]
|
||||
error = sys.modules[__name__ + ".moves.urllib_error"]
|
||||
request = sys.modules[__name__ + ".moves.urllib_request"]
|
||||
response = sys.modules[__name__ + ".moves.urllib_response"]
|
||||
robotparser = sys.modules[__name__ + ".moves.urllib_robotparser"]
|
||||
|
||||
def __dir__(self):
|
||||
return ['parse', 'error', 'request', 'response', 'robotparser']
|
||||
|
||||
|
||||
sys.modules[__name__ + ".moves.urllib"] = Module_six_moves_urllib(__name__ + ".moves.urllib")
|
||||
|
||||
|
||||
def add_move(move):
|
||||
"""Add an item to six.moves."""
|
||||
setattr(_MovedItems, move.name, move)
|
||||
|
||||
|
||||
def remove_move(name):
|
||||
"""Remove item from six.moves."""
|
||||
try:
|
||||
delattr(_MovedItems, name)
|
||||
except AttributeError:
|
||||
try:
|
||||
del moves.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError("no such move, %r" % (name,))
|
||||
|
||||
|
||||
if PY3:
|
||||
_meth_func = "__func__"
|
||||
_meth_self = "__self__"
|
||||
|
||||
_func_closure = "__closure__"
|
||||
_func_code = "__code__"
|
||||
_func_defaults = "__defaults__"
|
||||
_func_globals = "__globals__"
|
||||
|
||||
_iterkeys = "keys"
|
||||
_itervalues = "values"
|
||||
_iteritems = "items"
|
||||
_iterlists = "lists"
|
||||
else:
|
||||
_meth_func = "im_func"
|
||||
_meth_self = "im_self"
|
||||
|
||||
_func_closure = "func_closure"
|
||||
_func_code = "func_code"
|
||||
_func_defaults = "func_defaults"
|
||||
_func_globals = "func_globals"
|
||||
|
||||
_iterkeys = "iterkeys"
|
||||
_itervalues = "itervalues"
|
||||
_iteritems = "iteritems"
|
||||
_iterlists = "iterlists"
|
||||
|
||||
|
||||
try:
|
||||
advance_iterator = next
|
||||
except NameError:
|
||||
def advance_iterator(it):
|
||||
return it.next()
|
||||
next = advance_iterator
|
||||
|
||||
|
||||
try:
|
||||
callable = callable
|
||||
except NameError:
|
||||
def callable(obj):
|
||||
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
|
||||
|
||||
|
||||
if PY3:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound
|
||||
|
||||
create_bound_method = types.MethodType
|
||||
|
||||
Iterator = object
|
||||
else:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound.im_func
|
||||
|
||||
def create_bound_method(func, obj):
|
||||
return types.MethodType(func, obj, obj.__class__)
|
||||
|
||||
class Iterator(object):
|
||||
|
||||
def next(self):
|
||||
return type(self).__next__(self)
|
||||
|
||||
callable = callable
|
||||
_add_doc(get_unbound_function,
|
||||
"""Get the function out of a possibly unbound function""")
|
||||
|
||||
|
||||
get_method_function = operator.attrgetter(_meth_func)
|
||||
get_method_self = operator.attrgetter(_meth_self)
|
||||
get_function_closure = operator.attrgetter(_func_closure)
|
||||
get_function_code = operator.attrgetter(_func_code)
|
||||
get_function_defaults = operator.attrgetter(_func_defaults)
|
||||
get_function_globals = operator.attrgetter(_func_globals)
|
||||
|
||||
|
||||
def iterkeys(d, **kw):
|
||||
"""Return an iterator over the keys of a dictionary."""
|
||||
return iter(getattr(d, _iterkeys)(**kw))
|
||||
|
||||
def itervalues(d, **kw):
|
||||
"""Return an iterator over the values of a dictionary."""
|
||||
return iter(getattr(d, _itervalues)(**kw))
|
||||
|
||||
def iteritems(d, **kw):
|
||||
"""Return an iterator over the (key, value) pairs of a dictionary."""
|
||||
return iter(getattr(d, _iteritems)(**kw))
|
||||
|
||||
def iterlists(d, **kw):
|
||||
"""Return an iterator over the (key, [values]) pairs of a dictionary."""
|
||||
return iter(getattr(d, _iterlists)(**kw))
|
||||
|
||||
|
||||
if PY3:
|
||||
def b(s):
|
||||
return s.encode("latin-1")
|
||||
def u(s):
|
||||
return s
|
||||
unichr = chr
|
||||
if sys.version_info[1] <= 1:
|
||||
def int2byte(i):
|
||||
return bytes((i,))
|
||||
else:
|
||||
# This is about 2x faster than the implementation above on 3.2+
|
||||
int2byte = operator.methodcaller("to_bytes", 1, "big")
|
||||
byte2int = operator.itemgetter(0)
|
||||
indexbytes = operator.getitem
|
||||
iterbytes = iter
|
||||
import io
|
||||
StringIO = io.StringIO
|
||||
BytesIO = io.BytesIO
|
||||
else:
|
||||
def b(s):
|
||||
return s
|
||||
# Workaround for standalone backslash
|
||||
def u(s):
|
||||
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
|
||||
unichr = unichr
|
||||
int2byte = chr
|
||||
def byte2int(bs):
|
||||
return ord(bs[0])
|
||||
def indexbytes(buf, i):
|
||||
return ord(buf[i])
|
||||
def iterbytes(buf):
|
||||
return (ord(byte) for byte in buf)
|
||||
import StringIO
|
||||
StringIO = BytesIO = StringIO.StringIO
|
||||
_add_doc(b, """Byte literal""")
|
||||
_add_doc(u, """Text literal""")
|
||||
|
||||
|
||||
if PY3:
|
||||
exec_ = getattr(moves.builtins, "exec")
|
||||
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
else:
|
||||
def exec_(_code_, _globs_=None, _locs_=None):
|
||||
"""Execute code in a namespace."""
|
||||
if _globs_ is None:
|
||||
frame = sys._getframe(1)
|
||||
_globs_ = frame.f_globals
|
||||
if _locs_ is None:
|
||||
_locs_ = frame.f_locals
|
||||
del frame
|
||||
elif _locs_ is None:
|
||||
_locs_ = _globs_
|
||||
exec("""exec _code_ in _globs_, _locs_""")
|
||||
|
||||
|
||||
exec_("""def reraise(tp, value, tb=None):
|
||||
raise tp, value, tb
|
||||
""")
|
||||
|
||||
|
||||
print_ = getattr(moves.builtins, "print", None)
|
||||
if print_ is None:
|
||||
def print_(*args, **kwargs):
|
||||
"""The new-style print function for Python 2.4 and 2.5."""
|
||||
fp = kwargs.pop("file", sys.stdout)
|
||||
if fp is None:
|
||||
return
|
||||
def write(data):
|
||||
if not isinstance(data, basestring):
|
||||
data = str(data)
|
||||
# If the file has an encoding, encode unicode with it.
|
||||
if (isinstance(fp, file) and
|
||||
isinstance(data, unicode) and
|
||||
fp.encoding is not None):
|
||||
errors = getattr(fp, "errors", None)
|
||||
if errors is None:
|
||||
errors = "strict"
|
||||
data = data.encode(fp.encoding, errors)
|
||||
fp.write(data)
|
||||
want_unicode = False
|
||||
sep = kwargs.pop("sep", None)
|
||||
if sep is not None:
|
||||
if isinstance(sep, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(sep, str):
|
||||
raise TypeError("sep must be None or a string")
|
||||
end = kwargs.pop("end", None)
|
||||
if end is not None:
|
||||
if isinstance(end, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(end, str):
|
||||
raise TypeError("end must be None or a string")
|
||||
if kwargs:
|
||||
raise TypeError("invalid keyword arguments to print()")
|
||||
if not want_unicode:
|
||||
for arg in args:
|
||||
if isinstance(arg, unicode):
|
||||
want_unicode = True
|
||||
break
|
||||
if want_unicode:
|
||||
newline = unicode("\n")
|
||||
space = unicode(" ")
|
||||
else:
|
||||
newline = "\n"
|
||||
space = " "
|
||||
if sep is None:
|
||||
sep = space
|
||||
if end is None:
|
||||
end = newline
|
||||
for i, arg in enumerate(args):
|
||||
if i:
|
||||
write(sep)
|
||||
write(arg)
|
||||
write(end)
|
||||
|
||||
_add_doc(reraise, """Reraise an exception.""")
|
||||
|
||||
|
||||
def with_metaclass(meta, *bases):
|
||||
"""Create a base class with a metaclass."""
|
||||
return meta("NewBase", bases, {})
|
||||
|
||||
def add_metaclass(metaclass):
|
||||
"""Class decorator for creating a class with a metaclass."""
|
||||
def wrapper(cls):
|
||||
orig_vars = cls.__dict__.copy()
|
||||
orig_vars.pop('__dict__', None)
|
||||
orig_vars.pop('__weakref__', None)
|
||||
slots = orig_vars.get('__slots__')
|
||||
if slots is not None:
|
||||
if isinstance(slots, str):
|
||||
slots = [slots]
|
||||
for slots_var in slots:
|
||||
orig_vars.pop(slots_var)
|
||||
return metaclass(cls.__name__, cls.__bases__, orig_vars)
|
||||
return wrapper
|
||||
|
|
@ -51,6 +51,14 @@
|
|||
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import optparse
|
||||
import logging
|
||||
|
|
@ -94,7 +102,7 @@ def update_client(repository_mirror):
|
|||
# Does 'repository_mirror' have the correct format?
|
||||
try:
|
||||
tuf.formats.URL_SCHEMA.check_match(repository_mirror)
|
||||
except tuf.FormatError, e:
|
||||
except tuf.FormatError as e:
|
||||
message = 'The repository mirror supplied is invalid.'
|
||||
raise tuf.RepositoryError(message)
|
||||
|
||||
|
|
@ -126,7 +134,7 @@ def update_client(repository_mirror):
|
|||
for target in updated_targets:
|
||||
try:
|
||||
updater.download_target(target, destination_directory)
|
||||
except tuf.DownloadError, e:
|
||||
except tuf.DownloadError as e:
|
||||
pass
|
||||
|
||||
# Remove any files from the destination directory that are no longer being
|
||||
|
|
@ -211,7 +219,8 @@ def parse_options():
|
|||
# the current directory.
|
||||
try:
|
||||
update_client(repository_mirror)
|
||||
except (tuf.NoWorkingMirrorError, tuf.RepositoryError), e:
|
||||
|
||||
except (tuf.NoWorkingMirrorError, tuf.RepositoryError) as e:
|
||||
sys.stderr.write('Error: '+str(e)+'\n')
|
||||
sys.exit(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -99,12 +99,19 @@
|
|||
updater.download_target(target, destination_directory)
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
import urllib
|
||||
import random
|
||||
|
||||
import tuf
|
||||
|
|
@ -120,6 +127,7 @@
|
|||
import tuf.sig
|
||||
import tuf.util
|
||||
import tuf._vendor.iso8601 as iso8601
|
||||
import tuf._vendor.six as six
|
||||
|
||||
logger = logging.getLogger('tuf.client.updater')
|
||||
|
||||
|
|
@ -505,7 +513,7 @@ 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():
|
||||
for keyid, keyinfo in six.iteritems(keys_info):
|
||||
if keyinfo['keytype'] in ['rsa', 'ed25519']:
|
||||
key = tuf.keys.format_metadata_to_key(keyinfo)
|
||||
|
||||
|
|
@ -517,13 +525,13 @@ def _import_delegations(self, parent_role):
|
|||
except tuf.KeyAlreadyExistsError:
|
||||
pass
|
||||
|
||||
except (tuf.FormatError, tuf.Error), e:
|
||||
except (tuf.FormatError, tuf.Error) as e:
|
||||
logger.exception('Failed to add keyid: '+repr(keyid)+'.')
|
||||
logger.error('Aborting role delegation for parent role '+parent_role+'.')
|
||||
raise
|
||||
|
||||
else:
|
||||
logger.warn('Invalid key type for '+repr(keyid)+'.')
|
||||
logger.warning('Invalid key type for '+repr(keyid)+'.')
|
||||
continue
|
||||
|
||||
# Add the roles to the role database.
|
||||
|
|
@ -535,8 +543,8 @@ def _import_delegations(self, parent_role):
|
|||
logger.debug('Adding delegated role: '+str(rolename)+'.')
|
||||
tuf.roledb.add_role(rolename, roleinfo)
|
||||
|
||||
except tuf.RoleAlreadyExistsError, e:
|
||||
logger.warn('Role already exists: '+rolename)
|
||||
except tuf.RoleAlreadyExistsError as e:
|
||||
logger.warning('Role already exists: '+rolename)
|
||||
|
||||
except:
|
||||
logger.exception('Failed to add delegated role: '+rolename+'.')
|
||||
|
|
@ -642,7 +650,7 @@ def refresh(self, unsafely_update_root_if_necessary=True):
|
|||
self._update_metadata_if_changed('root')
|
||||
self._update_metadata_if_changed('targets')
|
||||
|
||||
except tuf.NoWorkingMirrorError, e:
|
||||
except tuf.NoWorkingMirrorError as e:
|
||||
if unsafely_update_root_if_necessary:
|
||||
message = 'Valid top-level metadata cannot be downloaded. Unsafely '+\
|
||||
'update the Root metadata.'
|
||||
|
|
@ -689,7 +697,7 @@ def _check_hashes(self, file_object, trusted_hashes):
|
|||
|
||||
# Verify each trusted hash of 'trusted_hashes'. If all are valid, simply
|
||||
# return.
|
||||
for algorithm, trusted_hash in trusted_hashes.items():
|
||||
for algorithm, trusted_hash in six.iteritems(trusted_hashes):
|
||||
digest_object = tuf.hash.digest(algorithm)
|
||||
digest_object.update(file_object.read())
|
||||
computed_hash = digest_object.hexdigest()
|
||||
|
|
@ -842,7 +850,7 @@ def verify_target_file(target_file_object):
|
|||
# 'compression' argument to _get_file() is needed only for decompression of
|
||||
# metadata. Target files may be compressed or uncompressed.
|
||||
if self.consistent_snapshot:
|
||||
target_digest = random.choice(file_hashes.values())
|
||||
target_digest = random.choice(list(file_hashes.values()))
|
||||
dirname, basename = os.path.split(target_filepath)
|
||||
target_filepath = os.path.join(dirname, target_digest+'.'+basename)
|
||||
|
||||
|
|
@ -897,12 +905,12 @@ def _verify_uncompressed_metadata_file(self, metadata_file_object,
|
|||
None.
|
||||
"""
|
||||
|
||||
metadata = metadata_file_object.read()
|
||||
metadata = metadata_file_object.read().decode('utf-8')
|
||||
|
||||
try:
|
||||
metadata_signable = tuf.util.load_json_string(metadata)
|
||||
|
||||
except Exception, exception:
|
||||
except Exception as exception:
|
||||
raise tuf.InvalidMetadataJSONError(exception)
|
||||
|
||||
else:
|
||||
|
|
@ -931,7 +939,7 @@ def _verify_uncompressed_metadata_file(self, metadata_file_object,
|
|||
# are not allowed.
|
||||
if metadata_signable['signed']['_type'] == 'Targets':
|
||||
if metadata_role != 'targets':
|
||||
metadata_targets = metadata_signable['signed']['targets'].keys()
|
||||
metadata_targets = list(metadata_signable['signed']['targets'].keys())
|
||||
parent_rolename = tuf.roledb.get_parent_rolename(metadata_role)
|
||||
parent_role_metadata = self.metadata['current'][parent_rolename]
|
||||
parent_delegations = parent_role_metadata['delegations']
|
||||
|
|
@ -1198,7 +1206,7 @@ def _get_file(self, filepath, verify_file_function, file_type,
|
|||
# uncompressed version).
|
||||
verify_file_function(file_object)
|
||||
|
||||
except Exception, exception:
|
||||
except Exception as exception:
|
||||
# Remember the error from this mirror, and "reset" the target file.
|
||||
logger.exception('Update failed from '+file_mirror+'.')
|
||||
file_mirror_errors[file_mirror] = exception
|
||||
|
|
@ -1319,11 +1327,11 @@ def _update_metadata(self, metadata_role, uncompressed_fileinfo,
|
|||
if self.consistent_snapshot:
|
||||
if compression:
|
||||
filename_digest = \
|
||||
random.choice(compressed_fileinfo['hashes'].values())
|
||||
random.choice(list(compressed_fileinfo['hashes'].values()))
|
||||
|
||||
else:
|
||||
filename_digest = \
|
||||
random.choice(uncompressed_fileinfo['hashes'].values())
|
||||
random.choice(list(uncompressed_fileinfo['hashes'].values()))
|
||||
dirname, basename = os.path.split(remote_filename)
|
||||
remote_filename = os.path.join(dirname, filename_digest+'.'+basename)
|
||||
|
||||
|
|
@ -1352,7 +1360,7 @@ def _update_metadata(self, metadata_role, uncompressed_fileinfo,
|
|||
# Next, move the verified updated metadata file to the 'current' directory.
|
||||
# Note that the 'move' method comes from tuf.util's TempFile class.
|
||||
# 'metadata_file_object' is an instance of tuf.util.TempFile.
|
||||
metadata_signable = tuf.util.load_json_string(metadata_file_object.read())
|
||||
metadata_signable = tuf.util.load_json_string(metadata_file_object.read().decode('utf-8'))
|
||||
if compression == 'gzip':
|
||||
current_uncompressed_filepath = \
|
||||
os.path.join(self.metadata_directory['current'],
|
||||
|
|
@ -1605,7 +1613,7 @@ def _fileinfo_has_changed(self, metadata_filename, new_fileinfo):
|
|||
# without having that result in considering all files as needing to be
|
||||
# updated, or not all hash algorithms listed can be calculated on the
|
||||
# specific client.
|
||||
for algorithm, hash_value in new_fileinfo['hashes'].items():
|
||||
for algorithm, hash_value in six.iteritems(new_fileinfo['hashes']):
|
||||
# We're only looking for a single match. This isn't a security
|
||||
# check, we just want to prevent unnecessary downloads.
|
||||
if algorithm in current_fileinfo['hashes']:
|
||||
|
|
@ -1892,7 +1900,7 @@ def _refresh_targets_metadata(self, rolename='targets', include_delegations=Fals
|
|||
# See if this role provides metadata and, if we're including delegations,
|
||||
# look for metadata from delegated roles.
|
||||
role_prefix = rolename + '/'
|
||||
for metadata_path in self.metadata['current']['snapshot']['meta'].keys():
|
||||
for metadata_path in six.iterkeys(self.metadata['current']['snapshot']['meta']):
|
||||
if metadata_path == rolename + '.json':
|
||||
roles_to_update.append(metadata_path[:-len('.json')])
|
||||
elif include_delegations and metadata_path.startswith(role_prefix):
|
||||
|
|
@ -2000,8 +2008,8 @@ def refresh_targets_metadata_chain(self, rolename):
|
|||
# This only goes to -1 because we only want to store the parents (so we
|
||||
# ignore the last element).
|
||||
for next_role in parts[1:-1]:
|
||||
parent_roles.append(roles_added+'/'+next_role)
|
||||
roles_added = roles_added+'/'+next_role
|
||||
parent_roles.append(roles_added + '/' + next_role)
|
||||
roles_added = roles_added + '/' + next_role
|
||||
|
||||
message = 'Minimum metadata to download and set the chain of trust: '+\
|
||||
repr(parent_roles)+'.'
|
||||
|
|
@ -2010,13 +2018,13 @@ def refresh_targets_metadata_chain(self, rolename):
|
|||
# Check if 'snapshot.json' provides metadata for each of the roles in
|
||||
# 'parent_roles'. All the available roles on the repository are specified
|
||||
# in the 'snapshot.json' metadata.
|
||||
targets_metadata_allowed = self.metadata['current']['snapshot']['meta'].keys()
|
||||
targets_metadata_allowed = list(self.metadata['current']['snapshot']['meta'].keys())
|
||||
for parent_role in parent_roles:
|
||||
parent_role = parent_role + '.json'
|
||||
|
||||
if parent_role not in targets_metadata_allowed:
|
||||
message = '"snapshot.json" does not provide all the parent roles '+\
|
||||
'of '+repr(rolename)+'.'
|
||||
'of ' + repr(rolename) + '.'
|
||||
raise tuf.RepositoryError(message)
|
||||
|
||||
# Remove the 'targets' role because it gets updated when the targets.json
|
||||
|
|
@ -2034,7 +2042,7 @@ def refresh_targets_metadata_chain(self, rolename):
|
|||
|
||||
# Sort the roles so that parent roles always come first.
|
||||
parent_roles.sort()
|
||||
logger.debug('Roles to update: '+repr(parent_roles)+'.')
|
||||
logger.debug('Roles to update: ' + repr(parent_roles) + '.')
|
||||
|
||||
# Iterate 'parent_roles', load each role's metadata file from disk, and
|
||||
# update it if it has changed.
|
||||
|
|
@ -2108,7 +2116,7 @@ def _targets_of_role(self, rolename, targets=None, skip_refresh=False):
|
|||
return targets
|
||||
|
||||
# Get the targets specified by the role itself.
|
||||
for filepath, fileinfo in self.metadata['current'][rolename]['targets'].items():
|
||||
for filepath, fileinfo in six.iteritems(self.metadata['current'][rolename]['targets']):
|
||||
new_target = {}
|
||||
new_target['filepath'] = filepath
|
||||
new_target['fileinfo'] = fileinfo
|
||||
|
|
@ -2209,7 +2217,7 @@ def target(self, target_filepath):
|
|||
|
||||
# 'target_filepath' might contain URL encoding escapes.
|
||||
# http://docs.python.org/2/library/urllib.html#urllib.unquote
|
||||
target_filepath = urllib.unquote(target_filepath)
|
||||
target_filepath = six.moves.urllib.parse.unquote(target_filepath)
|
||||
|
||||
if not target_filepath.startswith('/'):
|
||||
target_filepath = '/' + target_filepath
|
||||
|
|
@ -2292,15 +2300,28 @@ def _preorder_depth_first_walk(self, target_filepath):
|
|||
|
||||
if target is None:
|
||||
|
||||
# Push children in reverse order of appearance onto the stack.
|
||||
child_roles_to_visit = []
|
||||
# NOTE: This may be a slow operation if there are many delegated roles.
|
||||
for child_role in reversed(child_roles):
|
||||
for child_role in child_roles:
|
||||
child_role_name = self._visit_child_role(child_role, target_filepath)
|
||||
if child_role_name is None:
|
||||
if not child_role['backtrack'] and child_role_name is not None:
|
||||
logger.debug('Adding child role '+repr(child_role_name))
|
||||
logger.debug('Not backtracking to other roles.')
|
||||
role_names = []
|
||||
child_roles_to_visit.append(child_role_name)
|
||||
break
|
||||
|
||||
elif child_role_name is None:
|
||||
logger.debug('Skipping child role '+repr(child_role_name))
|
||||
|
||||
else:
|
||||
logger.debug('Adding child role '+repr(child_role_name))
|
||||
role_names.append(child_role_name)
|
||||
child_roles_to_visit.append(child_role_name)
|
||||
|
||||
# Push 'child_roles_to_visit' in reverse order of appearance onto
|
||||
# 'role_names'. Roles are popped from the end of the 'role_names' list.
|
||||
child_roles_to_visit.reverse()
|
||||
role_names.extend(child_roles_to_visit)
|
||||
|
||||
else:
|
||||
logger.debug('Found target in current role '+repr(role_name))
|
||||
|
|
@ -2342,13 +2363,15 @@ def _get_target_from_targets_role(self, role_name, targets, target_filepath):
|
|||
target = None
|
||||
|
||||
# Does the current role name have our target?
|
||||
logger.debug('Asking role '+repr(role_name)+' about target '+\
|
||||
logger.debug('Asking role ' + repr(role_name) + ' about target '+\
|
||||
repr(target_filepath))
|
||||
for filepath, fileinfo in targets.iteritems():
|
||||
|
||||
for filepath, fileinfo in six.iteritems(targets):
|
||||
if filepath == target_filepath:
|
||||
logger.debug('Found target '+target_filepath+' in role '+role_name)
|
||||
logger.debug('Found target ' + target_filepath + ' in role ' + role_name)
|
||||
target = {'filepath': filepath, 'fileinfo': fileinfo}
|
||||
break
|
||||
|
||||
else:
|
||||
logger.debug('No target '+target_filepath+' in role '+role_name)
|
||||
|
||||
|
|
@ -2425,16 +2448,17 @@ def _visit_child_role(self, child_role, target_filepath):
|
|||
# 'role_name' should have been validated when it was downloaded.
|
||||
# The 'paths' or 'path_hash_prefixes' fields should not be missing,
|
||||
# so we raise a format error here in case they are both missing.
|
||||
raise tuf.FormatError(repr(child_role_name)+' has neither ' \
|
||||
'"paths" nor "path_hash_prefixes"!')
|
||||
raise tuf.FormatError(repr(child_role_name) + ' has neither ' \
|
||||
'"paths" nor "path_hash_prefixes".')
|
||||
|
||||
if child_role_is_relevant:
|
||||
logger.debug('Child role '+repr(child_role_name)+' has target '+
|
||||
logger.debug('Child role ' + repr(child_role_name) + ' has target ' + \
|
||||
repr(target_filepath))
|
||||
return child_role_name
|
||||
|
||||
else:
|
||||
logger.debug('Child role '+repr(child_role_name)+
|
||||
' does not have target '+repr(target_filepath))
|
||||
logger.debug('Child role ' + repr(child_role_name) + \
|
||||
' does not have target ' + repr(target_filepath))
|
||||
return None
|
||||
|
||||
|
||||
|
|
@ -2472,20 +2496,11 @@ def _get_target_hash(self, target_filepath, hash_function='sha256'):
|
|||
"""
|
||||
|
||||
# Calculate the hash of the filepath to determine which bin to find the
|
||||
# target. The client currently assumes the repository uses
|
||||
# 'hash_function' to generate hashes.
|
||||
|
||||
# target. The client currently assumes the repository (i.e., repository
|
||||
# tool) uses 'hash_function' to generate hashes and UTF-8.
|
||||
digest_object = tuf.hash.digest(hash_function)
|
||||
|
||||
try:
|
||||
digest_object.update(target_filepath)
|
||||
except UnicodeEncodeError:
|
||||
# Sometimes, there are Unicode characters in target paths. We assume a
|
||||
# UTF-8 encoding and try to hash that.
|
||||
digest_object = tuf.hash.digest(hash_function)
|
||||
encoded_target_filepath = target_filepath.encode('utf-8')
|
||||
digest_object.update(encoded_target_filepath)
|
||||
|
||||
encoded_target_filepath = target_filepath.encode('utf-8')
|
||||
digest_object.update(encoded_target_filepath)
|
||||
target_filepath_hash = digest_object.hexdigest()
|
||||
|
||||
return target_filepath_hash
|
||||
|
|
@ -2528,24 +2543,24 @@ def remove_obsolete_targets(self, destination_directory):
|
|||
for role in tuf.roledb.get_rolenames():
|
||||
if role.startswith('targets'):
|
||||
if role in self.metadata['previous'] and self.metadata['previous'][role] != None:
|
||||
for target in self.metadata['previous'][role]['targets'].keys():
|
||||
if target not in self.metadata['current'][role]['targets'].keys():
|
||||
for target in self.metadata['previous'][role]['targets']:
|
||||
if target not in self.metadata['current'][role]['targets']:
|
||||
# 'target' is only in 'previous', so remove it.
|
||||
logger.warn('Removing obsolete file: '+repr(target)+'.')
|
||||
logger.warning('Removing obsolete file: ' + repr(target) + '.')
|
||||
# Remove the file if it hasn't been removed already.
|
||||
destination = os.path.join(destination_directory, target)
|
||||
try:
|
||||
os.remove(destination)
|
||||
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
# If 'filename' already removed, just log it.
|
||||
if e.errno == errno.ENOENT:
|
||||
logger.info('File '+repr(destination)+' was already removed.')
|
||||
logger.info('File ' + repr(destination) + ' was already removed.')
|
||||
|
||||
else:
|
||||
logger.error(str(e))
|
||||
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
|
||||
|
||||
|
|
@ -2607,7 +2622,7 @@ def updated_targets(self, targets, destination_directory):
|
|||
|
||||
# Try one of the algorithm/digest combos for a mismatch. We break
|
||||
# as soon as we find a mismatch.
|
||||
for algorithm, digest in target['fileinfo']['hashes'].items():
|
||||
for algorithm, digest in six.iteritems(target['fileinfo']['hashes']):
|
||||
digest_object = None
|
||||
try:
|
||||
digest_object = tuf.hash.digest_filename(target_filepath,
|
||||
|
|
@ -2691,7 +2706,7 @@ def download_target(self, target, destination_directory):
|
|||
try:
|
||||
os.makedirs(target_dirpath)
|
||||
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
if e.errno == errno.EEXIST:
|
||||
pass
|
||||
|
||||
|
|
@ -2699,6 +2714,6 @@ def download_target(self, target, destination_directory):
|
|||
raise
|
||||
|
||||
else:
|
||||
logger.warn(str(target_dirpath)+' does not exist.')
|
||||
logger.warning(repr(target_dirpath) + ' does not exist.')
|
||||
|
||||
target_file_object.move(destination)
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
"""
|
||||
We copy some backwards compatibility from pip.
|
||||
|
||||
https://github.com/pypa/pip/tree/d0fa66ecc03ab20b7411b35f7c7b423f31f77761/pip/backwardcompat
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
import http.client as httplib
|
||||
import urllib.parse as urlparse
|
||||
import urllib.request as urllib2
|
||||
else:
|
||||
import httplib
|
||||
import urllib2
|
||||
import urlparse
|
||||
|
||||
|
||||
## py25 has no builtin ssl module
|
||||
## only >=py32 has ssl.match_hostname and ssl.CertificateError
|
||||
try:
|
||||
import ssl
|
||||
try:
|
||||
from ssl import match_hostname, CertificateError
|
||||
except ImportError:
|
||||
from tuf.compatibility.ssl_match_hostname import match_hostname, CertificateError
|
||||
except ImportError:
|
||||
ssl = None
|
||||
|
||||
|
||||
# patch for py25 socket to work with http://pypi.python.org/pypi/ssl/
|
||||
import socket
|
||||
if not hasattr(socket, 'create_connection'): # for Python 2.5
|
||||
# monkey-patch socket module
|
||||
from tuf.compatibility.socket_create_connection import create_connection
|
||||
socket.create_connection = create_connection
|
||||
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
"""
|
||||
We copy some functions from the Python 2.7.3 socket module.
|
||||
|
||||
http://hg.python.org/releasing/2.7.3/file/7bb96963d067/Lib/socket.py
|
||||
"""
|
||||
|
||||
|
||||
_GLOBAL_DEFAULT_TIMEOUT = object()
|
||||
|
||||
|
||||
def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
|
||||
source_address=None):
|
||||
"""Connect to *address* and return the socket object.
|
||||
|
||||
Convenience function. Connect to *address* (a 2-tuple ``(host,
|
||||
port)``) and return the socket object. Passing the optional
|
||||
*timeout* parameter will set the timeout on the socket instance
|
||||
before attempting to connect. If no *timeout* is supplied, the
|
||||
global default timeout setting returned by :func:`getdefaulttimeout`
|
||||
is used. If *source_address* is set it must be a tuple of (host, port)
|
||||
for the socket to bind as a source address before making the connection.
|
||||
An host of '' or port 0 tells the OS to use the default.
|
||||
"""
|
||||
|
||||
host, port = address
|
||||
err = None
|
||||
for res in getaddrinfo(host, port, 0, SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
sock = None
|
||||
try:
|
||||
sock = socket(af, socktype, proto)
|
||||
if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
|
||||
sock.settimeout(timeout)
|
||||
if source_address:
|
||||
sock.bind(source_address)
|
||||
sock.connect(sa)
|
||||
return sock
|
||||
|
||||
except error as _:
|
||||
err = _
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
|
||||
if err is not None:
|
||||
raise err
|
||||
else:
|
||||
raise error("getaddrinfo returns an empty list")
|
||||
|
||||
12
tuf/conf.py
12
tuf/conf.py
|
|
@ -18,6 +18,14 @@
|
|||
and cryptography libraries clients wish to use.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# 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
|
||||
|
|
@ -49,7 +57,7 @@
|
|||
DEFAULT_ROOT_REQUIRED_LENGTH = 512000 #bytes
|
||||
|
||||
# Set a timeout value in seconds (float) for non-blocking socket operations.
|
||||
SOCKET_TIMEOUT = 1 #seconds
|
||||
SOCKET_TIMEOUT = 2 #seconds
|
||||
|
||||
# The maximum chunk of data, in bytes, we would download in every round.
|
||||
CHUNK_SIZE = 8192 #bytes
|
||||
|
|
@ -59,7 +67,7 @@
|
|||
MIN_AVERAGE_DOWNLOAD_SPEED = CHUNK_SIZE #bytes/second
|
||||
|
||||
# The time (in seconds) we ignore a server with a slow initial retrieval speed.
|
||||
SLOW_START_GRACE_PERIOD = 30 #seconds
|
||||
SLOW_START_GRACE_PERIOD = 3 #seconds
|
||||
|
||||
# The current "good enough" number of PBKDF2 passphrase iterations.
|
||||
# We recommend that important keys, such as root, be kept offline.
|
||||
|
|
|
|||
798
tuf/download.py
798
tuf/download.py
|
|
@ -13,21 +13,24 @@
|
|||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
Perform any file downloads and check their validity. This means that the
|
||||
hash and length of a downloaded file has to match the hash and length
|
||||
supplied by the metadata of that file. The downloaded file is technically a
|
||||
file-like object that will automatically destroys itself once closed. Note
|
||||
that the file-like object, 'tuf.util.TempFile', is returned by the
|
||||
'_download_file()' function.
|
||||
Download metadata and target files and check their validity. The hash and
|
||||
length of a downloaded file has to match the hash and length supplied by the
|
||||
metadata of that file. The downloaded file is technically a file-like object
|
||||
that will automatically destroys itself once closed. Note that the file-like
|
||||
object, 'tuf.util.TempFile', is returned by the '_download_file()' function.
|
||||
"""
|
||||
|
||||
# Induce "true division" (http://www.python.org/dev/peps/pep-0238/).
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import httplib
|
||||
import logging
|
||||
import os.path
|
||||
import os
|
||||
import socket
|
||||
import logging
|
||||
import timeit
|
||||
|
||||
import tuf
|
||||
|
|
@ -35,324 +38,304 @@
|
|||
import tuf.hash
|
||||
import tuf.util
|
||||
import tuf.formats
|
||||
|
||||
from tuf.compatibility import httplib, ssl, urllib2, urlparse
|
||||
|
||||
if ssl:
|
||||
from tuf.compatibility import match_hostname
|
||||
else:
|
||||
raise tuf.Error("No SSL support!") # TODO: degrade gracefully
|
||||
|
||||
# We will be overriding socket._fileobject to perform non-blocking socket
|
||||
# reads. Therefore, we will need these global variables.
|
||||
# http://hg.python.org/cpython/file/5be3fa83d436/Lib/socket.py#l84
|
||||
import tuf._vendor.six as six
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
from ssl import match_hostname, CertificateError
|
||||
|
||||
try:
|
||||
import errno
|
||||
except ImportError:
|
||||
errno = None
|
||||
EINTR = getattr(errno, 'EINTR', 4)
|
||||
from tuf._vendor.ssl_match_hostname import match_hostname, CertificateError
|
||||
|
||||
# See 'log.py' to learn how logging is handled in TUF.
|
||||
logger = logging.getLogger('tuf.download')
|
||||
|
||||
|
||||
|
||||
def safe_download(url, required_length):
|
||||
"""
|
||||
<Purpose>
|
||||
Given the 'url' and 'required_length' of the desired file, open a connection
|
||||
to 'url', download it, and return the contents of the file. Also ensure
|
||||
the length of the downloaded file matches 'required_length' exactly.
|
||||
tuf.download.unsafe_download() may be called if an upper download limit is
|
||||
preferred.
|
||||
|
||||
'tuf.util.TempFile', the file-like object returned, is used instead of
|
||||
regular tempfile object because of additional functionality provided, such
|
||||
as handling compressed metadata and automatically closing files after
|
||||
moving to final destination.
|
||||
|
||||
<Arguments>
|
||||
url:
|
||||
A URL string that represents the location of the file.
|
||||
|
||||
required_length:
|
||||
An integer value representing the length of the file. This is an exact
|
||||
limit.
|
||||
|
||||
<Side Effects>
|
||||
A 'tuf.util.TempFile' object is created on disk to store the contents of
|
||||
'url'.
|
||||
|
||||
<Exceptions>
|
||||
tuf.DownloadLengthMismatchError, if there was a mismatch of observed vs
|
||||
expected lengths while downloading the file.
|
||||
|
||||
tuf.FormatError, if any of the arguments are improperly formatted.
|
||||
|
||||
class SaferSocketFileObject(socket._fileobject):
|
||||
"""We override socket._fileobject to produce a file-like object which reads
|
||||
from a socket more safely than its ancestor. One the safety properties is
|
||||
that reading from a socket must be a non-blocking operation."""
|
||||
|
||||
def __init__(self, sock, mode='rb', bufsize=-1, close=False):
|
||||
super(SaferSocketFileObject, self).__init__(sock, mode=mode,
|
||||
bufsize=bufsize, close=close)
|
||||
|
||||
# Count the number of bytes received with this socket.
|
||||
self.__number_of_bytes_received = 0
|
||||
# Count the seconds spent receiving with this socket. Tolerate servers with
|
||||
# a slow start by ignoring their delivery speed for
|
||||
# tuf.conf.SLOW_START_GRACE_PERIOD seconds.
|
||||
assert tuf.conf.SLOW_START_GRACE_PERIOD > 0
|
||||
self.__seconds_spent_receiving = -tuf.conf.SLOW_START_GRACE_PERIOD
|
||||
# Remember the time a clock was started.
|
||||
self.__start_time = None
|
||||
Any other unforeseen runtime exception.
|
||||
|
||||
<Returns>
|
||||
A 'tuf.util.TempFile' file-like object that points to the contents of 'url'.
|
||||
"""
|
||||
|
||||
return _download_file(url, required_length, STRICT_REQUIRED_LENGTH=True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def __start_clock(self):
|
||||
"""
|
||||
<Purpose>
|
||||
Start the clock to measure time difference later.
|
||||
def unsafe_download(url, required_length):
|
||||
"""
|
||||
<Purpose>
|
||||
Given the 'url' and 'required_length' of the desired file, open a connection
|
||||
to 'url', download it, and return the contents of the file. Also ensure
|
||||
the length of the downloaded file is up to 'required_length', and no larger.
|
||||
tuf.download.safe_download() may be called if an exact download limit is
|
||||
preferred.
|
||||
|
||||
'tuf.util.TempFile', the file-like object returned, is used instead of
|
||||
regular tempfile object because of additional functionality provided, such
|
||||
as handling compressed metadata and automatically closing files after
|
||||
moving to final destination.
|
||||
|
||||
<Arguments>
|
||||
url:
|
||||
A URL string that represents the location of the file.
|
||||
|
||||
required_length:
|
||||
An integer value representing the length of the file. This is an upper
|
||||
limit.
|
||||
|
||||
<Arguments>
|
||||
None.
|
||||
<Side Effects>
|
||||
A 'tuf.util.TempFile' object is created on disk to store the contents of
|
||||
'url'.
|
||||
|
||||
<Exceptions>
|
||||
tuf.DownloadLengthMismatchError, if there was a mismatch of observed vs
|
||||
expected lengths while downloading the file.
|
||||
|
||||
tuf.FormatError, if any of the arguments are improperly formatted.
|
||||
|
||||
<Exceptions>
|
||||
AssertionError:
|
||||
When any internal condition is not true.
|
||||
|
||||
<Side Effects>
|
||||
Start time is kept inside this object.
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
"""
|
||||
|
||||
# We must have reset the clock before this.
|
||||
assert self.__start_time is None
|
||||
# We use (platform-specific) wall time, so it will be imprecise sometimes.
|
||||
self.__start_time = timeit.default_timer()
|
||||
Any other unforeseen runtime exception.
|
||||
|
||||
<Returns>
|
||||
A 'tuf.util.TempFile' file-like object that points to the contents of 'url'.
|
||||
"""
|
||||
|
||||
return _download_file(url, required_length, STRICT_REQUIRED_LENGTH=False)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def __stop_clock_and_check_speed(self, data_length):
|
||||
"""
|
||||
<Purpose>
|
||||
Stop the clock and try to detect slow retrieval.
|
||||
def _download_file(url, required_length, STRICT_REQUIRED_LENGTH=True):
|
||||
"""
|
||||
<Purpose>
|
||||
Given the url, hashes and length of the desired file, this function
|
||||
opens a connection to 'url' and downloads the file while ensuring its
|
||||
length and hashes match 'required_hashes' and 'required_length'.
|
||||
|
||||
tuf.util.TempFile is used instead of regular tempfile object because of
|
||||
additional functionality provided by 'tuf.util.TempFile'.
|
||||
|
||||
<Arguments>
|
||||
url:
|
||||
A URL string that represents the location of the file.
|
||||
|
||||
required_length:
|
||||
An integer value representing the length of the file.
|
||||
|
||||
<Arguments>
|
||||
data_length:
|
||||
A non-negative integer indicating the size of data retrieved in bytes.
|
||||
STRICT_REQUIRED_LENGTH:
|
||||
A Boolean indicator used to signal whether we should perform strict
|
||||
checking of required_length. True by default. We explicitly set this to
|
||||
False when we know that we want to turn this off for downloading the
|
||||
timestamp metadata, which has no signed required_length.
|
||||
|
||||
<Exceptions>
|
||||
tuf.SlowRetrievalError:
|
||||
If the average download speed falls below
|
||||
'tuf.conf.MIN_AVERAGE_DOWNLOAD_SPEED'.
|
||||
<Side Effects>
|
||||
A 'tuf.util.TempFile' object is created on disk to store the contents of
|
||||
'url'.
|
||||
|
||||
<Exceptions>
|
||||
tuf.DownloadLengthMismatchError, if there was a mismatch of observed vs
|
||||
expected lengths while downloading the file.
|
||||
|
||||
tuf.FormatError, if any of the arguments are improperly formatted.
|
||||
|
||||
AssertionError:
|
||||
When any internal condition is not true.
|
||||
Any other unforeseen runtime exception.
|
||||
|
||||
<Returns>
|
||||
A 'tuf.util.TempFile' file-like object that points to the contents of 'url'.
|
||||
"""
|
||||
|
||||
<Side Effects>
|
||||
Start time is cleared inside this object.
|
||||
# Do all of the arguments have the appropriate format?
|
||||
# Raise 'tuf.FormatError' if there is a mismatch.
|
||||
tuf.formats.URL_SCHEMA.check_match(url)
|
||||
tuf.formats.LENGTH_SCHEMA.check_match(required_length)
|
||||
|
||||
<Returns>
|
||||
None.
|
||||
"""
|
||||
# 'url.replace()' is for compatibility with Windows-based systems because
|
||||
# they might put back-slashes in place of forward-slashes. This converts it
|
||||
# to the common format.
|
||||
url = url.replace('\\', '/')
|
||||
logger.info('Downloading: '+str(url))
|
||||
|
||||
# We use (platform-specific) wall time, so it will be imprecise sometimes.
|
||||
stop_time = timeit.default_timer()
|
||||
# We must have already started the clock.
|
||||
assert self.__start_time > 0
|
||||
time_delta = stop_time-self.__start_time
|
||||
# Reset the clock.
|
||||
self.__start_time = None
|
||||
# This is the temporary file that we will return to contain the contents of
|
||||
# the downloaded file.
|
||||
temp_file = tuf.util.TempFile()
|
||||
|
||||
# Measure the average download speed.
|
||||
self.__number_of_bytes_received += data_length
|
||||
self.__seconds_spent_receiving += time_delta
|
||||
try:
|
||||
# Open the connection to the remote file.
|
||||
connection = _open_connection(url)
|
||||
|
||||
# self.__seconds_spent_receiving begins at negative
|
||||
# 'tuf.conf.SLOW_START_GRACE_PERIOD'.
|
||||
if self.__seconds_spent_receiving > 0:
|
||||
average_download_speed = \
|
||||
self.__number_of_bytes_received/self.__seconds_spent_receiving
|
||||
# We ask the server about how big it thinks this file should be.
|
||||
reported_length = _get_content_length(connection)
|
||||
|
||||
# If the average download speed is below a certain threshold, we flag this
|
||||
# as a possible slow-retrieval attack. This threshold will determine our
|
||||
# bias: if it is too low, we will have more false positives; if it is too
|
||||
# high, we will have more false negatives.
|
||||
# Then, we check whether the required length matches the reported length.
|
||||
_check_content_length(reported_length, required_length,
|
||||
STRICT_REQUIRED_LENGTH)
|
||||
|
||||
# Download the contents of the URL, up to the required length, to a
|
||||
# temporary file, and get the total number of downloaded bytes.
|
||||
total_downloaded = _download_fixed_amount_of_data(connection, temp_file,
|
||||
required_length)
|
||||
|
||||
# Does the total number of downloaded bytes match the required length?
|
||||
_check_downloaded_length(total_downloaded, required_length,
|
||||
STRICT_REQUIRED_LENGTH=STRICT_REQUIRED_LENGTH)
|
||||
|
||||
except:
|
||||
# Close 'temp_file'; any written data is lost.
|
||||
temp_file.close_temp_file()
|
||||
logger.exception('Could not download URL: '+str(url))
|
||||
raise
|
||||
|
||||
else:
|
||||
return temp_file
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _download_fixed_amount_of_data(connection, temp_file, required_length):
|
||||
"""
|
||||
<Purpose>
|
||||
This is a helper function, where the download really happens. While-block
|
||||
reads data from connection a fixed chunk of data at a time, or less, until
|
||||
'required_length' is reached.
|
||||
|
||||
<Arguments>
|
||||
connection:
|
||||
The object that the _open_connection returns for communicating with the
|
||||
server about the contents of a URL.
|
||||
|
||||
temp_file:
|
||||
A temporary file where the contents at the URL specified by the
|
||||
'connection' object will be stored.
|
||||
|
||||
required_length:
|
||||
The number of bytes that we must download for the file. This is almost
|
||||
always specified by the TUF metadata for the data file in question
|
||||
(except in the case of timestamp metadata, in which case we would fix a
|
||||
reasonable upper bound).
|
||||
|
||||
<Side Effects>
|
||||
Data from the server will be written to 'temp_file'.
|
||||
|
||||
<Exceptions>
|
||||
Runtime or network exceptions will be raised without question.
|
||||
|
||||
<Returns>
|
||||
total_downloaded:
|
||||
The total number of bytes downloaded for the desired file.
|
||||
"""
|
||||
|
||||
# Tolerate servers with a slow start by ignoring their delivery speed for
|
||||
# 'tuf.conf.SLOW_START_GRACE_PERIOD' seconds. Set 'seconds_spent_receiving'
|
||||
# to negative SLOW_START_GRACE_PERIOD seconds, and begin checking the average
|
||||
# download speed once it is positive.
|
||||
grace_period = -tuf.conf.SLOW_START_GRACE_PERIOD
|
||||
|
||||
# Keep track of total bytes downloaded.
|
||||
number_of_bytes_received = 0
|
||||
|
||||
start_time = timeit.default_timer()
|
||||
|
||||
try:
|
||||
while True:
|
||||
# We download a fixed chunk of data in every round. This is so that we
|
||||
# can defend against slow retrieval attacks. Furthermore, we do not wish
|
||||
# to download an extremely large file in one shot.
|
||||
data = b''
|
||||
read_amount = min(tuf.conf.CHUNK_SIZE,
|
||||
required_length - number_of_bytes_received)
|
||||
#logger.debug('Reading next chunk...')
|
||||
|
||||
try:
|
||||
data = connection.read(read_amount)
|
||||
|
||||
# Python 3.2 returns 'IOError' if the remote file object has timed out.
|
||||
except (socket.error, IOError):
|
||||
pass
|
||||
|
||||
number_of_bytes_received = number_of_bytes_received + len(data)
|
||||
|
||||
# Data successfully read from the connection. Store it.
|
||||
temp_file.write(data)
|
||||
|
||||
if number_of_bytes_received == required_length:
|
||||
break
|
||||
|
||||
stop_time = timeit.default_timer()
|
||||
seconds_spent_receiving = stop_time - start_time
|
||||
|
||||
if (seconds_spent_receiving + grace_period) < 0:
|
||||
#logger.debug('Ignoring average download speed for another: '+\
|
||||
#str(-seconds_spent_receiving) + ' seconds')
|
||||
continue
|
||||
|
||||
# Measure the average download speed.
|
||||
average_download_speed = number_of_bytes_received / seconds_spent_receiving
|
||||
|
||||
# If the average download speed is below a certain threshold, we flag
|
||||
# this as a possible slow-retrieval attack.
|
||||
if average_download_speed < tuf.conf.MIN_AVERAGE_DOWNLOAD_SPEED:
|
||||
raise tuf.SlowRetrievalError(average_download_speed)
|
||||
break
|
||||
|
||||
else:
|
||||
logger.debug('Good average download speed: '+\
|
||||
str(average_download_speed)+' bytes/second')
|
||||
else:
|
||||
logger.debug('Ignoring average download speed for another: '+\
|
||||
str(-self.__seconds_spent_receiving)+' seconds')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def read(self, size):
|
||||
"""
|
||||
<Purpose>
|
||||
We override the ancestor read (socket._fileobject.read) operation to be a
|
||||
non-blocking operation.
|
||||
|
||||
Original code is at:
|
||||
http://hg.python.org/cpython/file/5be3fa83d436/Lib/socket.py#l336
|
||||
|
||||
<Arguments>
|
||||
size:
|
||||
The length of the data chunk that we would like to download. We assume
|
||||
that the size of the expected data chunk is accurate; otherwise, we are
|
||||
liable to miscount the number of truly slowly-retrieved chunks.
|
||||
|
||||
<Exceptions>
|
||||
tuf.SlowRetrievalError, in case we detect a slow-retrieval attack.
|
||||
|
||||
Any other exception thrown by socket._fileobject.read.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
Received data up to 'size' bytes.
|
||||
"""
|
||||
|
||||
# We should never try to specify a negative size.
|
||||
assert size >= 0
|
||||
|
||||
# Use max, disallow tiny reads in a loop as they are very inefficient.
|
||||
# We never leave read() with any leftover data from a new recv() call
|
||||
# in our internal buffer.
|
||||
rbufsize = max(self._rbufsize, self.default_bufsize)
|
||||
# Our use of StringIO rather than lists of string objects returned by
|
||||
# recv() minimizes memory usage and fragmentation that occurs when
|
||||
# rbufsize is large compared to the typical return value of recv().
|
||||
buf = self._rbuf
|
||||
buf.seek(0, 2) # seek end
|
||||
|
||||
# Read until size bytes or EOF seen, whichever comes first
|
||||
buf_len = buf.tell()
|
||||
if buf_len >= size:
|
||||
# Already have size bytes in our buffer? Extract and return.
|
||||
buf.seek(0)
|
||||
rv = buf.read(size)
|
||||
self._rbuf = StringIO()
|
||||
self._rbuf.write(buf.read())
|
||||
return rv
|
||||
|
||||
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
|
||||
# Since we try to detect slow retrieval, this should not be an infinite loop.
|
||||
while True:
|
||||
left = size - buf_len
|
||||
# recv() will malloc the amount of memory given as its
|
||||
# parameter even though it often returns much less data
|
||||
# than that. The returned data string is short lived
|
||||
# as we copy it into a StringIO and free it. This avoids
|
||||
# fragmentation issues on many platforms.
|
||||
try:
|
||||
self.__start_clock()
|
||||
data = self._sock.recv(left)
|
||||
except socket.timeout:
|
||||
self.__stop_clock_and_check_speed(0)
|
||||
continue
|
||||
except socket.error, e:
|
||||
if e.args[0] == EINTR:
|
||||
self.__stop_clock_and_check_speed(0)
|
||||
continue
|
||||
raise
|
||||
else:
|
||||
self.__stop_clock_and_check_speed(len(data))
|
||||
str(average_download_speed) + ' bytes per second')
|
||||
|
||||
# We might have no more data to read. Check number of bytes downloaded.
|
||||
if not data:
|
||||
message = 'Downloaded '+str(number_of_bytes_received)+'/'+ \
|
||||
str(required_length)+' bytes.'
|
||||
logger.debug(message)
|
||||
|
||||
# Finally, we signal that the download is complete.
|
||||
break
|
||||
n = len(data)
|
||||
if n == size and not buf_len:
|
||||
# Shortcut. Avoid buffer data copies when:
|
||||
# - We have no data in our buffer.
|
||||
# AND
|
||||
# - Our call to recv returned exactly the
|
||||
# number of bytes we were asked to read.
|
||||
return data
|
||||
if n == left:
|
||||
buf.write(data)
|
||||
del data # explicit free
|
||||
break
|
||||
assert n <= left, "recv(%d) returned %d bytes" % (left, n)
|
||||
buf.write(data)
|
||||
buf_len += n
|
||||
del data # explicit free
|
||||
#assert buf_len == buf.tell()
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class SaferHTTPResponse(httplib.HTTPResponse):
|
||||
"""A safer version of httplib.HTTPResponse, in which we only use safe socket
|
||||
file-like objects."""
|
||||
|
||||
def __init__(self, sock, debuglevel=0, strict=0, method=None,
|
||||
buffering=False):
|
||||
httplib.HTTPResponse.__init__(self, sock, debuglevel,
|
||||
strict, method)
|
||||
|
||||
# Delete the previous socket file-like object...
|
||||
del self.fp
|
||||
# ...and replace it with our safer version.
|
||||
if buffering:
|
||||
self.fp = SaferSocketFileObject(sock._sock, 'rb')
|
||||
else:
|
||||
self.fp = SaferSocketFileObject(sock._sock, 'rb', 0)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class VerifiedHTTPSConnection(httplib.HTTPSConnection):
|
||||
"""
|
||||
A connection that wraps connections with ssl certificate verification.
|
||||
|
||||
https://github.com/pypa/pip/blob/d0fa66ecc03ab20b7411b35f7c7b423f31f77761/pip/download.py#L72
|
||||
"""
|
||||
|
||||
def connect(self):
|
||||
|
||||
self.connection_kwargs = {}
|
||||
|
||||
#TODO: refactor compatibility logic into tuf.compatibility?
|
||||
|
||||
# for > py2.5
|
||||
if hasattr(self, 'timeout'):
|
||||
self.connection_kwargs.update(timeout = self.timeout)
|
||||
|
||||
# for >= py2.7
|
||||
if hasattr(self, 'source_address'):
|
||||
self.connection_kwargs.update(source_address = self.source_address)
|
||||
|
||||
sock = socket.create_connection((self.host, self.port), **self.connection_kwargs)
|
||||
|
||||
# for >= py2.7
|
||||
if getattr(self, '_tunnel_host', None):
|
||||
self.sock = sock
|
||||
self._tunnel()
|
||||
|
||||
# set location of certificate authorities
|
||||
assert os.path.isfile( tuf.conf.ssl_certificates )
|
||||
cert_path = tuf.conf.ssl_certificates
|
||||
|
||||
# TODO: Disallow SSLv2.
|
||||
# http://docs.python.org/dev/library/ssl.html#protocol-versions
|
||||
# TODO: Select the right ciphers.
|
||||
# http://docs.python.org/dev/library/ssl.html#cipher-selection
|
||||
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
|
||||
cert_reqs=ssl.CERT_REQUIRED,
|
||||
ca_certs=cert_path)
|
||||
|
||||
match_hostname(self.sock.getpeercert(), self.host)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class VerifiedHTTPSHandler(urllib2.HTTPSHandler):
|
||||
"""
|
||||
A HTTPSHandler that uses our own VerifiedHTTPSConnection.
|
||||
|
||||
https://github.com/pypa/pip/blob/d0fa66ecc03ab20b7411b35f7c7b423f31f77761/pip/download.py#L109
|
||||
"""
|
||||
|
||||
def __init__(self, connection_class = VerifiedHTTPSConnection):
|
||||
self.specialized_conn_class = connection_class
|
||||
urllib2.HTTPSHandler.__init__(self)
|
||||
|
||||
def https_open(self, req):
|
||||
return self.do_open(self.specialized_conn_class, req)
|
||||
|
||||
except:
|
||||
raise
|
||||
|
||||
else:
|
||||
# This else block returns and skips closing the connection in the finally
|
||||
# block, so close the connection here.
|
||||
connection.close()
|
||||
return number_of_bytes_received
|
||||
|
||||
finally:
|
||||
# Whatever happens, make sure that we always close the connection.
|
||||
connection.close()
|
||||
|
||||
|
||||
|
||||
|
|
@ -366,7 +349,7 @@ def _get_request(url):
|
|||
https://github.com/pypa/pip/blob/d0fa66ecc03ab20b7411b35f7c7b423f31f77761/pip/download.py#L147
|
||||
"""
|
||||
|
||||
return urllib2.Request(url, headers={'Accept-encoding': 'identity'})
|
||||
return six.moves.urllib.request.Request(url, headers={'Accept-encoding': 'identity'})
|
||||
|
||||
|
||||
|
||||
|
|
@ -385,15 +368,16 @@ def _get_opener(scheme=None):
|
|||
# If we are going over https, use an opener which will provide SSL
|
||||
# certificate verification.
|
||||
https_handler = VerifiedHTTPSHandler()
|
||||
opener = urllib2.build_opener(https_handler)
|
||||
opener = six.moves.urllib.request.build_opener(https_handler)
|
||||
|
||||
# strip out HTTPHandler to prevent MITM spoof
|
||||
for handler in opener.handlers:
|
||||
if isinstance(handler, urllib2.HTTPHandler):
|
||||
if isinstance(handler, six.moves.urllib.request.HTTPHandler):
|
||||
opener.handlers.remove(handler)
|
||||
|
||||
else:
|
||||
# Otherwise, use the default opener.
|
||||
opener = urllib2.build_opener()
|
||||
opener = six.moves.urllib.request.build_opener()
|
||||
|
||||
return opener
|
||||
|
||||
|
|
@ -434,84 +418,11 @@ def _open_connection(url):
|
|||
# servers do not recognize connections that originates from
|
||||
# Python-urllib/x.y.
|
||||
|
||||
parsed_url = urlparse.urlparse(url)
|
||||
parsed_url = six.moves.urllib.parse.urlparse(url)
|
||||
opener = _get_opener(scheme=parsed_url.scheme)
|
||||
request = _get_request(url)
|
||||
return opener.open(request)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _download_fixed_amount_of_data(connection, temp_file, required_length):
|
||||
"""
|
||||
<Purpose>
|
||||
This is a helper function, where the download really happens. While-block
|
||||
reads data from connection a fixed chunk of data at a time, or less, until
|
||||
'required_length' is reached.
|
||||
|
||||
<Arguments>
|
||||
connection:
|
||||
The object that the _open_connection returns for communicating with the
|
||||
server about the contents of a URL.
|
||||
|
||||
temp_file:
|
||||
A temporary file where the contents at the URL specified by the
|
||||
'connection' object will be stored.
|
||||
|
||||
required_length:
|
||||
The number of bytes that we must download for the file. This is almost
|
||||
always specified by the TUF metadata for the data file in question
|
||||
(except in the case of timestamp metadata, in which case we would fix a
|
||||
reasonable upper bound).
|
||||
|
||||
<Side Effects>
|
||||
Data from the server will be written to 'temp_file'.
|
||||
|
||||
<Exceptions>
|
||||
Runtime or network exceptions will be raised without question.
|
||||
|
||||
<Returns>
|
||||
total_downloaded:
|
||||
The total number of bytes we have downloaded for the desired file and
|
||||
which should be equal to 'required_length'.
|
||||
"""
|
||||
|
||||
# Keep track of total bytes downloaded.
|
||||
total_downloaded = 0
|
||||
|
||||
try:
|
||||
while True:
|
||||
# We download a fixed chunk of data in every round. This is so that we
|
||||
# can defend against slow retrieval attacks. Furthermore, we do not wish
|
||||
# to download an extremely large file in one shot.
|
||||
amount_to_read = min(tuf.conf.CHUNK_SIZE,
|
||||
required_length-total_downloaded)
|
||||
logger.debug('Reading next chunk...')
|
||||
data = connection.read(amount_to_read)
|
||||
|
||||
# We might have no more data to read. Check number of bytes downloaded.
|
||||
if not data:
|
||||
message = 'Downloaded '+str(total_downloaded)+'/'+ \
|
||||
str(required_length)+' bytes.'
|
||||
logger.debug(message)
|
||||
|
||||
# Finally, we signal that the download is complete.
|
||||
break
|
||||
|
||||
# Data successfully read from the connection. Store it.
|
||||
temp_file.write(data)
|
||||
total_downloaded = total_downloaded + len(data)
|
||||
|
||||
except:
|
||||
raise
|
||||
|
||||
else:
|
||||
return total_downloaded
|
||||
|
||||
finally:
|
||||
# Whatever happens, make sure that we always close the connection.
|
||||
connection.close()
|
||||
return opener.open(request, timeout = tuf.conf.SOCKET_TIMEOUT)
|
||||
|
||||
|
||||
|
||||
|
|
@ -542,14 +453,19 @@ def _get_content_length(connection):
|
|||
try:
|
||||
# What is the length of this document according to the HTTP spec?
|
||||
reported_length = connection.info().get('Content-Length')
|
||||
|
||||
# Try casting it as a decimal number.
|
||||
reported_length = int(reported_length, 10)
|
||||
|
||||
# Make sure that it is a nonnegative integer.
|
||||
assert reported_length > -1
|
||||
|
||||
except:
|
||||
logger.exception('Could not get content length about '+str(connection)+
|
||||
' from server!')
|
||||
message = \
|
||||
'Could not get content length about ' + str(connection) + ' from server.'
|
||||
logger.exception(message)
|
||||
reported_length = None
|
||||
|
||||
finally:
|
||||
return reported_length
|
||||
|
||||
|
|
@ -653,7 +569,7 @@ def _check_downloaded_length(total_downloaded, required_length,
|
|||
logger.info('Downloaded '+str(total_downloaded)+' bytes out of the '+\
|
||||
'expected '+str(required_length)+ ' bytes.')
|
||||
else:
|
||||
difference_in_bytes = abs(total_downloaded-required_length)
|
||||
difference_in_bytes = abs(total_downloaded - required_length)
|
||||
|
||||
# What we downloaded is not equal to the required length, but did we ask
|
||||
# for strict checking of required length?
|
||||
|
|
@ -679,117 +595,61 @@ def _check_downloaded_length(total_downloaded, required_length,
|
|||
|
||||
|
||||
|
||||
def safe_download(url, required_length):
|
||||
return _download_file(url, required_length, STRICT_REQUIRED_LENGTH=True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def unsafe_download(url, required_length):
|
||||
return _download_file(url, required_length, STRICT_REQUIRED_LENGTH=False)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _download_file(url, required_length, STRICT_REQUIRED_LENGTH=True):
|
||||
class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection):
|
||||
"""
|
||||
<Purpose>
|
||||
Given the url, hashes and length of the desired file, this function
|
||||
opens a connection to 'url' and downloads the file while ensuring its
|
||||
length and hashes match 'required_hashes' and 'required_length'.
|
||||
|
||||
tuf.util.TempFile is used instead of regular tempfile object because of
|
||||
additional functionality provided by 'tuf.util.TempFile'.
|
||||
|
||||
<Arguments>
|
||||
url:
|
||||
A URL string that represents the location of the file.
|
||||
|
||||
required_length:
|
||||
An integer value representing the length of the file.
|
||||
A connection that wraps connections with ssl certificate verification.
|
||||
|
||||
STRICT_REQUIRED_LENGTH:
|
||||
A Boolean indicator used to signal whether we should perform strict
|
||||
checking of required_length. True by default. We explicitly set this to
|
||||
False when we know that we want to turn this off for downloading the
|
||||
timestamp metadata, which has no signed required_length.
|
||||
|
||||
<Side Effects>
|
||||
A 'tuf.util.TempFile' object is created on disk to store the contents of
|
||||
'url'.
|
||||
|
||||
<Exceptions>
|
||||
tuf.DownloadLengthMismatchError, if there was a mismatch of observed vs
|
||||
expected lengths while downloading the file.
|
||||
|
||||
tuf.FormatError, if any of the arguments are improperly formatted.
|
||||
|
||||
Any other unforeseen runtime exception.
|
||||
|
||||
<Returns>
|
||||
A 'tuf.util.TempFile' file-like object which points to the contents of
|
||||
'url'.
|
||||
https://github.com/pypa/pip/blob/d0fa66ecc03ab20b7411b35f7c7b423f31f77761/pip/download.py#L72
|
||||
"""
|
||||
|
||||
# Do all of the arguments have the appropriate format?
|
||||
# Raise 'tuf.FormatError' if there is a mismatch.
|
||||
tuf.formats.URL_SCHEMA.check_match(url)
|
||||
tuf.formats.LENGTH_SCHEMA.check_match(required_length)
|
||||
def connect(self):
|
||||
|
||||
# 'url.replace()' is for compatibility with Windows-based systems because
|
||||
# they might put back-slashes in place of forward-slashes. This converts it
|
||||
# to the common format.
|
||||
url = url.replace('\\', '/')
|
||||
logger.info('Downloading: '+str(url))
|
||||
self.connection_kwargs = {}
|
||||
|
||||
# NOTE: Not thread-safe.
|
||||
# Save current values or functions for restoration later.
|
||||
previous_socket_timeout = socket.getdefaulttimeout()
|
||||
previous_http_response_class = httplib.HTTPConnection.response_class
|
||||
# for > py2.5
|
||||
if hasattr(self, 'timeout'):
|
||||
self.connection_kwargs.update(timeout = self.timeout)
|
||||
|
||||
# This is the temporary file that we will return to contain the contents of
|
||||
# the downloaded file.
|
||||
temp_file = tuf.util.TempFile()
|
||||
# for >= py2.7
|
||||
if hasattr(self, 'source_address'):
|
||||
self.connection_kwargs.update(source_address = self.source_address)
|
||||
|
||||
try:
|
||||
# NOTE: Not thread-safe.
|
||||
# Set timeout to induce non-blocking socket operations.
|
||||
socket.setdefaulttimeout(tuf.conf.SOCKET_TIMEOUT)
|
||||
# Replace the socket file-like object class with our safer version.
|
||||
httplib.HTTPConnection.response_class = SaferHTTPResponse
|
||||
sock = socket.create_connection((self.host, self.port), **self.connection_kwargs)
|
||||
|
||||
# Open the connection to the remote file.
|
||||
connection = _open_connection(url)
|
||||
# for >= py2.7
|
||||
if getattr(self, '_tunnel_host', None):
|
||||
self.sock = sock
|
||||
self._tunnel()
|
||||
|
||||
# We ask the server about how big it thinks this file should be.
|
||||
reported_length = _get_content_length(connection)
|
||||
# set location of certificate authorities
|
||||
assert os.path.isfile( tuf.conf.ssl_certificates )
|
||||
cert_path = tuf.conf.ssl_certificates
|
||||
|
||||
# Then, we check whether the required length matches the reported length.
|
||||
_check_content_length(reported_length, required_length,
|
||||
STRICT_REQUIRED_LENGTH)
|
||||
# TODO: Disallow SSLv2.
|
||||
# http://docs.python.org/dev/library/ssl.html#protocol-versions
|
||||
# TODO: Select the right ciphers.
|
||||
# http://docs.python.org/dev/library/ssl.html#cipher-selection
|
||||
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
|
||||
cert_reqs=ssl.CERT_REQUIRED,
|
||||
ca_certs=cert_path)
|
||||
|
||||
# Download the contents of the URL, up to the required length, to a
|
||||
# temporary file, and get the total number of downloaded bytes.
|
||||
total_downloaded = _download_fixed_amount_of_data(connection, temp_file,
|
||||
required_length)
|
||||
match_hostname(self.sock.getpeercert(), self.host)
|
||||
|
||||
# Does the total number of downloaded bytes match the required length?
|
||||
_check_downloaded_length(total_downloaded, required_length,
|
||||
STRICT_REQUIRED_LENGTH=STRICT_REQUIRED_LENGTH)
|
||||
|
||||
except:
|
||||
# Close 'temp_file'; any written data is lost.
|
||||
temp_file.close_temp_file()
|
||||
logger.exception('Could not download URL: '+str(url))
|
||||
raise
|
||||
|
||||
else:
|
||||
return temp_file
|
||||
|
||||
finally:
|
||||
# NOTE: Not thread-safe.
|
||||
# Restore previously saved values or functions.
|
||||
httplib.HTTPConnection.response_class = previous_http_response_class
|
||||
socket.setdefaulttimeout(previous_socket_timeout)
|
||||
|
||||
class VerifiedHTTPSHandler(six.moves.urllib.request.HTTPSHandler):
|
||||
"""
|
||||
A HTTPSHandler that uses our own VerifiedHTTPSConnection.
|
||||
|
||||
https://github.com/pypa/pip/blob/d0fa66ecc03ab20b7411b35f7c7b423f31f77761/pip/download.py#L109
|
||||
"""
|
||||
|
||||
def __init__(self, connection_class = VerifiedHTTPSConnection):
|
||||
self.specialized_conn_class = connection_class
|
||||
six.moves.urllib.request.HTTPSHandler.__init__(self)
|
||||
|
||||
def https_open(self, req):
|
||||
return self.do_open(self.specialized_conn_class, req)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# 'binascii' required for hexadecimal conversions. Signatures and
|
||||
# public/private keys are hexlified.
|
||||
|
|
@ -84,6 +85,10 @@
|
|||
# TODO: Version 0.2.3 of 'pynacl' prints: "UserWarning: reimporting '...' might
|
||||
# overwrite older definitions." when importing 'nacl.signing'. Suppress user
|
||||
# warnings temporarily (at least until this issue is fixed by PyNaCl).
|
||||
#
|
||||
# Note: A 'pragma: no cover' comment is intended for test 'coverage'. Lines
|
||||
# or code blocks with this comment should not be flagged as uncovered.
|
||||
# pynacl will always be install prior to running the unit tests.
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore')
|
||||
try:
|
||||
|
|
@ -92,7 +97,7 @@
|
|||
|
||||
# PyNaCl's 'cffi' dependency may raise an 'IOError' exception when importing
|
||||
# 'nacl.signing'.
|
||||
except (ImportError, IOError):
|
||||
except (ImportError, IOError): # pragma: no cover
|
||||
pass
|
||||
|
||||
# The optimized pure Python implementation of ed25519 provided by TUF. If
|
||||
|
|
@ -163,9 +168,9 @@ def generate_public_and_private():
|
|||
# key generation.
|
||||
try:
|
||||
nacl_key = nacl.signing.SigningKey(seed)
|
||||
public = str(nacl_key.verify_key)
|
||||
public = nacl_key.verify_key.encode(encoder=nacl.encoding.RawEncoder())
|
||||
|
||||
except NameError:
|
||||
except NameError: # pragma: no cover
|
||||
message = 'The PyNaCl library and/or its dependencies unavailable.'
|
||||
raise tuf.UnsupportedLibraryError(message)
|
||||
|
||||
|
|
@ -187,7 +192,7 @@ def create_signature(public_key, private_key, data):
|
|||
A signature is a 64-byte string.
|
||||
|
||||
>>> public, private = generate_public_and_private()
|
||||
>>> data = 'The quick brown fox jumps over the lazy dog'
|
||||
>>> data = b'The quick brown fox jumps over the lazy dog'
|
||||
>>> signature, method = \
|
||||
create_signature(public, private, data)
|
||||
>>> tuf.formats.ED25519SIGNATURE_SCHEMA.matches(signature)
|
||||
|
|
@ -250,13 +255,13 @@ def create_signature(public_key, private_key, data):
|
|||
nacl_sig = nacl_key.sign(data)
|
||||
signature = nacl_sig.signature
|
||||
|
||||
except NameError:
|
||||
except NameError: # pragma: no cover
|
||||
message = 'The PyNaCl library and/or its dependencies unavailable.'
|
||||
raise tuf.UnsupportedLibraryError(message)
|
||||
|
||||
except (ValueError, TypeError, nacl.exceptions.CryptoError):
|
||||
except (ValueError, TypeError, nacl.exceptions.CryptoError) as e:
|
||||
message = 'An "ed25519" signature could not be created with PyNaCl.'
|
||||
raise tuf.CryptoError(message)
|
||||
raise tuf.CryptoError(message + str(e))
|
||||
|
||||
return signature, method
|
||||
|
||||
|
|
@ -272,14 +277,14 @@ def verify_signature(public_key, method, signature, data, use_pynacl=False):
|
|||
'sig', and 'data' arguments to complete the verification.
|
||||
|
||||
>>> public, private = generate_public_and_private()
|
||||
>>> data = 'The quick brown fox jumps over the lazy dog'
|
||||
>>> data = b'The quick brown fox jumps over the lazy dog'
|
||||
>>> signature, method = \
|
||||
create_signature(public, private, data)
|
||||
>>> 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_data = b'The sly brown fox jumps over the lazy dog'
|
||||
>>> bad_signature, method = \
|
||||
create_signature(public, private, bad_data)
|
||||
>>> verify_signature(public, method, bad_signature, data, use_pynacl=False)
|
||||
|
|
@ -348,10 +353,9 @@ def verify_signature(public_key, method, signature, data, use_pynacl=False):
|
|||
try:
|
||||
nacl_verify_key = nacl.signing.VerifyKey(public)
|
||||
nacl_message = nacl_verify_key.verify(data, signature)
|
||||
if nacl_message == data:
|
||||
valid_signature = True
|
||||
valid_signature = True
|
||||
|
||||
except NameError:
|
||||
except NameError: # pragma: no cover
|
||||
message = 'The PyNaCl library and/or its dependencies unavailable.'
|
||||
raise tuf.UnsupportedLibraryError(message)
|
||||
|
||||
|
|
@ -366,8 +370,9 @@ def verify_signature(public_key, method, signature, data, use_pynacl=False):
|
|||
|
||||
# The pure Python implementation raises 'Exception' if 'signature' is
|
||||
# invalid.
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
else:
|
||||
message = 'Unsupported ed25519 signing method: '+repr(method)+'.\n'+ \
|
||||
'Supported methods: '+repr(_SUPPORTED_ED25519_SIGNING_METHODS)+'.'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
formats.py
|
||||
|
|
@ -60,6 +62,14 @@
|
|||
signable_object = make_signable(unsigned_object)
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import binascii
|
||||
import calendar
|
||||
import re
|
||||
|
|
@ -69,7 +79,7 @@
|
|||
|
||||
import tuf
|
||||
import tuf.schema as SCHEMA
|
||||
|
||||
import tuf._vendor.six as six
|
||||
|
||||
# Note that in the schema definitions below, the 'SCHEMA.Object' types allow
|
||||
# additional keys which are not defined. Thus, any additions to them will be
|
||||
|
|
@ -146,7 +156,7 @@
|
|||
|
||||
# The contents of an encrypted TUF key. Encrypted TUF keys are saved to files
|
||||
# in this format.
|
||||
ENCRYPTEDKEY_SCHEMA = SCHEMA.AnyString()
|
||||
ENCRYPTEDKEY_SCHEMA = SCHEMA.AnyBytes()
|
||||
|
||||
# A value that is either True or False, on or off, etc.
|
||||
BOOLEAN_SCHEMA = SCHEMA.Boolean()
|
||||
|
|
@ -169,7 +179,7 @@
|
|||
NUMBINS_SCHEMA = SCHEMA.Integer(lo=1)
|
||||
|
||||
# A PyCrypto signature.
|
||||
PYCRYPTOSIGNATURE_SCHEMA = SCHEMA.AnyString()
|
||||
PYCRYPTOSIGNATURE_SCHEMA = SCHEMA.AnyBytes()
|
||||
|
||||
# An RSA key in PEM format.
|
||||
PEMRSA_SCHEMA = SCHEMA.AnyString()
|
||||
|
|
@ -219,13 +229,13 @@
|
|||
keyval = KEYVAL_SCHEMA)
|
||||
|
||||
# An ED25519 raw public key, which must be 32 bytes.
|
||||
ED25519PUBLIC_SCHEMA = SCHEMA.LengthString(32)
|
||||
ED25519PUBLIC_SCHEMA = SCHEMA.LengthBytes(32)
|
||||
|
||||
# An ED25519 raw seed key, which must be 32 bytes.
|
||||
ED25519SEED_SCHEMA = SCHEMA.LengthString(32)
|
||||
ED25519SEED_SCHEMA = SCHEMA.LengthBytes(32)
|
||||
|
||||
# An ED25519 raw signature, which must be 64 bytes.
|
||||
ED25519SIGNATURE_SCHEMA = SCHEMA.LengthString(64)
|
||||
ED25519SIGNATURE_SCHEMA = SCHEMA.LengthBytes(64)
|
||||
|
||||
# Required installation libraries expected by the repository tools and other
|
||||
# cryptography modules.
|
||||
|
|
@ -359,6 +369,7 @@
|
|||
name = SCHEMA.Optional(ROLENAME_SCHEMA),
|
||||
keyids = KEYIDS_SCHEMA,
|
||||
threshold = THRESHOLD_SCHEMA,
|
||||
backtrack = SCHEMA.Optional(BOOLEAN_SCHEMA),
|
||||
paths = SCHEMA.Optional(RELPATHS_SCHEMA),
|
||||
path_hash_prefixes = SCHEMA.Optional(PATH_HASH_PREFIXES_SCHEMA))
|
||||
|
||||
|
|
@ -494,7 +505,8 @@ class MetaFile(object):
|
|||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, MetaFile) and self.info == other.info
|
||||
|
||||
|
||||
__hash__ = None
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
|
@ -506,10 +518,12 @@ def __getattr__(self, name):
|
|||
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:
|
||||
return self.info[name]
|
||||
raise AttributeError, name
|
||||
|
||||
else:
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
|
||||
|
|
@ -735,7 +749,7 @@ def datetime_to_unix_timestamp(datetime_object):
|
|||
timestamp. For example, Python's time.time() returns a Unix timestamp, and
|
||||
includes the number of microseconds. 'datetime_object' is converted to UTC.
|
||||
|
||||
>>> datetime_object = datetime.datetime(1985, 10, 26, 01, 22)
|
||||
>>> datetime_object = datetime.datetime(1985, 10, 26, 1, 22)
|
||||
>>> timestamp = datetime_to_unix_timestamp(datetime_object)
|
||||
>>> timestamp
|
||||
499137720
|
||||
|
|
@ -820,7 +834,7 @@ def format_base64(data):
|
|||
|
||||
<Arguments>
|
||||
data:
|
||||
A string or buffer of data to convert.
|
||||
Binary or buffer of data to convert.
|
||||
|
||||
<Exceptions>
|
||||
tuf.FormatError, if the base64 encoding fails or the argument
|
||||
|
|
@ -834,9 +848,9 @@ def format_base64(data):
|
|||
"""
|
||||
|
||||
try:
|
||||
return binascii.b2a_base64(data).rstrip('=\n ')
|
||||
return binascii.b2a_base64(data).decode('utf-8').rstrip('=\n ')
|
||||
|
||||
except (TypeError, binascii.Error), e:
|
||||
except (TypeError, binascii.Error) as e:
|
||||
raise tuf.FormatError('Invalid base64 encoding: '+str(e))
|
||||
|
||||
|
||||
|
|
@ -864,7 +878,7 @@ def parse_base64(base64_string):
|
|||
'base64_string'.
|
||||
"""
|
||||
|
||||
if not isinstance(base64_string, basestring):
|
||||
if not isinstance(base64_string, six.string_types):
|
||||
message = 'Invalid argument: '+repr(base64_string)
|
||||
raise tuf.FormatError(message)
|
||||
|
||||
|
|
@ -874,9 +888,9 @@ def parse_base64(base64_string):
|
|||
base64_string = base64_string + padding
|
||||
|
||||
try:
|
||||
return binascii.a2b_base64(base64_string)
|
||||
return binascii.a2b_base64(base64_string.encode('utf-8'))
|
||||
|
||||
except (TypeError, binascii.Error), e:
|
||||
except (TypeError, binascii.Error) as e:
|
||||
raise tuf.FormatError('Invalid base64 encoding: '+str(e))
|
||||
|
||||
|
||||
|
|
@ -1194,10 +1208,8 @@ def _canonical_string_encoder(string):
|
|||
"""
|
||||
|
||||
string = '"%s"' % re.sub(r'(["\\])', r'\\\1', string)
|
||||
if isinstance(string, unicode):
|
||||
return string.encode('utf-8')
|
||||
else:
|
||||
return string
|
||||
|
||||
return string
|
||||
|
||||
|
||||
|
||||
|
|
@ -1207,7 +1219,7 @@ def _encode_canonical(object, output_function):
|
|||
# Helper for encode_canonical. Older versions of json.encoder don't
|
||||
# even let us replace the separators.
|
||||
|
||||
if isinstance(object, basestring):
|
||||
if isinstance(object, six.string_types):
|
||||
output_function(_canonical_string_encoder(object))
|
||||
elif object is True:
|
||||
output_function("true")
|
||||
|
|
@ -1215,7 +1227,7 @@ def _encode_canonical(object, output_function):
|
|||
output_function("false")
|
||||
elif object is None:
|
||||
output_function("null")
|
||||
elif isinstance(object, (int, long)):
|
||||
elif isinstance(object, six.integer_types):
|
||||
output_function(str(object))
|
||||
elif isinstance(object, (tuple, list)):
|
||||
output_function("[")
|
||||
|
|
@ -1228,8 +1240,7 @@ def _encode_canonical(object, output_function):
|
|||
elif isinstance(object, dict):
|
||||
output_function("{")
|
||||
if len(object):
|
||||
items = object.items()
|
||||
items.sort()
|
||||
items = sorted(six.iteritems(object))
|
||||
for key, value in items[:-1]:
|
||||
output_function(_canonical_string_encoder(key))
|
||||
output_function(":")
|
||||
|
|
|
|||
63
tuf/hash.py
63
tuf/hash.py
|
|
@ -18,17 +18,25 @@
|
|||
available to TUF, simplifying the creation of digest objects, and
|
||||
providing a central location for hash routines are the main goals
|
||||
of this module. Support routines implemented include functions to
|
||||
create digest objects given a filename or file object.
|
||||
Hashlib and pycrypto hash algorithms currently supported.
|
||||
|
||||
create digest objects given a filename or file object. Hashlib and PyCrypto
|
||||
hash algorithms currently supported.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
# Import tuf Exceptions.
|
||||
import tuf
|
||||
import tuf.log
|
||||
import tuf._vendor.six as six
|
||||
|
||||
|
||||
# Import tuf logger to log warning messages.
|
||||
logger = logging.getLogger('tuf.hash')
|
||||
|
|
@ -126,7 +134,6 @@ def digest(algorithm=_DEFAULT_HASH_ALGORITHM,
|
|||
<Returns>
|
||||
Digest object (e.g., hashlib.new(algorithm) or
|
||||
algorithm.new() # pycrypto).
|
||||
|
||||
"""
|
||||
|
||||
# Was a hashlib digest object requested and is it supported?
|
||||
|
|
@ -189,6 +196,7 @@ def digest_fileobject(file_object, algorithm=_DEFAULT_HASH_ALGORITHM,
|
|||
|
||||
<Exceptions>
|
||||
tuf.UnsupportedAlgorithmError
|
||||
|
||||
tuf.Error
|
||||
|
||||
<Side Effects>
|
||||
|
|
@ -197,7 +205,6 @@ def digest_fileobject(file_object, algorithm=_DEFAULT_HASH_ALGORITHM,
|
|||
<Returns>
|
||||
Digest object (e.g., hashlib.new(algorithm) or
|
||||
algorithm.new() # pycrypto).
|
||||
|
||||
"""
|
||||
|
||||
# Digest object returned whose hash will be updated using 'file_object'.
|
||||
|
|
@ -218,7 +225,13 @@ def digest_fileobject(file_object, algorithm=_DEFAULT_HASH_ALGORITHM,
|
|||
data = file_object.read(chunksize)
|
||||
if not data:
|
||||
break
|
||||
digest_object.update(data_to_string(data))
|
||||
|
||||
if not isinstance(data, six.binary_type):
|
||||
digest_object.update(data.encode('utf-8'))
|
||||
|
||||
else:
|
||||
digest_object.update(data)
|
||||
|
||||
return digest_object
|
||||
|
||||
|
||||
|
|
@ -254,7 +267,6 @@ def digest_filename(filename, algorithm=_DEFAULT_HASH_ALGORITHM,
|
|||
<Returns>
|
||||
Digest object (e.g., hashlib.new(algorithm) or
|
||||
algorithm.new() # pycrypto).
|
||||
|
||||
"""
|
||||
|
||||
# Open 'filename' in read+binary mode.
|
||||
|
|
@ -267,40 +279,5 @@ def digest_filename(filename, algorithm=_DEFAULT_HASH_ALGORITHM,
|
|||
digest_object = digest_fileobject(file_object, algorithm, hash_library)
|
||||
|
||||
file_object.close()
|
||||
|
||||
return digest_object
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def data_to_string(data):
|
||||
"""
|
||||
<Purpose>
|
||||
Return 'data' as a string. The update() function of a digest object
|
||||
only accepts strings, however, TUF will often need to feed this function
|
||||
non-strings. This utility function circumvents this issue and decides how
|
||||
exactly to convert these objects TUF might use.
|
||||
|
||||
<Arguments>
|
||||
data:
|
||||
The data object to be returned as a string.
|
||||
|
||||
<Exceptions>
|
||||
None.
|
||||
|
||||
<Side Effects>
|
||||
None.
|
||||
|
||||
<Returns>
|
||||
String.
|
||||
|
||||
"""
|
||||
|
||||
if isinstance(data, str):
|
||||
return data
|
||||
|
||||
elif isinstance(data, unicode):
|
||||
return data.encode("utf-8")
|
||||
|
||||
else:
|
||||
return str(data)
|
||||
|
|
|
|||
19
tuf/keydb.py
19
tuf/keydb.py
|
|
@ -27,12 +27,21 @@
|
|||
'keyid' key (i.e., rsakey['keyid']).
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import copy
|
||||
|
||||
import tuf
|
||||
import tuf.formats
|
||||
import tuf.keys
|
||||
import tuf._vendor.six as six
|
||||
|
||||
# List of strings representing the key types supported by TUF.
|
||||
_SUPPORTED_KEY_TYPES = ['rsa', 'ed25519']
|
||||
|
|
@ -84,7 +93,7 @@ def create_keydb_from_root_metadata(root_metadata):
|
|||
# Iterate through the keys found in 'root_metadata' by converting
|
||||
# them to 'RSAKEY_SCHEMA' if their type is 'rsa', and then
|
||||
# adding them the database. Duplicates are avoided.
|
||||
for keyid, key_metadata in root_metadata['keys'].items():
|
||||
for keyid, key_metadata in six.iteritems(root_metadata['keys']):
|
||||
if key_metadata['keytype'] in _SUPPORTED_KEY_TYPES:
|
||||
# 'key_metadata' is stored in 'KEY_SCHEMA' format. Call
|
||||
# create_from_metadata_format() to get the key in 'RSAKEY_SCHEMA'
|
||||
|
|
@ -93,17 +102,17 @@ def create_keydb_from_root_metadata(root_metadata):
|
|||
try:
|
||||
add_key(key_dict, keyid)
|
||||
|
||||
except tuf.KeyAlreadyExistsError, e:
|
||||
logger.warn(e)
|
||||
except tuf.KeyAlreadyExistsError as e:
|
||||
logger.warning(e)
|
||||
continue
|
||||
|
||||
# 'tuf.Error' raised if keyid does not match the keyid for 'rsakey_dict'.
|
||||
except tuf.Error, e:
|
||||
except tuf.Error as e:
|
||||
logger.error(e)
|
||||
continue
|
||||
|
||||
else:
|
||||
logger.warn('Root Metadata file contains a key with an invalid keytype.')
|
||||
logger.warning('Root Metadata file contains a key with an invalid keytype.')
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
63
tuf/keys.py
63
tuf/keys.py
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
keys.py
|
||||
|
|
@ -44,6 +46,14 @@
|
|||
key (i.e., rsakey['keyid']).
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# Required for hexadecimal conversions. Signatures and public/private keys are
|
||||
# hexlified.
|
||||
import binascii
|
||||
|
|
@ -303,13 +313,13 @@ def generate_ed25519_key():
|
|||
# Generate the keyid of the ED25519 key. 'key_value' corresponds to the
|
||||
# 'keyval' entry of the 'ED25519KEY_SCHEMA' dictionary. The private key
|
||||
# information is not included in the generation of the 'keyid' identifier.
|
||||
key_value = {'public': binascii.hexlify(public),
|
||||
key_value = {'public': binascii.hexlify(public).decode(),
|
||||
'private': ''}
|
||||
keyid = _get_keyid(keytype, key_value)
|
||||
|
||||
# Build the 'ed25519_key' dictionary. Update 'key_value' with the ED25519
|
||||
# private key prior to adding 'key_value' to 'ed25519_key'.
|
||||
key_value['private'] = binascii.hexlify(private)
|
||||
key_value['private'] = binascii.hexlify(private).decode()
|
||||
|
||||
ed25519_key['keytype'] = keytype
|
||||
ed25519_key['keyid'] = keyid
|
||||
|
|
@ -491,7 +501,7 @@ def _get_keyid(keytype, key_value):
|
|||
# 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(key_update_data)
|
||||
digest_object.update(key_update_data.encode('utf-8'))
|
||||
|
||||
# 'keyid' becomes the hexadecimal representation of the hash.
|
||||
keyid = digest_object.hexdigest()
|
||||
|
|
@ -684,7 +694,7 @@ def create_signature(key_dict, data):
|
|||
# otherwise raise an exception.
|
||||
if keytype == 'rsa':
|
||||
if _RSA_CRYPTO_LIBRARY == 'pycrypto':
|
||||
sig, method = tuf.pycrypto_keys.create_rsa_signature(private, data)
|
||||
sig, method = tuf.pycrypto_keys.create_rsa_signature(private, data.encode('utf-8'))
|
||||
|
||||
else: # pragma: no cover
|
||||
message = 'Unsupported "tuf.conf.RSA_CRYPTO_LIBRARY": '+\
|
||||
|
|
@ -692,10 +702,10 @@ def create_signature(key_dict, data):
|
|||
raise tuf.UnsupportedLibraryError(message)
|
||||
|
||||
elif keytype == 'ed25519':
|
||||
public = binascii.unhexlify(public)
|
||||
private = binascii.unhexlify(private)
|
||||
public = binascii.unhexlify(public.encode('utf-8'))
|
||||
private = binascii.unhexlify(private.encode('utf-8'))
|
||||
if 'pynacl' in _available_crypto_libraries:
|
||||
sig, method = tuf.ed25519_keys.create_signature(public, private, data)
|
||||
sig, method = tuf.ed25519_keys.create_signature(public, private, data.encode('utf-8'))
|
||||
|
||||
else: # pragma: no cover
|
||||
message = 'The required PyNaCl library is unavailable.'
|
||||
|
|
@ -709,7 +719,7 @@ def create_signature(key_dict, data):
|
|||
# The hexadecimal representation of 'sig' is stored in the signature.
|
||||
signature['keyid'] = keyid
|
||||
signature['method'] = method
|
||||
signature['sig'] = binascii.hexlify(sig)
|
||||
signature['sig'] = binascii.hexlify(sig).decode()
|
||||
|
||||
return signature
|
||||
|
||||
|
|
@ -798,7 +808,7 @@ def verify_signature(key_dict, signature, data):
|
|||
# key_dict['keyval']['private'].
|
||||
method = signature['method']
|
||||
sig = signature['sig']
|
||||
sig = binascii.unhexlify(sig)
|
||||
sig = binascii.unhexlify(sig.encode('utf-8'))
|
||||
public = key_dict['keyval']['public']
|
||||
keytype = key_dict['keytype']
|
||||
valid_signature = False
|
||||
|
|
@ -807,7 +817,7 @@ def verify_signature(key_dict, signature, data):
|
|||
# generated across different platforms and Python key dictionaries. The
|
||||
# resulting 'data' is a string encoded in UTF-8 and compatible with the input
|
||||
# expected by the cryptography functions called below.
|
||||
data = tuf.formats.encode_canonical(data)
|
||||
data = tuf.formats.encode_canonical(data).encode('utf-8')
|
||||
|
||||
# Call the appropriate cryptography libraries for the supported key types,
|
||||
# otherwise raise an exception.
|
||||
|
|
@ -822,20 +832,20 @@ def verify_signature(key_dict, signature, data):
|
|||
else:
|
||||
valid_signature = tuf.pycrypto_keys.verify_rsa_signature(sig, method,
|
||||
public, data)
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
message = 'Unsupported "tuf.conf.RSA_CRYPTO_LIBRARY": '+\
|
||||
repr(_RSA_CRYPTO_LIBRARY)+'.'
|
||||
raise tuf.UnsupportedLibraryError(message)
|
||||
|
||||
elif keytype == 'ed25519':
|
||||
public = binascii.unhexlify(public)
|
||||
public = binascii.unhexlify(public.encode('utf-8'))
|
||||
if _ED25519_CRYPTO_LIBRARY == 'pynacl' or \
|
||||
'pynacl' in _available_crypto_libraries:
|
||||
valid_signature = tuf.ed25519_keys.verify_signature(public,
|
||||
method, sig, data,
|
||||
use_pynacl=True)
|
||||
# Fall back to the optimized pure python implementation of ed25519.
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
valid_signature = tuf.ed25519_keys.verify_signature(public,
|
||||
method, sig, data,
|
||||
use_pynacl=False)
|
||||
|
|
@ -933,7 +943,7 @@ def import_rsakey_from_encrypted_pem(encrypted_pem, password):
|
|||
public, private = \
|
||||
tuf.pycrypto_keys.create_rsa_public_and_private_from_encrypted_pem(encrypted_pem,
|
||||
password)
|
||||
else:
|
||||
else: #pragma: no cover
|
||||
message = 'Invalid crypto library: '+repr(_RSA_CRYPTO_LIBRARY)+'.'
|
||||
raise tuf.UnsupportedLibraryError(message)
|
||||
|
||||
|
|
@ -978,10 +988,6 @@ def format_rsakey_from_pem(pem):
|
|||
>>> rsa_key2 = format_rsakey_from_pem(public)
|
||||
>>> rsa_key == rsa_key2
|
||||
True
|
||||
>>> format_rsakey_from_pem('bad_pem')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
FormatError: The PEM string argument is improperly formatted.
|
||||
|
||||
<Arguments>
|
||||
pem:
|
||||
|
|
@ -1007,7 +1013,7 @@ def format_rsakey_from_pem(pem):
|
|||
# Ensure the PEM string starts with the required number of dashes. Although
|
||||
# a simple validation of 'pem' is performed here, a fully valid PEM string is
|
||||
# needed to successfully verify signatures.
|
||||
if not pem.startswith(b'-----'):
|
||||
if not pem.startswith('-----'):
|
||||
raise tuf.FormatError('The PEM string argument is improperly formatted.')
|
||||
|
||||
# Begin building the RSA key dictionary.
|
||||
|
|
@ -1057,7 +1063,7 @@ def encrypt_key(key_object, password):
|
|||
|
||||
>>> ed25519_key = generate_ed25519_key()
|
||||
>>> password = 'secret'
|
||||
>>> encrypted_key = encrypt_key(ed25519_key, password)
|
||||
>>> encrypted_key = encrypt_key(ed25519_key, password).encode('utf-8')
|
||||
>>> tuf.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key)
|
||||
True
|
||||
|
||||
|
|
@ -1111,8 +1117,9 @@ def encrypt_key(key_object, password):
|
|||
if _GENERAL_CRYPTO_LIBRARY == 'pycrypto':
|
||||
encrypted_key = \
|
||||
tuf.pycrypto_keys.encrypt_key(key_object, password)
|
||||
|
||||
else:
|
||||
|
||||
# check_crypto_libraries() should have fully verified _GENERAL_CRYPTO_LIBRARY.
|
||||
else: # pragma: no cover
|
||||
message = 'Invalid crypto library: '+repr(_GENERAL_CRYPTO_LIBRARY)+'.'
|
||||
raise tuf.UnsupportedLibraryError(message)
|
||||
|
||||
|
|
@ -1147,7 +1154,7 @@ def decrypt_key(encrypted_key, passphrase):
|
|||
>>> ed25519_key = generate_ed25519_key()
|
||||
>>> password = 'secret'
|
||||
>>> encrypted_key = encrypt_key(ed25519_key, password)
|
||||
>>> decrypted_key = decrypt_key(encrypted_key, password)
|
||||
>>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), password)
|
||||
>>> tuf.formats.ANYKEY_SCHEMA.matches(decrypted_key)
|
||||
True
|
||||
>>> decrypted_key == ed25519_key
|
||||
|
|
@ -1209,7 +1216,8 @@ def decrypt_key(encrypted_key, passphrase):
|
|||
key_object = \
|
||||
tuf.pycrypto_keys.decrypt_key(encrypted_key, passphrase)
|
||||
|
||||
else:
|
||||
# check_crypto_libraries() should have fully verified _GENERAL_CRYPTO_LIBRARY.
|
||||
else: # pragma: no cover
|
||||
message = 'Invalid crypto library: '+repr(_GENERAL_CRYPTO_LIBRARY)+'.'
|
||||
raise tuf.UnsupportedLibraryError(message)
|
||||
|
||||
|
|
@ -1278,7 +1286,7 @@ def create_rsa_encrypted_pem(private_key, passphrase):
|
|||
|
||||
# Raise 'tuf.UnsupportedLibraryError' if the following libraries, specified in
|
||||
# 'tuf.conf', are unsupported or unavailable:
|
||||
# 'tuf.conf.RSA_CRYPTO_LIBRARY' and 'tuf.conf.GENERAL_CRYPTO_LIBRARY'.
|
||||
# 'tuf.conf.GENERAL_CRYPTO_LIBRARY' and 'tuf.conf.RSA_CRYPTO_LIBRARY'.
|
||||
check_crypto_libraries(['rsa', 'general'])
|
||||
|
||||
encrypted_pem = None
|
||||
|
|
@ -1290,8 +1298,9 @@ def create_rsa_encrypted_pem(private_key, passphrase):
|
|||
if _RSA_CRYPTO_LIBRARY == 'pycrypto':
|
||||
encrypted_pem = \
|
||||
tuf.pycrypto_keys.create_rsa_encrypted_pem(private_key, passphrase)
|
||||
|
||||
else:
|
||||
|
||||
# check_crypto_libraries() should have fully verified _RSA_CRYPTO_LIBRARY.
|
||||
else: # pragma: no cover
|
||||
message = 'Invalid crypto library: '+repr(_RSA_CRYPTO_LIBRARY)+'.'
|
||||
raise tuf.UnsupportedLibraryError(message)
|
||||
|
||||
|
|
|
|||
11
tuf/log.py
11
tuf/log.py
|
|
@ -56,6 +56,13 @@
|
|||
http://docs.python.org/2/howto/logging-cookbook.html
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
|
@ -312,7 +319,7 @@ def add_console_handler(log_level=_DEFAULT_CONSOLE_LOG_LEVEL):
|
|||
logger.debug('Added a console handler.')
|
||||
|
||||
else:
|
||||
logger.warn('We already have a console handler.')
|
||||
logger.warning('We already have a console handler.')
|
||||
|
||||
|
||||
|
||||
|
|
@ -346,4 +353,4 @@ def remove_console_handler():
|
|||
logger.debug('Removed a console handler.')
|
||||
|
||||
else:
|
||||
logger.warn('We do not have a console handler.')
|
||||
logger.warning('We do not have a console handler.')
|
||||
|
|
|
|||
|
|
@ -3,26 +3,34 @@
|
|||
mirrors.py
|
||||
|
||||
<Author>
|
||||
Konstantin Andrianov
|
||||
Konstantin Andrianov.
|
||||
Derived from original mirrors.py written by Geremy Condra.
|
||||
|
||||
<Started>
|
||||
March 12, 2012
|
||||
March 12, 2012.
|
||||
|
||||
<Copyright>
|
||||
See LICENSE for licensing information.
|
||||
|
||||
<Purpose>
|
||||
To extract a list of mirror urls corresponding to the file type and
|
||||
the location of the file with respect to the base url.
|
||||
Extract a list of mirror urls corresponding to the file type and the location
|
||||
of the file with respect to the base url.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import urllib
|
||||
|
||||
import tuf
|
||||
import tuf.util
|
||||
import tuf.formats
|
||||
import tuf._vendor.six as six
|
||||
|
||||
# The type of file to be downloaded from a repository. The
|
||||
# 'get_list_of_mirrors' function supports these file types.
|
||||
|
|
@ -74,6 +82,7 @@ def get_list_of_mirrors(file_type, file_path, mirrors_dict):
|
|||
tuf.formats.MIRRORDICT_SCHEMA.check_match(mirrors_dict)
|
||||
tuf.formats.NAME_SCHEMA.check_match(file_type)
|
||||
|
||||
# Verify 'file_type' is supported.
|
||||
if file_type not in _SUPPORTED_FILE_TYPES:
|
||||
message = 'Invalid file_type argument. '+ \
|
||||
'Supported file types: '+repr(_SUPPORTED_FILE_TYPES)
|
||||
|
|
@ -87,29 +96,26 @@ def get_list_of_mirrors(file_type, file_path, mirrors_dict):
|
|||
in_confined_directory = tuf.util.file_in_confined_directories
|
||||
|
||||
list_of_mirrors = []
|
||||
for mirror_name, mirror_info in mirrors_dict.items():
|
||||
for mirror_name, mirror_info in six.iteritems(mirrors_dict):
|
||||
if file_type == 'meta':
|
||||
base = mirror_info['url_prefix']+'/'+mirror_info['metadata_path']
|
||||
|
||||
elif file_type == 'target':
|
||||
|
||||
# 'file_type' == 'target'. 'file_type' should have been verified to contain
|
||||
# a supported string value above (either 'meta' or 'target').
|
||||
else:
|
||||
targets_path = mirror_info['targets_path']
|
||||
full_filepath = os.path.join(targets_path, file_path)
|
||||
if not in_confined_directory(full_filepath,
|
||||
mirror_info['confined_target_dirs']):
|
||||
continue
|
||||
base = mirror_info['url_prefix']+'/'+mirror_info['targets_path']
|
||||
|
||||
else:
|
||||
message = repr(file_type)+' is not a supported file type. '+ \
|
||||
'Supported file types: '+repr(_SUPPORTED_FILE_TYPES)
|
||||
raise tuf.Error(message)
|
||||
|
||||
# urllib.quote(string) replaces special characters in string using the %xx
|
||||
# escape. This is done to avoid parsing issues of the URL on the server
|
||||
# side. Do *NOT* pass URLs with Unicode characters without first encoding
|
||||
# the URL as UTF-8. We need a long-term solution with #61.
|
||||
# http://bugs.python.org/issue1712522
|
||||
file_path = urllib.quote(file_path)
|
||||
file_path = six.moves.urllib.parse.quote(file_path)
|
||||
url = base + '/' + file_path.lstrip(os.sep)
|
||||
list_of_mirrors.append(url)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
pycrypto_keys.py
|
||||
|
|
@ -44,6 +46,14 @@
|
|||
Derivation Function 1 (PBKF1) + MD5.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import binascii
|
||||
import json
|
||||
|
|
@ -205,7 +215,7 @@ def generate_rsa_public_and_private(bits=_DEFAULT_RSA_KEY_BITS):
|
|||
rsa_pubkey = rsa_key_object.publickey()
|
||||
public = rsa_pubkey.exportKey(format='PEM')
|
||||
|
||||
return public, private
|
||||
return public.decode(), private.decode()
|
||||
|
||||
|
||||
|
||||
|
|
@ -224,13 +234,13 @@ def create_rsa_signature(private_key, data):
|
|||
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'
|
||||
>>> data = 'The quick brown fox jumps over the lazy dog'.encode('utf-8')
|
||||
>>> signature, method = create_rsa_signature(private, data)
|
||||
>>> tuf.formats.NAME_SCHEMA.matches(method)
|
||||
True
|
||||
>>> method == 'RSASSA-PSS'
|
||||
True
|
||||
>>> tuf.formats.PYCRYPTOSIGNATURE_SCHEMA.matches(method)
|
||||
>>> tuf.formats.PYCRYPTOSIGNATURE_SCHEMA.matches(signature)
|
||||
True
|
||||
|
||||
<Arguments>
|
||||
|
|
@ -267,8 +277,8 @@ def create_rsa_signature(private_key, data):
|
|||
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
|
||||
# key is a NULL string if unset. Although it may be clearer to explicitly
|
||||
# check that 'private_key' is not '', we can/should check for a value and not
|
||||
# compare identities with the 'is' keyword. Up to this point 'private_key'
|
||||
# has variable size and can be an empty string.
|
||||
if len(private_key):
|
||||
|
|
@ -284,7 +294,7 @@ def create_rsa_signature(private_key, data):
|
|||
sha256_object = Crypto.Hash.SHA256.new(data)
|
||||
rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)
|
||||
|
||||
except (ValueError, IndexError, TypeError), e:
|
||||
except (ValueError, IndexError, TypeError) as e:
|
||||
message = 'Invalid private key or hash data: '+str(e)
|
||||
raise tuf.CryptoError(message)
|
||||
|
||||
|
|
@ -321,11 +331,11 @@ def verify_rsa_signature(signature, signature_method, public_key, data):
|
|||
and 'data' to complete the verification.
|
||||
|
||||
>>> public, private = generate_rsa_public_and_private(2048)
|
||||
>>> data = 'The quick brown fox jumps over the lazy dog'
|
||||
>>> data = b'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')
|
||||
>>> verify_rsa_signature(signature, method, public, b'bad_data')
|
||||
False
|
||||
|
||||
<Arguments>
|
||||
|
|
@ -383,7 +393,7 @@ def verify_rsa_signature(signature, signature_method, public_key, data):
|
|||
sha256_object = Crypto.Hash.SHA256.new(data)
|
||||
valid_signature = pkcs1_pss_verifier.verify(sha256_object, signature)
|
||||
|
||||
except (ValueError, IndexError, TypeError), e:
|
||||
except (ValueError, IndexError, TypeError) as e:
|
||||
message = 'The RSA signature could not be verified.'
|
||||
raise tuf.CryptoError(message)
|
||||
|
||||
|
|
@ -463,7 +473,7 @@ def create_rsa_encrypted_pem(private_key, passphrase):
|
|||
encrypted_pem = rsa_key_object.exportKey(format='PEM',
|
||||
passphrase=passphrase)
|
||||
|
||||
except (ValueError, IndexError, TypeError), e:
|
||||
except (ValueError, IndexError, TypeError) as e:
|
||||
message = 'An encrypted RSA key in PEM format cannot be generated: '+str(e)
|
||||
raise tuf.CryptoError(message)
|
||||
|
||||
|
|
@ -471,7 +481,7 @@ def create_rsa_encrypted_pem(private_key, passphrase):
|
|||
raise TypeError('The required private key is unset.')
|
||||
|
||||
|
||||
return encrypted_pem
|
||||
return encrypted_pem.decode()
|
||||
|
||||
|
||||
|
||||
|
|
@ -559,7 +569,7 @@ def create_rsa_public_and_private_from_encrypted_pem(encrypted_pem, passphrase):
|
|||
# (possibly because the passphrase is wrong)."
|
||||
# If the passphrase is incorrect, PyCrypto returns: "RSA key format is not
|
||||
# supported".
|
||||
except (ValueError, IndexError, TypeError), e:
|
||||
except (ValueError, IndexError, TypeError) as e:
|
||||
message = 'RSA (public, private) tuple cannot be generated from the'+\
|
||||
' encrypted PEM string: '+str(e)
|
||||
# Raise 'tuf.CryptoError' and PyCrypto's exception message. Avoid
|
||||
|
|
@ -580,7 +590,7 @@ def create_rsa_public_and_private_from_encrypted_pem(encrypted_pem, passphrase):
|
|||
message = 'The public and private keys cannot be exported in PEM format.'
|
||||
raise tuf.CryptoError(message)
|
||||
|
||||
return public, private
|
||||
return public.decode(), private.decode()
|
||||
|
||||
|
||||
|
||||
|
|
@ -616,7 +626,7 @@ def encrypt_key(key_object, password):
|
|||
'1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}}
|
||||
>>> passphrase = 'secret'
|
||||
>>> encrypted_key = encrypt_key(ed25519_key, passphrase)
|
||||
>>> tuf.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key)
|
||||
>>> tuf.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key.encode('utf-8'))
|
||||
True
|
||||
|
||||
<Arguments>
|
||||
|
|
@ -706,7 +716,7 @@ def decrypt_key(encrypted_key, password):
|
|||
'1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}}
|
||||
>>> passphrase = 'secret'
|
||||
>>> encrypted_key = encrypt_key(ed25519_key, passphrase)
|
||||
>>> decrypted_key = decrypt_key(encrypted_key, passphrase)
|
||||
>>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), passphrase)
|
||||
>>> tuf.formats.ED25519KEY_SCHEMA.matches(decrypted_key)
|
||||
True
|
||||
>>> decrypted_key == ed25519_key
|
||||
|
|
@ -751,11 +761,11 @@ def decrypt_key(encrypted_key, password):
|
|||
|
||||
# Decrypt 'encrypted_key', using 'password' (and additional key derivation
|
||||
# data like salts and password iterations) to re-derive the decryption key.
|
||||
json_data = _decrypt(encrypted_key, password)
|
||||
json_data = _decrypt(encrypted_key.decode('utf-8'), password)
|
||||
|
||||
# Raise 'tuf.Error' if 'json_data' cannot be deserialized to a valid
|
||||
# 'tuf.formats.ANYKEY_SCHEMA' key object.
|
||||
key_object = tuf.util.load_json_string(json_data)
|
||||
key_object = tuf.util.load_json_string(json_data.decode())
|
||||
|
||||
return key_object
|
||||
|
||||
|
|
@ -836,7 +846,7 @@ def _encrypt(key_data, derived_key_information):
|
|||
# encryption.
|
||||
iv = Crypto.Random.new().read(16)
|
||||
stateful_counter_128bit_blocks = Crypto.Util.Counter.new(128,
|
||||
initial_value=long(iv.encode('hex'), 16))
|
||||
initial_value=int(binascii.hexlify(iv), 16))
|
||||
symmetric_key = derived_key_information['derived_key']
|
||||
aes_cipher = Crypto.Cipher.AES.new(symmetric_key,
|
||||
Crypto.Cipher.AES.MODE_CTR,
|
||||
|
|
@ -851,7 +861,7 @@ def _encrypt(key_data, derived_key_information):
|
|||
# what circumstances. PyCrypto example given is to call encrypt() without
|
||||
# checking for exceptions. Avoid propogating the exception trace and only
|
||||
# raise 'tuf.CryptoError', along with the cause of encryption failure.
|
||||
except (ValueError, IndexError, TypeError), e:
|
||||
except (ValueError, IndexError, TypeError) as e:
|
||||
message = 'The key data cannot be encrypted: '+str(e)
|
||||
raise tuf.CryptoError(message)
|
||||
|
||||
|
|
@ -862,7 +872,7 @@ def _encrypt(key_data, derived_key_information):
|
|||
hmac_object = Crypto.Hash.HMAC.new(symmetric_key, ciphertext,
|
||||
Crypto.Hash.SHA256)
|
||||
hmac = hmac_object.hexdigest()
|
||||
|
||||
|
||||
# Store the number of PBKDF2 iterations used to derive the symmetric key so
|
||||
# that the decryption routine can regenerate the symmetric key successfully.
|
||||
# The pbkdf2 iterations are allowed to vary for the keys loaded and saved.
|
||||
|
|
@ -873,11 +883,11 @@ def _encrypt(key_data, derived_key_information):
|
|||
# '_ENCRYPTION_DELIMITER' to make extraction easier. This delimiter is
|
||||
# arbitrarily chosen and should not occur in the hexadecimal representations
|
||||
# of the fields it is separating.
|
||||
return binascii.hexlify(salt) + _ENCRYPTION_DELIMITER + \
|
||||
binascii.hexlify(str(iterations)) + _ENCRYPTION_DELIMITER + \
|
||||
binascii.hexlify(hmac) + _ENCRYPTION_DELIMITER + \
|
||||
binascii.hexlify(iv) + _ENCRYPTION_DELIMITER + \
|
||||
binascii.hexlify(ciphertext)
|
||||
return binascii.hexlify(salt).decode() + _ENCRYPTION_DELIMITER + \
|
||||
str(iterations) + _ENCRYPTION_DELIMITER + \
|
||||
hmac + _ENCRYPTION_DELIMITER + \
|
||||
binascii.hexlify(iv).decode() + _ENCRYPTION_DELIMITER + \
|
||||
binascii.hexlify(ciphertext).decode()
|
||||
|
||||
|
||||
|
||||
|
|
@ -904,11 +914,10 @@ def _decrypt(file_contents, password):
|
|||
raise tuf.CryptoError('Invalid encrypted file.')
|
||||
|
||||
# Ensure we have the expected raw data for the delimited cryptographic data.
|
||||
salt = binascii.unhexlify(salt)
|
||||
iterations = int(binascii.unhexlify(iterations))
|
||||
hmac = binascii.unhexlify(hmac)
|
||||
iv = binascii.unhexlify(iv)
|
||||
ciphertext = binascii.unhexlify(ciphertext)
|
||||
salt = binascii.unhexlify(salt.encode('utf-8'))
|
||||
iterations = int(iterations)
|
||||
iv = binascii.unhexlify(iv.encode('utf-8'))
|
||||
ciphertext = binascii.unhexlify(ciphertext.encode('utf-8'))
|
||||
|
||||
# Generate derived key from 'password'. The salt and iterations are specified
|
||||
# so that the expected derived key is regenerated correctly. Discard the old
|
||||
|
|
@ -928,7 +937,7 @@ def _decrypt(file_contents, password):
|
|||
# The following decryption routine assumes 'ciphertext' was encrypted with
|
||||
# AES-256.
|
||||
stateful_counter_128bit_blocks = Crypto.Util.Counter.new(128,
|
||||
initial_value=long(iv.encode('hex'), 16))
|
||||
initial_value=int(binascii.hexlify(iv), 16))
|
||||
aes_cipher = Crypto.Cipher.AES.new(derived_key,
|
||||
Crypto.Cipher.AES.MODE_CTR,
|
||||
counter=stateful_counter_128bit_blocks)
|
||||
|
|
@ -939,7 +948,9 @@ def _decrypt(file_contents, password):
|
|||
# what circumstances. PyCrypto example given is to call decrypt() without
|
||||
# checking for exceptions. Avoid propogating the exception trace and only
|
||||
# raise 'tuf.CryptoError', along with the cause of decryption failure.
|
||||
except (ValueError, IndexError, TypeError), e:
|
||||
# Note: decryption failure, due to malicious ciphertext, should not occur here
|
||||
# if the hmac check above passed.
|
||||
except (ValueError, IndexError, TypeError) as e: # pragma: no cover
|
||||
raise tuf.CryptoError('Decryption failed: '+str(e))
|
||||
|
||||
return key_plaintext
|
||||
|
|
|
|||
2231
tuf/repository_lib.py
Executable file
2231
tuf/repository_lib.py
Executable file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -35,12 +35,21 @@
|
|||
optional.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import copy
|
||||
|
||||
import tuf
|
||||
import tuf.formats
|
||||
import tuf.log
|
||||
import tuf._vendor.six as six
|
||||
|
||||
# See 'tuf.log' to learn how logging is handled in TUF.
|
||||
logger = logging.getLogger('tuf.roledb')
|
||||
|
|
@ -89,7 +98,7 @@ 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():
|
||||
for rolename, roleinfo in six.iteritems(root_metadata['roles']):
|
||||
if rolename == 'root':
|
||||
roleinfo['version'] = root_metadata['version']
|
||||
roleinfo['expires'] = root_metadata['expires']
|
||||
|
|
@ -104,7 +113,7 @@ def create_roledb_from_root_metadata(root_metadata):
|
|||
try:
|
||||
add_role(rolename, roleinfo)
|
||||
# tuf.Error raised if the parent role of 'rolename' does not exist.
|
||||
except tuf.Error, e:
|
||||
except tuf.Error as e:
|
||||
logger.error(e)
|
||||
raise
|
||||
|
||||
|
|
@ -475,7 +484,7 @@ def get_rolenames():
|
|||
A list of rolenames.
|
||||
"""
|
||||
|
||||
return _roledb_dict.keys()
|
||||
return list(_roledb_dict.keys())
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
126
tuf/schema.py
126
tuf/schema.py
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
<Program Name>
|
||||
schema.py
|
||||
|
|
@ -40,12 +42,19 @@
|
|||
can be found in 'formats.py'.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
import tuf
|
||||
|
||||
import tuf._vendor.six as six
|
||||
|
||||
class Schema:
|
||||
"""
|
||||
|
|
@ -141,7 +150,7 @@ class String(Schema):
|
|||
"""
|
||||
|
||||
def __init__(self, string):
|
||||
if not isinstance(string, basestring):
|
||||
if not isinstance(string, six.string_types):
|
||||
raise tuf.FormatError('Expected a string but got '+repr(string))
|
||||
|
||||
self._string = string
|
||||
|
|
@ -188,13 +197,51 @@ def __init__(self):
|
|||
|
||||
|
||||
def check_match(self, object):
|
||||
if not isinstance(object, basestring):
|
||||
if not isinstance(object, six.string_types):
|
||||
raise tuf.FormatError('Expected a string but got '+repr(object))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class AnyBytes(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
Matches any byte string, but not a non-byte object. This schema
|
||||
can be viewed as the Any() schema applied to byte strings, but an
|
||||
additional check is performed to ensure only strings are considered.
|
||||
|
||||
Supported methods include
|
||||
matches(): returns a Boolean result.
|
||||
check_match(): raises 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> schema = AnyBytes()
|
||||
>>> schema.matches(b'')
|
||||
True
|
||||
>>> schema.matches(b'a string')
|
||||
True
|
||||
>>> schema.matches(['a'])
|
||||
False
|
||||
>>> schema.matches(3)
|
||||
False
|
||||
>>> schema.matches({})
|
||||
False
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
def check_match(self, object):
|
||||
if not isinstance(object, six.binary_type):
|
||||
raise tuf.FormatError('Expected a byte string but got '+repr(object))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class LengthString(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
|
|
@ -217,7 +264,7 @@ class LengthString(Schema):
|
|||
"""
|
||||
|
||||
def __init__(self, length):
|
||||
if isinstance(length, bool) or not isinstance(length, (int, long)):
|
||||
if isinstance(length, bool) or not isinstance(length, six.integer_types):
|
||||
# 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.')
|
||||
|
|
@ -226,7 +273,7 @@ def __init__(self, length):
|
|||
|
||||
|
||||
def check_match(self, object):
|
||||
if not isinstance(object, basestring):
|
||||
if not isinstance(object, six.string_types):
|
||||
raise tuf.FormatError('Expected a string but got '+repr(object))
|
||||
|
||||
if len(object) != self._string_length:
|
||||
|
|
@ -237,6 +284,48 @@ def check_match(self, object):
|
|||
|
||||
|
||||
|
||||
class LengthBytes(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
Matches any Bytes of a specified length. The argument object must be either
|
||||
a str() in Python 2, or bytes() in Python 3. At instantiation, the bytes
|
||||
length is set and any future comparisons are checked against this internal
|
||||
bytes value length.
|
||||
|
||||
Supported methods include
|
||||
matches(): returns a Boolean result.
|
||||
check_match(): raises 'tuf.FormatError' on a mismatch.
|
||||
|
||||
<Example Use>
|
||||
|
||||
>>> schema = LengthBytes(5)
|
||||
>>> schema.matches(b'Hello')
|
||||
True
|
||||
>>> schema.matches(b'Hi')
|
||||
False
|
||||
"""
|
||||
|
||||
def __init__(self, length):
|
||||
if isinstance(length, bool) or not isinstance(length, six.integer_types):
|
||||
# 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._bytes_length = length
|
||||
|
||||
|
||||
def check_match(self, object):
|
||||
if not isinstance(object, six.binary_type):
|
||||
raise tuf.FormatError('Expected a byte but got '+repr(object))
|
||||
|
||||
if len(object) != self._bytes_length:
|
||||
raise tuf.FormatError('Expected a byte of length '+
|
||||
repr(self._bytes_length))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class OneOf(Schema):
|
||||
"""
|
||||
<Purpose>
|
||||
|
|
@ -401,7 +490,7 @@ class ListOf(Schema):
|
|||
False
|
||||
"""
|
||||
|
||||
def __init__(self, schema, min_count=0, max_count=sys.maxint, list_name='list'):
|
||||
def __init__(self, schema, min_count=0, max_count=sys.maxsize, list_name='list'):
|
||||
"""
|
||||
<Purpose>
|
||||
Create a new ListOf schema.
|
||||
|
|
@ -433,7 +522,7 @@ def check_match(self, object):
|
|||
for item in object:
|
||||
try:
|
||||
self._schema.check_match(item)
|
||||
except tuf.FormatError, e:
|
||||
except tuf.FormatError as e:
|
||||
raise tuf.FormatError(str(e)+' in '+repr(self._list_name))
|
||||
|
||||
# Raise exception if the number of items in the list is
|
||||
|
|
@ -465,8 +554,6 @@ class Integer(Schema):
|
|||
True
|
||||
>>> schema.matches(False)
|
||||
False
|
||||
>>> schema.matches(0L)
|
||||
True
|
||||
>>> schema.matches('a string')
|
||||
False
|
||||
>>> Integer(lo=10, hi=30).matches(25)
|
||||
|
|
@ -475,7 +562,7 @@ class Integer(Schema):
|
|||
False
|
||||
"""
|
||||
|
||||
def __init__(self, lo= -sys.maxint, hi=sys.maxint):
|
||||
def __init__(self, lo = -2147483648, hi = 2147483647):
|
||||
"""
|
||||
<Purpose>
|
||||
Create a new Integer schema.
|
||||
|
|
@ -490,7 +577,7 @@ def __init__(self, lo= -sys.maxint, hi=sys.maxint):
|
|||
|
||||
|
||||
def check_match(self, object):
|
||||
if isinstance(object, bool) or not isinstance(object, (int, long)):
|
||||
if isinstance(object, bool) or not isinstance(object, six.integer_types):
|
||||
# We need to check for bool as a special case, since bool
|
||||
# is for historical reasons a subtype of int.
|
||||
raise tuf.FormatError('Got '+repr(object)+' instead of an integer.')
|
||||
|
|
@ -556,7 +643,7 @@ def check_match(self, object):
|
|||
if not isinstance(object, dict):
|
||||
raise tuf.FormatError('Expected a dict but got '+repr(object))
|
||||
|
||||
for key, value in object.items():
|
||||
for key, value in six.iteritems(object):
|
||||
self._key_schema.check_match(key)
|
||||
self._value_schema.check_match(value)
|
||||
|
||||
|
|
@ -643,12 +730,12 @@ def __init__(self, object_name='object', **required):
|
|||
"""
|
||||
|
||||
# Ensure valid arguments.
|
||||
for key, schema in required.items():
|
||||
for key, schema in six.iteritems(required):
|
||||
if not isinstance(schema, Schema):
|
||||
raise tuf.FormatError('Expected Schema but got '+repr(schema))
|
||||
|
||||
self._object_name = object_name
|
||||
self._required = required.items()
|
||||
self._required = list(required.items())
|
||||
|
||||
|
||||
def check_match(self, object):
|
||||
|
|
@ -672,7 +759,7 @@ def check_match(self, object):
|
|||
else:
|
||||
try:
|
||||
schema.check_match(item)
|
||||
except tuf.FormatError, e:
|
||||
except tuf.FormatError as e:
|
||||
raise tuf.FormatError(str(e)+' in '+self._object_name+'.'+key)
|
||||
|
||||
|
||||
|
|
@ -827,8 +914,9 @@ def __init__(self, pattern=None, modifiers=0, re_object=None, re_name=None):
|
|||
re_name: Identifier for the regular expression object.
|
||||
"""
|
||||
|
||||
if not isinstance(pattern, basestring):
|
||||
raise tuf.FormatError(repr(pattern)+' is not a string.')
|
||||
if not isinstance(pattern, six.string_types):
|
||||
if pattern is not None:
|
||||
raise tuf.FormatError(repr(pattern)+' is not a string.')
|
||||
|
||||
if re_object is None:
|
||||
if pattern is None:
|
||||
|
|
@ -848,13 +936,11 @@ def __init__(self, pattern=None, modifiers=0, re_object=None, re_name=None):
|
|||
|
||||
|
||||
def check_match(self, object):
|
||||
if not isinstance(object, basestring) or not self._re_object.match(object):
|
||||
if not isinstance(object, six.string_types) or not self._re_object.match(object):
|
||||
raise tuf.FormatError(repr(object)+' did not match '+repr(self._re_name))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# The interactive sessions of the documentation strings can
|
||||
# be tested by running schema.py as a standalone module.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,14 @@
|
|||
is also a function for that.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import tuf
|
||||
import tuf.formats
|
||||
import tuf.keydb
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@
|
|||
Specifically, Modified_TestCase is a derived class from unittest.TestCase.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
|
@ -108,7 +116,7 @@ def _destroy_temp_file():
|
|||
def make_temp_data_file(self, suffix='', directory=None, data = 'junk data'):
|
||||
"""Returns an absolute path of a temp file containing data."""
|
||||
temp_file_path = self.make_temp_file(suffix=suffix, directory=directory)
|
||||
temp_file = open(temp_file_path, 'wb')
|
||||
temp_file = open(temp_file_path, 'wt')
|
||||
temp_file.write(data)
|
||||
temp_file.close()
|
||||
return temp_file_path
|
||||
|
|
|
|||
63
tuf/util.py
63
tuf/util.py
|
|
@ -18,6 +18,14 @@
|
|||
TempFile class that generates a file-like object for temporary storage, etc.
|
||||
"""
|
||||
|
||||
# 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
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
import gzip
|
||||
|
|
@ -29,6 +37,7 @@
|
|||
import tuf.hash
|
||||
import tuf.conf
|
||||
import tuf.formats
|
||||
import tuf._vendor.six as six
|
||||
|
||||
# The algorithm used by the repository to generate the digests of the
|
||||
# target filepaths, which are included in metadata files and may be prepended
|
||||
|
|
@ -54,8 +63,9 @@ def _default_temporary_directory(self, prefix):
|
|||
"""__init__ helper."""
|
||||
try:
|
||||
self.temporary_file = tempfile.NamedTemporaryFile(prefix=prefix)
|
||||
except OSError, err:
|
||||
logger.critical('Temp file in '+temp_dir+'failed: '+repr(err))
|
||||
|
||||
except OSError as err: # pragma: no cover
|
||||
logger.critical('Cannot create a system temporary directory: '+repr(err))
|
||||
raise tuf.Error(err)
|
||||
|
||||
|
||||
|
|
@ -77,22 +87,26 @@ def __init__(self, prefix='tuf_temp_'):
|
|||
"""
|
||||
|
||||
self._compression = None
|
||||
|
||||
# If compression is set then the original file is saved in 'self._orig_file'.
|
||||
self._orig_file = None
|
||||
temp_dir = tuf.conf.temporary_directory
|
||||
if temp_dir is not None and isinstance(temp_dir, str):
|
||||
if temp_dir is not None and tuf.formats.PATH_SCHEMA.matches(temp_dir):
|
||||
try:
|
||||
self.temporary_file = tempfile.NamedTemporaryFile(prefix=prefix,
|
||||
dir=temp_dir)
|
||||
except OSError, err:
|
||||
logger.error('Temp file in '+temp_dir+' failed: '+repr(err))
|
||||
except OSError as err:
|
||||
logger.error('Temp file in ' + temp_dir + ' failed: '+repr(err))
|
||||
logger.error('Will attempt to use system default temp dir.')
|
||||
self._default_temporary_directory(prefix)
|
||||
|
||||
else:
|
||||
self._default_temporary_directory(prefix)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_compressed_length(self):
|
||||
"""
|
||||
<Purpose>
|
||||
|
|
@ -134,6 +148,8 @@ def flush(self):
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
def read(self, size=None):
|
||||
"""
|
||||
<Purpose>
|
||||
|
|
@ -155,14 +171,19 @@ def read(self, size=None):
|
|||
self.temporary_file.seek(0)
|
||||
data = self.temporary_file.read()
|
||||
self.temporary_file.seek(0)
|
||||
|
||||
return data
|
||||
|
||||
else:
|
||||
if not (isinstance(size, int) and size > 0):
|
||||
raise tuf.FormatError
|
||||
|
||||
return self.temporary_file.read(size)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def write(self, data, auto_flush=True):
|
||||
"""
|
||||
<Purpose>
|
||||
|
|
@ -216,6 +237,8 @@ def move(self, destination_path):
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
def seek(self, *args):
|
||||
"""
|
||||
<Purpose>
|
||||
|
|
@ -239,6 +262,8 @@ def seek(self, *args):
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
def decompress_temp_file_object(self, compression):
|
||||
"""
|
||||
<Purpose>
|
||||
|
|
@ -293,7 +318,7 @@ def decompress_temp_file_object(self, compression):
|
|||
try:
|
||||
self.temporary_file = gzip.GzipFile(fileobj=self.temporary_file,
|
||||
mode='rb')
|
||||
except Exception, exception:
|
||||
except Exception as exception:
|
||||
raise tuf.DecompressionError(exception)
|
||||
|
||||
|
||||
|
|
@ -415,8 +440,10 @@ def ensure_parent_dir(filename):
|
|||
|
||||
# Split 'filename' into head and tail, check if head exists.
|
||||
directory = os.path.split(filename)[0]
|
||||
|
||||
if directory and not os.path.exists(directory):
|
||||
os.makedirs(directory, 0700)
|
||||
# mode = 'rwx------'. 448 (decimal) is 700 in octal.
|
||||
os.makedirs(directory, 448)
|
||||
|
||||
|
||||
|
||||
|
|
@ -508,7 +535,7 @@ def find_delegated_role(roles, delegated_role):
|
|||
# The index of a role, if any, with the same name.
|
||||
role_index = None
|
||||
|
||||
for index in xrange(len(roles)):
|
||||
for index in six.moves.xrange(len(roles)):
|
||||
role = roles[index]
|
||||
name = role.get('name')
|
||||
|
||||
|
|
@ -770,19 +797,10 @@ def get_target_hash(target_filepath):
|
|||
|
||||
# Calculate the hash of the filepath to determine which bin to find the
|
||||
# target. The client currently assumes the repository uses
|
||||
# 'HASH_FUNCTION' to generate hashes.
|
||||
# 'HASH_FUNCTION' to generate hashes and 'utf-8'.
|
||||
digest_object = tuf.hash.digest(HASH_FUNCTION)
|
||||
|
||||
try:
|
||||
digest_object.update(target_filepath)
|
||||
|
||||
except UnicodeEncodeError:
|
||||
# Sometimes, there are Unicode characters in target paths. We assume a
|
||||
# UTF-8 encoding and try to hash that.
|
||||
digest_object = tuf.hash.digest(HASH_FUNCTION)
|
||||
encoded_target_filepath = target_filepath.encode('utf-8')
|
||||
digest_object.update(encoded_target_filepath)
|
||||
|
||||
encoded_target_filepath = target_filepath.encode('utf-8')
|
||||
digest_object.update(encoded_target_filepath)
|
||||
target_filepath_hash = digest_object.hexdigest()
|
||||
|
||||
return target_filepath_hash
|
||||
|
|
@ -899,7 +917,7 @@ def load_json_file(filepath):
|
|||
# The file is mostly likely gzipped.
|
||||
if filepath.endswith('.gz'):
|
||||
logger.debug('gzip.open('+str(filepath)+')')
|
||||
fileobject = gzip.open(filepath)
|
||||
fileobject = six.StringIO(gzip.open(filepath).read().decode('utf-8'))
|
||||
|
||||
else:
|
||||
logger.debug('open('+str(filepath)+')')
|
||||
|
|
@ -908,11 +926,12 @@ def load_json_file(filepath):
|
|||
try:
|
||||
deserialized_object = json.load(fileobject)
|
||||
|
||||
except ValueError, TypeError:
|
||||
except (ValueError, TypeError) as e:
|
||||
message = 'Cannot deserialize to a Python object: '+repr(filepath)
|
||||
raise tuf.Error(message)
|
||||
|
||||
else:
|
||||
fileobject.close()
|
||||
return deserialized_object
|
||||
|
||||
finally:
|
||||
|
|
|
|||
Loading…
Reference in a new issue