% 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}