mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
423 lines
17 KiB
TeX
423 lines
17 KiB
TeX
% tuf_client_spec.tex
|
|
% This document has been deprecated. We may later update and include it in
|
|
% the supported documentation.
|
|
\documentclass{article}
|
|
\setlength\parindent{0pt}
|
|
\usepackage{listings}
|
|
\usepackage{hyperref}
|
|
\usepackage{color}
|
|
\usepackage{textcomp}
|
|
\definecolor{listinggray}{gray}{0.9}
|
|
\definecolor{lbcolor}{rgb}{0.9,0.9,0.9}
|
|
\lstset{
|
|
backgroundcolor=\color{lbcolor},
|
|
tabsize=4,
|
|
rulecolor=,
|
|
language=matlab,
|
|
basicstyle=\scriptsize,
|
|
upquote=true,
|
|
aboveskip={1.5\baselineskip},
|
|
columns=fixed,
|
|
showstringspaces=false,
|
|
extendedchars=true,
|
|
breaklines=true,
|
|
prebreak = \raisebox{0ex}[0ex][0ex]{\ensuremath{\hookleftarrow}},
|
|
frame=single,
|
|
showtabs=false,
|
|
showspaces=false,
|
|
showstringspaces=false,
|
|
identifierstyle=\ttfamily,
|
|
keywordstyle=\color[rgb]{0,0,1},
|
|
commentstyle=\color[rgb]{0.133,0.545,0.133},
|
|
stringstyle=\color[rgb]{0.627,0.126,0.941},
|
|
}
|
|
|
|
\begin{document}
|
|
|
|
%--------------------------------- Header --------------------------------------
|
|
\title{Secure Update Framework Client Key Management and Trust Delegation}
|
|
\author{Geremy Condra \and Justin Cappos}
|
|
\maketitle
|
|
%-------------------------------------------------------------------------------
|
|
|
|
|
|
%--------------------------------- Intro ---------------------------------------
|
|
\section{Introduction}
|
|
\subsection{Scope}
|
|
This document specifies the required trust delegation and key management routines
|
|
for TUF clients.
|
|
\subsection{Relationship to Other Documents}
|
|
Much of the behavior specified in this document is partially laid out in the
|
|
core TUF document. The system's behavior with regard to freeze and replay
|
|
attacks is covered in the document entitled "Software Update Security Framework:
|
|
Client Library Replay and Freeze Attack Protection". The repository-side counterpart
|
|
to this document contains a large amount of information on the response that
|
|
subsystem will demonstrate in many of the same circumstances.
|
|
%-------------------------------------------------------------------------------
|
|
|
|
%-------------------------------- Overview -------------------------------------
|
|
\section{Overview}
|
|
The Update Framework is a Python library designed to allow software developers to safely,
|
|
securely, and easily update clients running their software. In particular, it
|
|
focuses on the issues of timeliness of data, rapid recovery from a key compromise,
|
|
and ensuring the authenticity and integrity of installed updates. This document
|
|
describes the behavior the client must demonstrate in order to provide those
|
|
properties.
|
|
%-------------------------------------------------------------------------------
|
|
%-------------------------------- Example --------------------------------------
|
|
\section{Example}
|
|
The examples used throughout both this and its companion repository-side document
|
|
are designed to be easy to reproduce, both to verify TUFs behavior and to emulate
|
|
it. The following subsections demonstrate how to set up a small but fully
|
|
functional TUF system in which to do so.
|
|
|
|
\subsection{Setting up the Repository}
|
|
You'll need to run the following steps to set the stage:
|
|
|
|
\begin{lstlisting}
|
|
#! /bin/sh
|
|
|
|
# create the relevant directories
|
|
mkdir tufdemo
|
|
cd tufdemo
|
|
mkdir demorepo
|
|
mkdir demoproject
|
|
|
|
# add a file to the project
|
|
echo "#! /usr/bin/env python" > demoproject/helloworld.py
|
|
echo "print 'hello, world!'" >> demoproject/helloworld.py
|
|
|
|
# run the quickstart script
|
|
quickstart.py -t 1 -k keystore -l demorepo -r demoproject
|
|
\end{lstlisting}
|
|
|
|
This will prompt you for a password for your keystore and an expiration date.
|
|
Choosing your expiration date is something of a balancing act: on the one hand,
|
|
you want to make sure that all your clients have had a chance to update before
|
|
your keys and metadata expire, but on the other hand you want to choose a short
|
|
time so that keys you revoke expire quickly. A range of one to six weeks is likely
|
|
to be reasonable for most applications.
|
|
\\\\
|
|
After running this and choosing an expiration date, you'll see that it has created
|
|
an encrypted keystore and a repository for you to use, and that the repository's
|
|
contents match those of the demo project we created.
|
|
|
|
\subsection{Running the Server}
|
|
To actually perform an update out of this, you'll need to run a web server through
|
|
which the client can access the files. Fortunately, Python comes with an easy-to-use
|
|
module to do this for you:
|
|
|
|
\begin{lstlisting}
|
|
cd demorepo
|
|
python -m SimpleHTTPServer 8001
|
|
\end{lstlisting}
|
|
|
|
\subsection{Setting up the Client}
|
|
TUF isn't designed as a replacement for package managers so it doesn't provide
|
|
a mechanism with which to perform the initial installation of our demo project's
|
|
metadata. To do that, open up another terminal and run the following:
|
|
|
|
\begin{lstlisting}
|
|
#! /bin/sh
|
|
|
|
mkdir democlient
|
|
cp -r demorepo/meta democlient/cur
|
|
cp -r democlient/cur democlient/prev
|
|
\end{lstlisting}
|
|
|
|
|
|
Once we've installed our metadata, getting the software is a simple matter of
|
|
running the demonstration client, found with TUF's source at
|
|
examples/example\_client.py.
|
|
%-------------------------------- Details --------------------------------------
|
|
\section{Basic Client Behavior}
|
|
When trying to update, the goal of the client is to efficiently obtain the most up-to-date
|
|
legitimate version of the package. Doing that means three things: first, that
|
|
the client has to be able to get enough metadata from the repo to determine
|
|
which files to update, second that it has to be able to verify that metadata,
|
|
and third that it needs to be able to verify the files once it receives them.
|
|
\\\\
|
|
To start with, TUF downloads the timestamp.txt file, which tells it the last
|
|
time an update was made. To verify it, we pull the last known good public key for
|
|
the timestamp role out of our copy of root.txt. Assuming that the repo has updated
|
|
since the last time we did, TUF will continue by downloading the release.txt
|
|
metadata file and, like timestamp.txt, verifying it against the release role key
|
|
stored in our copy of root.txt. When combined with the timestamp metadata, the
|
|
release file will allow us to determine if we're receiving the appropriate version
|
|
of the other metadata files.
|
|
\\\\
|
|
Since we now have enough verified data to ensure that we're getting the proper
|
|
version of the rest of our metadata, we can go ahead and obtain and verify the
|
|
root.txt metadata file. As we've already seen, this stores metadata about both
|
|
other roles and their keys, and as we'll see later, this is also how we handle
|
|
key revocation.
|
|
\\\\
|
|
Assuming everything else has checked out, we can now download targets.txt, which
|
|
allows us to determine which target files (aka, non-metadata files) we will need
|
|
to update. Since we have all the hashes of all the target files and know that
|
|
those hashes are authentic, up-to-date, and valid, we can fetch the matching files
|
|
and complete the update.
|
|
\\\\
|
|
If, at any point in the process, we cannot verify a file against its signature
|
|
or if it hashes incorrectly, the update process will terminate with an error.
|
|
This signature verification process is positive in that the existence of a
|
|
threshold of signatures from the appropriate role is both necessary and sufficient
|
|
for a signature to be valid.
|
|
|
|
\section{Key Storage}
|
|
As we've seen, the proper operation of a TUF client only depends on the ability to
|
|
verify that any results it obtains when polling the server or mirrors have been
|
|
signed by all of the necessary keys. Since this only requires the use of public
|
|
keys, client key management reduces to the task of properly associating roles
|
|
with their keys. In TUF, the mechanism for doing so is via its metadata files, and
|
|
especially root.txt.
|
|
|
|
\subsection{root.txt}
|
|
This metadata file is responsible for storing all the trusted keys for TUF,
|
|
along with the key metadata needed to do routine key management. It must be
|
|
located at the base URL of the repository's metadata files and signed by the
|
|
root role's key.
|
|
|
|
\subsubsection{Format}
|
|
The format of root.txt is as follows:
|
|
|
|
\begin{verbatim}
|
|
{ "_type" : "Root",
|
|
"ts" : TIME,
|
|
"expires" : EXPIRES,
|
|
"keys" : {
|
|
KEYID : KEY
|
|
, ... },
|
|
"roles" : {
|
|
ROLE : {
|
|
"keyids" : [ KEYID, ... ] ,
|
|
"threshold" : THRESHOLD }
|
|
, ... }
|
|
}
|
|
\end{verbatim}
|
|
|
|
The format of each element in the above should be consistent with that described
|
|
in the TUF specification sections 4.1, 4.2, and 4.3, which is to say that the
|
|
TIME and EXPIRES values should be in "YYYY-MM-DD HH:MM:SS" format, KEYID is a
|
|
64 character hexadecimal string, and THRESHOLD is a (normally small) integer.
|
|
\\\\
|
|
If the current date is past that specified in EXPIRES, the update process should
|
|
end with an error. The same is true of all other metadata in TUF.
|
|
\\\\
|
|
The KEY value should be of the following format:
|
|
|
|
\begin{verbatim}
|
|
{ "keytype" : KEYTYPE,
|
|
"keyval" : KEYVAL }
|
|
\end{verbatim}
|
|
|
|
where KEYTYPE is a string signifying the encryption primitive (e.g., 'rsa') and
|
|
KEYVAL is a canonical JSON mapping specifying the appropriate key parameters for
|
|
that primitive.
|
|
\\\\
|
|
The ROLE value should be one of 'root', 'release', 'targets', 'timestamp', or
|
|
'mirrors'.
|
|
|
|
\subsubsection{Verification and Validation}
|
|
In addition to validation of the format as specified above, the client library
|
|
is required to perform the following checks upon receiving an updated root.txt:
|
|
|
|
\begin{enumerate}
|
|
\item Check the 'ts' field against the current time and date
|
|
to ensure that they do not replace this file with an older version.
|
|
This is to ensure that an attacker can't replay metadata from
|
|
before a compromised key was revoked.
|
|
\item Verify that each of the top level roles is correctly
|
|
specified in the "roles" field, with the exception of the
|
|
optional "mirrors" role.
|
|
\item Verify that each keyid matches its respective key and is unique.
|
|
\end{enumerate}
|
|
|
|
In addition to the above, the client library must also take care not to trust a root.txt past
|
|
its expiration time and to ensure that keys specified in it meet algorithm-specific
|
|
standards of safety. \textbf{Clients must not be allowed trust or install an improperly signed root.txt.}
|
|
Doing so would allow an attacker to forge arbitrary updates, effectively removing
|
|
all of TUF's security properties.
|
|
|
|
\section{Trust Delegation}
|
|
Trust delegation (the process of a trusted user allowing another user to share
|
|
some or all of their privileges) is built into TUF's trust model, particularly
|
|
with respect to the Target role. The goal of this delegation mechanism is to
|
|
make it possible for individual developers to be trusted on some portion of a
|
|
project, but not the project as a whole. Note that since authorization is positive
|
|
in TUF, if a valid signature for an update exists under any role with permission
|
|
over it that update will be accepted as valid.
|
|
|
|
\subsection{targets.txt}
|
|
The targets.txt file, signed by the target role's key, is responsible for providing
|
|
the mechanism for trust delegation. It must be located at the repository's
|
|
metadata base URL and have the following format:
|
|
|
|
\begin{verbatim}
|
|
{ "_type" : "Targets",
|
|
"ts" : TIME,
|
|
"expires" : EXPIRES,
|
|
"targets" : TARGETS,
|
|
("delegations" : DELEGATIONS)
|
|
}
|
|
\end{verbatim}
|
|
|
|
The TIME and EXPIRES fields are formatted as for the root.txt format.
|
|
\\\\
|
|
The TARGETS value should be a list of elements in the following format:
|
|
|
|
\begin{verbatim}
|
|
{ TARGETPATH : {
|
|
"length" : LENGTH,
|
|
"hashes" : HASHES,
|
|
("custom" : { ... }) }
|
|
, ...
|
|
}
|
|
\end{verbatim}
|
|
|
|
Where LENGTH is an integer and HASHES is a list of the cryptographic hashes of
|
|
the path's destination. The 'custom' field's contents are application-specific
|
|
and have no impact on the delegation behavior.
|
|
\\\\
|
|
The DELEGATIONS part of the targets.txt file points to a list of items formatted
|
|
like so:
|
|
|
|
\begin{verbatim}
|
|
{ "keys" : {
|
|
KEYID : KEY,
|
|
... },
|
|
"roles" : {
|
|
ROLE : {
|
|
"keyids" : [ KEYID, ... ] ,
|
|
"threshold" : THRESHOLD,
|
|
"paths" : [ PATHPATTERN, ... ] }
|
|
, ... }
|
|
}
|
|
\end{verbatim}
|
|
|
|
With the exception of PATHPATTERN, all the variables seen here are formatted
|
|
identically to the variables of the same names in the previous listings. PATHPATTERN
|
|
itself represents either a literal path in UNIX format or a path with ending with
|
|
a wildcard represented by '/**'.
|
|
|
|
\subsection{Example}
|
|
We can use the tools built into TUF to see how the above translates into a full
|
|
targets.txt. While in the demorepo/meta directory created by our first script, we can
|
|
use signercli.py like this:
|
|
\begin{lstlisting}
|
|
cd ../..
|
|
signercli.py delegate --keystore=keystore ROLE KEYID PATH
|
|
\end{lstlisting}
|
|
to modify our default targets.txt to add a delegated role named ROLE associated
|
|
with KEYID that has permission to modify elements in PATH. Here's the (scrubbed)
|
|
output:
|
|
|
|
\begin{verbatim}
|
|
{"signatures": [{
|
|
"keyid": KEYID,
|
|
"method": "sha256-pkcs1",
|
|
"sig": SIGNATURE
|
|
}],
|
|
"signed": {
|
|
"_type": "Targets",
|
|
"expires": EXPIRES,
|
|
"targets": {
|
|
PATH: {
|
|
"hashes": {
|
|
"sha256": HASH},
|
|
"length": LENGTH}
|
|
},
|
|
"ts": EXPIRES}
|
|
"delegations": {
|
|
"keys": {
|
|
KEYID: {
|
|
"keytype": "rsa",
|
|
"keyval": {"e": E, "n": N}
|
|
}
|
|
},
|
|
"roles": {ROLE: {"keyids": [KEYID],
|
|
"paths": [PATH],
|
|
"threshold": 1}
|
|
}
|
|
}
|
|
}
|
|
\end{verbatim}
|
|
|
|
\subsection{Delegated Targets Metadata}
|
|
Delegated targets metadata is stored in /targets/ROLE.txt, where ROLE is the
|
|
name of the role to be delegated. This file must be signed by that role and
|
|
be formatted identically to the top level targets.txt file. Hierarchically
|
|
delegated trust is, appropriately, handled hierarchically- if DELEGATED\_ROLE
|
|
delegates trust to ANOTHER\_ROLE, then the metadata file for ANOTHER\_ROLE can be
|
|
found at /targets/DELEGATED\_ROLE/ANOTHER\_ROLE.txt. We can create a simple
|
|
example with the following command:
|
|
|
|
\begin{lstlisting}
|
|
signercli.py maketargets \
|
|
--keystore=../keystore \
|
|
--parentdir=targets \
|
|
--keyid=KEYID \
|
|
--rolename=ROLE \
|
|
TARGETS
|
|
\end{lstlisting}
|
|
|
|
And here's the result, stored at targets/ROLE.txt:
|
|
|
|
\begin{verbatim}
|
|
{
|
|
"signatures": [
|
|
{
|
|
"keyid": KEYID,
|
|
"method": "sha256-pkcs1",
|
|
"sig": SIGNATURE
|
|
}
|
|
],
|
|
"signed": {
|
|
"_type": "Targets",
|
|
"expires": EXPIRES,
|
|
"targets": {
|
|
PATH: {
|
|
"hashes": {
|
|
"sha256": HASH
|
|
},
|
|
"length": LENGTH
|
|
}
|
|
},
|
|
"ts": TIMESTAMP
|
|
}
|
|
}
|
|
\end{verbatim}
|
|
|
|
\section{Key Revocation}
|
|
Key revocation in TUF falls into one of three cases:
|
|
|
|
\begin{enumerate}
|
|
\item Revocation of a delegated target key
|
|
\item Revocation of a non-root top level key
|
|
\item Revocation of a root key
|
|
\end{enumerate}
|
|
|
|
Revocation of a delegated target key is simple- the key in question is simply
|
|
removed from the metadata files that delegated to it. Similarly, revoking or
|
|
replacing a non-root top level key is just a matter of replacing it in root.txt
|
|
with the new value. For example, suppose that a role ALICE delegates trust to
|
|
another role EVE. ALICE can then revoke EVE's trust entirely by deleting the
|
|
targets/ALICE/EVE.txt file and removing the DELEGATIONS data structure from
|
|
either targets/ALICE.txt (if ALICE is a child of a toplevel role) or from her
|
|
parent role otherwise. The next time an update is generated, EVE's trust will
|
|
have been completely revoked. Replacing EVE's key can then be done by adding her
|
|
like a new delegation.
|
|
\\\\
|
|
Revocation of the root key is only slightly more complex. Merely replacing it would
|
|
leave older clients unable to update, so the better way is to simply sign with
|
|
both the new key and the old key until you are confident that all the relevant clients
|
|
have updated. Once you've done that you can stop signing with the old key.
|
|
|
|
\section{Future Work}
|
|
In the future, the format for keys may be opened to support OpenSSL-style keys.
|
|
Support for skewed clocks may also be added as noted in the core TUF spec, since
|
|
many clients seem to be operating under substantial clock drift. Support for
|
|
automatically integrating TUF with other projects using distutils is also a
|
|
potential future direction.
|
|
%-------------------------------------------------------------------------------
|
|
\end{document}
|