mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
Merge branch 'developer-tools' of github.com:SantiagoTorres/tuf into developer-tools
This commit is contained in:
commit
d6cfb467e7
1 changed files with 244 additions and 81 deletions
|
|
@ -2,77 +2,125 @@
|
|||
|
||||
## Table of Contents ##
|
||||
- [Overview](#overview)
|
||||
- [Creating a simple project](#creating_a_simple_project)
|
||||
- [Generating a key](#generating_a_key)
|
||||
- [The Project class](#the_project_class)
|
||||
- [Signing and writing metadata](#signing_and_writing_metadata)
|
||||
- [Loading an Existing project](#Loading_an_existing_project)
|
||||
- [Managing keys](#managing_keys)
|
||||
- [Managing targets](#managing_targets)
|
||||
- [Creating a Simple Project](#creating_a_simple_project)
|
||||
- [Generating a Key](#generating_a_key)
|
||||
- [The Project Class](#the_project_class)
|
||||
- [Signing and Writing the Metadata](#signing_and_writing_the_metadata)
|
||||
- [Loading an Existing Project](#loading_an_existing_project)
|
||||
- [Delegations](#delegations)
|
||||
- [Restricted paths](#restricted_paths)
|
||||
- [Keys and thresholds](#keys_and_thresholds)
|
||||
- [Managing Keys](#managing_keys)
|
||||
- [Managing Targets](#managing_targets)
|
||||
|
||||
<a name="overview">
|
||||
## Overview
|
||||
The TUF developer tool is a Python library that enables developers to create
|
||||
and maintain the required metadata for files hosted in a TUF Repository. This
|
||||
document has two parts. The first part walks through the creation of a
|
||||
prototypal TUF project. The second part demonstrates the full capabilities of
|
||||
the TUF developer tool, which can be users to expand the project from the first
|
||||
part to meet the developer''s needs.
|
||||
The TUF developer tool is a Python library that enables developers to create
|
||||
and maintain the required metadata for files hosted in a TUF Repository. The
|
||||
main concern when generating metadata for a TUF repository is generating
|
||||
information that matches the future location of the files in the repository. We
|
||||
use the developer tools to generate valid information so that the project and
|
||||
its metadata can be applied to the TUF project transparently.
|
||||
|
||||
This document has two parts. The first part walks through the creation of a
|
||||
prototypal TUF project. The second part demonstrates the full capabilities of
|
||||
the TUF developer tool, which can be used to expand the project from the first
|
||||
part to meet the developer's needs.
|
||||
|
||||
<a name="creating_a_simple_project">
|
||||
## Creating a Simple project ##
|
||||
### Generating a Key ###
|
||||
First, you will need to generate a key to sign the metadata. Keys are generated
|
||||
in pairs: one public and the other private. The private key is password-protected
|
||||
and is used to sign metadata. The public key can be shared freely, and is used
|
||||
to verify signatures made by the private key.
|
||||
## Creating a Simple project
|
||||
The following section describes a very basic example usage of the developer
|
||||
tools with a one-file project.
|
||||
|
||||
The generate_and_write_rsa_keypair function will create two key files in the
|
||||
path (path/to/) and named "key.pub", which is the public key and "key"
|
||||
which is the private key.
|
||||
<a name="generating_a_key">
|
||||
### Generating a Key
|
||||
First, we will need to generate a key to sign the metadata. Keys are generated
|
||||
in pairs: one public and the other private. The private key is
|
||||
password-protected and is used to sign metadata. The public key can be shared
|
||||
freely, and is used to verify signatures made by the private key.
|
||||
|
||||
The generate\_and\_write\_rsa\_keypair function will create two key files named
|
||||
"path/to/key.pub", which is the public key and "path/to/key", which
|
||||
is the private key.
|
||||
|
||||
```
|
||||
>>> from tuf.developer_tool import *
|
||||
|
||||
>>> generate_and_write_rsa_keypair("path/to/key")
|
||||
Enter a password for the RSA key:
|
||||
Confirm:
|
||||
>>>
|
||||
```
|
||||
|
||||
<a name="the_project_class">
|
||||
### The project class ###
|
||||
TUF-dev is built around the Project class, which is used to organize groups of
|
||||
targets associated with a single set of metadata. Each Project instance keeps
|
||||
track of which target files are associated with a single set of metadata. Each
|
||||
Project instance keeps track of which target files are signed and which need
|
||||
signing, which keys are used to sign metadata. It also keeps track of delegated
|
||||
roles, which are covered later.
|
||||
We can also use the bits parameter to set a different key length (the default
|
||||
is 3072). We can also provide the password parameter in order to suppress the
|
||||
password prompt.
|
||||
|
||||
Before creating a project, you must know where it will be located in the TUF
|
||||
In this example we will be using rsa keys, but ed25519 keys are also supported.
|
||||
|
||||
Now we have a key for our project, we can proceed to create our project.
|
||||
|
||||
<a name="the_project_class">
|
||||
### The Project Class
|
||||
The TUF developer tool is built around the Project class, which is used to
|
||||
organize groups of targets associated with a single set of metadata. Each
|
||||
Project instance keeps track of which target files are associated with a single
|
||||
set of metadata. Each Project instance keeps track of which target files are
|
||||
signed and which need signing, which keys are used to sign metadata. It also
|
||||
keeps track of delegated roles, which are covered later.
|
||||
|
||||
Before creating a project, you must know where it will be located in the TUF
|
||||
Repository. In the following example, we will create a project to be hosted as
|
||||
"repo/example_project" within the repository, and store a local copy of the
|
||||
metadata at "path/to/metadata". The project will comprise a single target file,
|
||||
"local/path/to/example_project/target_1" locally, and we will secure it with
|
||||
the key generated above.
|
||||
"repo/unclaimed/example_project" within the repository, and store a local copy
|
||||
of the metadata at "path/to/metadata". The project will comprise a single
|
||||
target file, "local/path/to/example\_project/target\_1" locally, and we will
|
||||
secure it with the key generated above.
|
||||
|
||||
First, we must import the generated keys. We can do that by issuing the
|
||||
following:
|
||||
|
||||
```
|
||||
>>> public_key = import_rsa_publickey_from_file("path/to/keys.pub")
|
||||
```
|
||||
|
||||
>>> project = create_new_project(metadata_directory="local/path/to/metadata/",
|
||||
... targets_directory="local/path/to/example_project",
|
||||
... location_in_repository="repo/example_project", key=public_key)
|
||||
After importing the key, we can generate a new project with the following
|
||||
command:
|
||||
|
||||
```
|
||||
>>> project = create_new_project(name="example_project",
|
||||
... metadata_directory="local/path/to/metadata/",
|
||||
... targets_directory="local/path/to/example_project",
|
||||
... location_in_repository="repo/unclaimed", key=public_key)
|
||||
```
|
||||
|
||||
Let's list the arguments and make sense out of this rather long function call:
|
||||
|
||||
- create a project named example_project: the name of the metadata file will match this name
|
||||
- the metadata will be located in "local/path/to/metadata", this means all of the generated files
|
||||
for this project will be located here
|
||||
- the targets are located in local/path/to/example project. If your targets are located in some other
|
||||
place, you can point the targets directory there. Files must reside under the path local/path/to/example_project or else it won't be possible to add them.
|
||||
- location\_in\_repository points to repo/unclaimed, this will be prepended to the paths in the generated metadata so the signatures all match.
|
||||
|
||||
Now the project is in memory and we can do different operations on it such as
|
||||
adding and removing targets, delegating files, changing signatures and keys,
|
||||
etc. For the moment we are interested in adding our one and only target inside
|
||||
the project.
|
||||
|
||||
To add a target, we issue the following method:
|
||||
|
||||
```
|
||||
>>> project.add_target("target_1")
|
||||
```
|
||||
|
||||
At this point, the metadata is not valid. We have assigned a key to the project,
|
||||
but we have not *signed* it with that key.
|
||||
Have in mind the file "target\_1" should be located in
|
||||
"local/path/to/example\_project" or else the adding procedure will throw an
|
||||
error.
|
||||
|
||||
At this point, the metadata is not valid. We have assigned a key to the
|
||||
project, but we have not *signed* it with that key. Signing is the process of
|
||||
generating a signature with our private key so it can be verified with the
|
||||
public key by the server (upon uploading) and by the clients (when updating).
|
||||
|
||||
<a name="signing_and_writing_the_metadata">
|
||||
### Signing and writing the metadata ###
|
||||
### Signing and Writing the Metadata ###
|
||||
In order to sign the metadata, we need to import the private key corresponding
|
||||
to the public key we added to the project. One the key is loaded to the project,
|
||||
it will automatically be used to sign the metadata whenever it is written.
|
||||
|
|
@ -84,17 +132,29 @@ Enter password for the RSA key:
|
|||
>>> project.write()
|
||||
```
|
||||
|
||||
When all changes to a project have been written, the Project instance can safely
|
||||
be deleted.
|
||||
When all changes to a project have been written, the Project instance can
|
||||
safely be deleted.
|
||||
|
||||
The project can be loaded later to update changes to the project. The metadata
|
||||
contains checksums that have to match the actual files or else it won't be
|
||||
accepted by the upstream repository.
|
||||
|
||||
At this point, if you have followed all the steps in this document so far
|
||||
(substituting appropriate names and filepaths) you will have created a basic
|
||||
TUF project, which can be expanded as needed. The simplest way to get your
|
||||
project secured is to add all your files using add\_target() (or see [Managing
|
||||
Keys](#managing_keys) on how to add whole directories). If your project has
|
||||
several contributors, you may want to consider adding
|
||||
[delegations](#delegations) to your project.
|
||||
|
||||
<a name="loading_an_existing_project">
|
||||
## Loading an Existing Project
|
||||
To make changes to existing metadata, we will need the Project again. We can
|
||||
restore it with the load_project() function.
|
||||
restore it with the load_project() function.
|
||||
|
||||
```
|
||||
>>> from tuf.developer_tool import *
|
||||
>>> project = load_project("local/path/to/metadata")
|
||||
>>>
|
||||
```
|
||||
Each time the project is loaded anew, the necessary private keys must also be
|
||||
loaded in order to sign metadata.
|
||||
|
|
@ -102,14 +162,82 @@ loaded in order to sign metadata.
|
|||
```
|
||||
>>> private_key = import_rsa_privatekey_from_file("path/to/key")
|
||||
Enter a password for the RSA key:
|
||||
|
||||
>>> project.load_signing_key(private_key)
|
||||
|
||||
>>> project.write()
|
||||
```
|
||||
|
||||
<a name="delegations">
|
||||
## Delegations
|
||||
|
||||
The project we created above is secured entirely by one key. If you want to
|
||||
allow someone else to update part of your project independently, you will need
|
||||
to delegate a new role for them. For example, we can do the following:
|
||||
|
||||
```
|
||||
>>> other_key = import_rsa_publickey_from_file(“another_public_key.pub”)
|
||||
>>> project.delegate(“newrole”, [other_key], targets)
|
||||
```
|
||||
|
||||
The new role is now an attribute of the Project instance, and contains the same
|
||||
methods as Project. For example, we can add targets in the same way as before:
|
||||
|
||||
```
|
||||
>>> project(“newrole”).add_target(“delegated_1”)
|
||||
```
|
||||
|
||||
Recall that we input the other person’s key as part of a list. That list can
|
||||
contain any number of public keys. We can also add keys to the role after
|
||||
creating it using the [add\_verification\_key()](#adding_a_key_to_a_delegation)
|
||||
method.
|
||||
|
||||
### Restricted Paths
|
||||
|
||||
By default, a delegated role is permitted to add and modify targets anywhere in
|
||||
the Project's targets directory. We can assign restricted paths to a delegated
|
||||
role to limit this permission.
|
||||
|
||||
```
|
||||
>>> project.add_restricted_paths(["restricted/filepath"], "newrole")
|
||||
```
|
||||
|
||||
This will prevent the delegated role from signing targets whose local filepaths
|
||||
do not begin with "restricted/filepath". We can assign several restricted
|
||||
filepaths to a role by adding them to the list in the first parameter, or by
|
||||
invoking the method again. A role with multiple restricted paths can add
|
||||
targets to any of them.
|
||||
|
||||
Note that this method is invoked the parent role (in this case, the Project)
|
||||
and takes the delegated role name as an argument.
|
||||
|
||||
### Nested Delegations
|
||||
|
||||
It is possible for a delegated role to have delegations of its own. We can do
|
||||
this by calling delegate() on a delegated role:
|
||||
|
||||
```
|
||||
>>> project("newrole").delegate(“nestedrole”, [key], targets)
|
||||
```
|
||||
|
||||
Nested delegations function no differently than first-order delegations. to
|
||||
demonstrate, adding a target to nested delegation looks like this:
|
||||
|
||||
```
|
||||
>>> project("newrole")("nestedrole").add_target("foo")
|
||||
```
|
||||
|
||||
### Revoking Delegations
|
||||
Delegations can be revoked, removing the delegated role from the project.
|
||||
|
||||
```
|
||||
>>> project.revoke("newrole")
|
||||
```
|
||||
|
||||
<a name="managing_keys">
|
||||
## Managing keys
|
||||
## Managing Keys
|
||||
This section describes the key-related functions and parameters not covered in
|
||||
the [Creating a Simple Project](#creating_a_simple_project) section.
|
||||
|
||||
### Additional Parameters for Key Generation
|
||||
When generating keys, it is possible to specify the length of the key in bits
|
||||
and its password as parameters:
|
||||
|
||||
|
|
@ -119,45 +247,80 @@ and its password as parameters:
|
|||
The bits parameter defaults to 3072, and values below 2048 will raise an error.
|
||||
The password parameter is only intended to be used in scripts.
|
||||
|
||||
|
||||
## Managing Targets
|
||||
<a name="adding_a_key_to_a_delegation">
|
||||
### Adding a Key to a Delegation
|
||||
New verifications keys can be added to an existing delegation using
|
||||
add\_verification\_key():
|
||||
|
||||
```
|
||||
>>> project("rolename").add_verification_key(pubkey)
|
||||
```
|
||||
|
||||
A delegation can have several verification keys at once. By default, a
|
||||
delegated role with multiple keys can be written using any one of their
|
||||
corresponding signing keys. To modify this behavior, you can change the
|
||||
delegated role's [threshold](#delegation_thrsholds).
|
||||
|
||||
### Removing a Key from a Delegation
|
||||
Verification keys can also be removed, like this:
|
||||
|
||||
```
|
||||
>>> project("rolename").remove_verification_key(pubkey)
|
||||
```
|
||||
|
||||
Remember that a project can only have one key, so this method will return an
|
||||
error if there is already a key assigned to it. In order to replace a key we
|
||||
must first delete the existing one and then add the new one. It is possible to
|
||||
omit the key parameter in the create\_new\_project() function, and add the key
|
||||
later.
|
||||
|
||||
### Changing the Project Key
|
||||
Each Project instance can only have one verification key. This key can be
|
||||
replaced by removing it and adding a new key, in that order.
|
||||
|
||||
```
|
||||
>>> project.remove_verification_key(oldkey)
|
||||
>>> project.add_verification_key(new)
|
||||
```
|
||||
|
||||
<a name="delegation_thresholds">
|
||||
### Delegation Thresholds
|
||||
|
||||
Every delegated role has a threshold, which determines how many of its signing
|
||||
keys need to be loaded to write the role. The threshold defaults to 1, and
|
||||
should not exceed the number of verification keys assigned to the role. The
|
||||
threshold can be accessed as a property of a delegated role.
|
||||
|
||||
```
|
||||
>>> project("rolename").threshold = 2
|
||||
```
|
||||
|
||||
The above line will set the "rolename" role's threshold to 2.
|
||||
|
||||
<a name="managing_targets">
|
||||
## Managing Targets
|
||||
There are supporting functions of the targets library to make the project
|
||||
maintenance easier. These functions are described in this section.
|
||||
|
||||
### Adding Targets by Directory
|
||||
This function is especially useful when creating a new project to add all the
|
||||
files contained in the targets directory. The following code block illustrates
|
||||
the usage of this function:
|
||||
|
||||
```
|
||||
>>> list_of_targets = \
|
||||
... project.get_filepaths_in_directory(“path/within/targets/folder”,
|
||||
... recursive_walk=False, follow_links=False)
|
||||
... project.add_targets(list_of_targets)
|
||||
>>> project.add_targets(list_of_targets)
|
||||
```
|
||||
|
||||
### Deleting Targets from a Project
|
||||
It is possible that we want to delete existing targets inside our project. To
|
||||
stop the developer tool from tracking this file we can issue the following
|
||||
command:
|
||||
|
||||
```
|
||||
>>> project.remove_target(“target_1”)
|
||||
```
|
||||
|
||||
## Delegations
|
||||
|
||||
The project we created above is secured entirely by one key. If you want to
|
||||
allow someone else to update part of your project independently, you will need
|
||||
to delegate a new role for them. For example, we can
|
||||
|
||||
```
|
||||
>>> other_key = import_rsa_publickey_from_file(“sombodys_public_key.pub”)
|
||||
|
||||
>>> project.delegate(“newrole”, [other_key], targets)
|
||||
```
|
||||
|
||||
The new role is now an attribute of the Project instance, and contains the same
|
||||
methods as Project. For example, we can add targets in the same way as before:
|
||||
|
||||
```
|
||||
|
||||
>>> project(“newrole”).add_target(“delegated_1”)
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
Recall that we input the other person’s key as part of a list. That list can
|
||||
contain any number of public keys. You can also add keys to the role after
|
||||
creating it using the add_signing_key() method.
|
||||
|
||||
Now the target file won't be part of the metadata.
|
||||
|
|
|
|||
Loading…
Reference in a new issue