Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addition of ASN.1 format with BER/DER encoding to TUF #3

Closed
wants to merge 155 commits into from

Conversation

awwad
Copy link
Owner

@awwad awwad commented Feb 27, 2017

The motivation for this format and encoding is to deliver data to a low-resource ECU (Electrical Control Unit, a general term for most of the electronic components in an automobile). Sam Lauzon at UMTRI has produced a client that is partially compliant with the Partial Verification model of Uptane's update validation, but expects this class of metadata.

Using this code, the Uptane demo and reference implementation now operate using ASN.1/DER (ref1, ref2) or Python dictionary/JSON metadata, whichever is preferred. The default is now DER.

Comments on development are largely here in the Uptane PR.

This pull request is not expected to be merged (as there is no need to merge into pinning_w_dirtywrites); it is specifically here for review of changes made in these commits.

Vlad, don't panic at the size.... Try to just focus on the core TUF changes and let me know how you feel about them? Please do read the linked thread, though, for the limitations (delegations, RSA keys, etc.)

@@ -0,0 +1,54 @@
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metadata files used by the tests are usually saved in tests/repository_data/repository/*. Is there a reason why they are saved in repository_data/*?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The files in tests/repository_data/repository/ constitute the metadata files that represent a single consistent repository. These new files are not related to that specific repository. I didn't want them to be confused, and since this is temporary, tests/repository_data felt adequate. I would like ultimately to use test files from several repositories, each in a directory in repository_data corresponding to their repository name, with all metadata files required for that repository, so that we can test them in full with the map file.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Do you think it would be better to just stick them in with the 'repository' stuff?)

Copy link
Collaborator

@vladimir-v-diaz vladimir-v-diaz Feb 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of having separate directories (e.g., tests/repository_data/repository, tests/repository_data/director) to test the map file. In PR #430 separate directories are created on the client side, but multiple server instances (on different ports) are started over the same tests/repository_data/repository data, which would be more ideal.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correction: "which is not ideal... we should also have separate repositories on the server side"

tuf/sig.py Outdated
@@ -60,6 +60,21 @@ def get_signature_status(signable, role=None, repository_name='default'):
all the keys that are valid, invalid, unrecognized, unauthorized, or
generated using an unknown method.

PLEASE NOTE that when running TUF with DER metadata (setting
tuf.conf.METADATA_FORMAT == 'DER'), this function can only be called
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to code below, DER should be lower case.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks.

},
"roles": [
{
"backtrack": true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This attribute was renamed to "terminating". This set of metadata will have to be regenerated if merging with TUF's repository.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. One of sadly many things that will have to be adjusted when the merge happens.

@@ -0,0 +1,86 @@
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Root metadata is now always written as if if consistent snapshots = True. Therefore, expect to have 1.uptane_director_root.json, 2.uptane_director_root.json for this repository.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a change made after the fork occurred, and will be another thing to unite in the merge.

@@ -0,0 +1,26 @@
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this valid metadata? Not all metadata is listed in the snapshot metadata, at least as named on disk. For example, why don't I see targets_simpler.json listed anywhere?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

targets_simpler.json isn't part of a complete test repository's data, no; it's for an individual test prodding just targets.json.

The other files are parts of repositories and are prefixed with the repository of which they're a part:

  • uptane_director_root.json
  • uptane_director_snapshot.json
  • ...
  • uptane_mainrepo_root.json
  • ...

Should they remain (if we decide Uptane gets that kind of privilege - instead of generalizing a bit), we'd want them in folders in repository_data like this:

  • repository_data/uptane_director:
    • root.json
    • ...
  • repository_data/uptane_imagerepo:
    • root.json
    • ...
  • repository_data/repository: <--- may or may not want to rename to 'simple', 'default', etc.
    • root.json
    • ...
  • repository_data/der_repository:
    • root.der
    • ...

except Exception as exception:
raise tuf.InvalidMetadataJSONError(exception)

else:
# Ensure the loaded 'metadata_signable' is properly formatted. Raise
# 'tuf.FormatError' if not.
tuf.formats.check_signable_object_format(metadata_signable)
tuf.formats.check_signable_object_format(metadata_signable) # TODO: <~> This needs some way of knowing that it has a piece of DER-signed data so that it can re-encode 'signed' as DER before checking it.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of in-line comments seems excessive. According to our code style guidelines, they should be used sparingly: https://github.com/secure-systems-lab/code-style-guidelines#inline-comments

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. It's a habit I have for temporary comments that I need to resolve before the code reaches merge-worthy state. Same goes for the '<~>' mark: generally, these are things I do not want to allow in, and would grep for.


elif tuf.conf.METADATA_FORMAT == 'der':
written_metadata_content = asn1_codec.convert_signed_metadata_to_der(
metadata_signable) # TODO: <~> CURRENTLY WORKING HERE
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can remove this now.

written_metadata_content = asn1_codec.convert_signed_metadata_to_der(
metadata_signable) # TODO: <~> CURRENTLY WORKING HERE
else:
raise tuf.Error('Unsupported metadata format in configuration. Unable to '
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be helpful to state exactly which configuration file. tuf.conf.py? Note: It's now named tuf.settings.py

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. Fixed and will be in future push.

@@ -54,6 +54,10 @@
# be saved to 'LOG_FILENAME'
LOG_FILENAME = 'tuf.log'

# This is the format/encoding of metadata that TUF will use. The options are
# 'json' and 'der'. DER is an encoding of ASN.1 used for the Uptane project.
METADATA_FORMAT = 'json' # 'der'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect the format of all metadata on a repository to be equal? For instance, must it all be JSON, or all DER? No mixing of formats?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes; currently, the formats of all metadata files must be consistent.
They're all JSON or all DER. (Except the map (still pinned.json in this
fork) file, which is never signed and sent anywhere. That's just always
JSON in this codebase.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should verify with Justin that this is what we eventually want. I also made this assumption with YAML, but only because it was simpler to implement. If we eventually want to allow mixing of metadata formats, we'll have to modify the design a bit.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I told him that that was the direction I'd develop in - at any one time, all metadata would be in the same format. We didn't talk about what the eventual state would be, though.

def test_1_root_partial_convert(self):
# Test 1: only_signed conversion PyDict -> ASN1 BER of Root
partial_der_conversion_tester(
'tests/repository_data/repository/metadata/root.json', self)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the unit tests, we usually move repository data to a temporary directory and then work with it from there. First moving it to a temp directory prevent the original files from being changed in unexpected ways. If the repository data is only being read here, I guess it's okay to use the original files.

… some critical-todo markers; other minor comment clarifications
<Side Effects>
None.

<Return>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We moved util.py to securesystemslib, so this function should ideally return any DER file. In TUF's case, we'd most likely always use this function to fetch a file in tuf.formats.SIGNABLE_SCHEMA format.

Copy link
Owner Author

@awwad awwad Mar 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that's possible in the case of DER, unfortunately. You have to provide an ASN1 definition for the metadata in order to decode DER, and it's pretty rigid.

When you decode using pyasn1, you provide the expected ASN.1 class of the result. Here's a simple example, decoding a token (nonce-like things for the Timeserver-Primary-Secondary workflow):

decoded = pyasn1.codec.der.decoder.decode(
    token_der,
    uptane_asn1_definitions.Token())

# Decode the DER into an abstract ASN.1 representation of its data,
# then convert that into a basic Python dictionary representation of the
# data within.
#return asn1_codec.convert_signed_der_to_dersigned_json(der_data)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented line of code.

tuf/util.py Outdated
None.

<Returns>
Deserialized object. For example, a dictionary.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asn1_codec.convert_signed_der_to_dersigned_json() returns a tuf.formats.signable_schema object. Might be clearer to say that instead of just "dictionary". Consider the name asn1_codec.convert_signed_der_to_tuf_signable(), or something along those lines.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. You're right that the doc should be better. In the next round of pushes, it'll be:
    """
    Deserialized object, conforming to tuf.formats.SIGNABLE_SCHEMA.
    The signatures contained in the returned object, if any, will have been
    unchanged. If, for example, the signatures were over a DER object,
    they will remain that way, even though the 'signed' portion will no longer
    be in DER.
    """

"convert_signed_der_to_dersigned_json" is meant to indicate that the Python dictionary returned retains a signature over the DER format ("dersigned"). Perhaps it's too ambitious a name. :P

@@ -241,7 +244,7 @@ def __init__(self, updater_name):
# Load pinned.json, which is required per TAP #4 and determines which
# which targets should be sought from which repository(/ies).
self._load_pinned_metadata(os.path.join(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: In the pull request that implements TAP 4 (the map file), the path to the pinned file is given as an argument to MultiRepoUpdater(). See theupdateframework#430 (comment)

We can decide on which to approach to use once we merge.

awwad added 3 commits March 6, 2017 13:32
…r_file, and tuf.util.load_file. Removal of some extraneous comments. Removal of urgent flag on TODO in tuf.keys.verify_signature.
…itive; add _ to name of private function ensure_valid_metadata_type_for_asn1
# being assumed that the key values are hex strings ('f9ac1325...') but an
# RSA public key value is e.g. '-----BEGIN PUBLIC
# KEY------\nMIIBojANBgk...\n...'
def test_1_root_partial_convert(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these test cases fail with a PyAsn1Error exception. It seems to complain about an incompatible value in the keyid field of the signature.

======================================================================
ERROR: test_11_root_uptane_partial_convert (__main__.TestASN1Conversion)
Test 11: only_signed conversion PyDict -> ASN1 BER of Root
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/test_asn1_codec.py", line 66, in test_11_root_uptane_partial_convert
    'tests/repository_data/uptane_mainrepo_root.json', self)
  File "tests/test_asn1_codec.py", line 118, in partial_der_conversion_tester
    role_signable_pydict, only_signed=True)))
  File "/home/vlad/projects/vladforks/tmp/tuf/tuf/asn1_codec.py", line 248, in convert_signed_metadata_to_der
    asn_signed = relevant_asn_module.get_asn_signed(json_signed) # Python3 breaks here.
  File "/home/vlad/projects/vladforks/tmp/tuf/tuf/encoding/rootmetadata.py", line 26, in get_asn_signed
    snapshotPublicKeyid, targetsPublicKeyid, rootMetadata)
  File "/home/vlad/projects/vladforks/tmp/tuf/tuf/encoding/rootmetadata.py", line 123, in set_keys
    tag.tagFormatSimple, 1))
  File "/home/vlad/projects/vladforks/tmp/tuf/env/local/lib/python2.7/site-packages/pyasn1/type/univ.py", line 1945, in __setitem__
    self.setComponentByName(idx, value)
  File "/home/vlad/projects/vladforks/tmp/tuf/env/local/lib/python2.7/site-packages/pyasn1/type/univ.py", line 2037, in setComponentByName
    self._componentType.getPositionByName(name), value, verifyConstraints, matchTags, matchConstraints
  File "/home/vlad/projects/vladforks/tmp/tuf/env/local/lib/python2.7/site-packages/pyasn1/type/univ.py", line 2497, in setComponentByPosition
    raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, componentType))
PyAsn1Error: Component value is tag-incompatible: OctetString(tagSet=TagSet((), Tag(tagClass=128, tagFormat=0, tagId=1)), hexValue='94c836f0c45168f0a437eef0e487b910f58db4d462ae457b5730a4487130f290') vs OctetString(tagSet=TagSet((), Tag(tagClass=128, tagFormat=0, tagId=1)))

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's odd. Of the 11, only 2 fail for me (and why they do is documented). The other 9 succeed. You're running Python2, so it's not that.... Investigating.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which commit are you on?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commit 8440e82

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My installed Python programs...

PyNaCl==1.0.1
appdirs==1.4.2
argparse==1.2.1
cffi==1.7.0
cryptography==1.4
enum34==1.1.6
idna==2.4
ipaddress==1.0.18
iso8601==0.1.11
packaging==16.8
pluggy==0.4.0
py==1.4.32
pyasn1==0.2.3
pycparser==2.17
pycrypto==2.6.1
pyparsing==2.2.0
six==1.10.0
tox==2.6.0
-e [email protected]:awwad/tuf.git@8440e822589ff78319574199d99a97018d4c6b81#egg=tuf-origin/pinning_w_ber
virtualenv==15.1.0
wsgiref==0.1.2

Copy link
Collaborator

@vladimir-v-diaz vladimir-v-diaz Mar 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pulled in your latest commits and am now on commit 892c0bc

I still get the PyAsn1Error exception.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reproduced it in a fresh environment and I'm trying to figure out what the difference is. Thanks for pointing it out.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pinning_w_ber is based on pinning_w_dirtywrites. Could there be something in the base branch that's changed?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I have one failing and one performing as expected even with both of them referencing the exact same tuf install.

Copy link
Owner Author

@awwad awwad Mar 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/: They were using different pyasn1 versions after all. It turns out that something in pyasn1 0.2.3 (update from a week or two ago) breaks the conversion code we're using. So for now, I'll make sure the installation instructions specify pyasn1==0.2.2, which works.

awwad added 9 commits March 10, 2017 12:51
Some of the inherited code from an import from early uptane/asn1
has been removed.
Specifically, tuf/encoding/metadata.py (which had some testing
and time handling functions, wrappers, and hacks).
Also cut out some unnecessary imports.
Reverts one of the changes in commit
45b7a59

An extra decode('utf-8') was added to util.load_json_string,
breaking behavior in Python3.
Creating and verifying signatures should now work in Python2 and
Python3, whether over JSON or ASN.1/DER representations.

The ed25519 library was unhappy when receiving a string in Python3,
so encode('utf-8') had to be run, once, in either format.
Reverted piece is keys.py, ll753-757,893-897, from commit
eed6d48

These commits from the sprint broke demo execution, but testing didn't
catch it. I'll review another way to try to get the unicode/str/bytes
encoding right for Python3 ASN.1/DER and provide that later.
force_treat_as_pydict for create and verify sig in keys.py
Add from __future__ imports for print_function and unicode_literals.
as a precaution. Doesn't correct existing issue, but best to be
consistent about this to avoid future headaches.
@awwad awwad closed this Mar 28, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants