<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://theupdateframework.github.io/python-tuf/feed.xml" rel="self" type="application/atom+xml" /><link href="https://theupdateframework.github.io/python-tuf/" rel="alternate" type="text/html" /><updated>2026-04-14T06:51:49+00:00</updated><id>https://theupdateframework.github.io/python-tuf/feed.xml</id><title type="html">Python-TUF</title><subtitle>Development blog for Python-TUF, a supply chain security framework for secure content delivery and updates.
</subtitle><author><name>Python-TUF community</name></author><entry><title type="html">New signing API</title><link href="https://theupdateframework.github.io/python-tuf/2023/01/24/securesystemslib-signer-api.html" rel="alternate" type="text/html" title="New signing API" /><published>2023-01-24T00:00:00+00:00</published><updated>2023-01-24T00:00:00+00:00</updated><id>https://theupdateframework.github.io/python-tuf/2023/01/24/securesystemslib-signer-api</id><content type="html" xml:base="https://theupdateframework.github.io/python-tuf/2023/01/24/securesystemslib-signer-api.html"><![CDATA[<blockquote>
  <p>Things should be made as simple as possible – but no simpler.</p>

  <p><em>- sometimes attributed to Einstein</em></p>
</blockquote>

<p>I believe the rule of thumb above stands on its own merit when it comes to software systems so the credibility of the attribution is not important (it’s also possible that we should not take software design advice from a physicist).</p>

<p>This post is about the PKI signing API provided by <a href="https://github.com/secure-systems-lab/securesystemslib/">Securesystemslib</a> and used by applications built with python-tuf. It’s an example of how keeping a thing too simple can actually make it more complex.</p>

<h2 id="the-problem-with-private-keys">The problem with private keys</h2>

<p>The original <code class="language-plaintext highlighter-rouge">securesystemslib.keys</code> module is based on the assumption that there are three distinct steps in the lifetime of a private-public keypair in a system like a TUF repository:</p>
<ol>
  <li>Generate private and public key</li>
  <li>Sign with private key</li>
  <li>Verify signature with public key</li>
</ol>

<p>This all seems logical on paper but in practice implementing signing for different underlying technologies (like online key vaults and Yubikeys) forces the API surface to grow linearly, and still requires the applications to also be aware of all the different signing technologies and their configuration. It was clear that something was wrong.</p>

<h2 id="new-signer-module">New signer module</h2>

<p>In reality there are four distinct events during the lifetime of a signing key. All of these steps can happen on different systems, with different operators and different access to the underlying signing system:</p>
<ol>
  <li>Generate private and public keys – <em>This may happen in securesystemslib but also in an online key vault configuration UI or the Yubikey command line tool</em></li>
  <li>Store the public key <em>and the information needed to access the private key</em></li>
  <li>Sign using the information stored in step 2</li>
  <li>Verify signature with public key</li>
</ol>

<p>Securesystemslib 0.26 introduces an improved signer API that recognizes this process complexity – and in turn makes managing and signing with keys simpler in practical application development. There are three main changes, all in the <code class="language-plaintext highlighter-rouge">securesystemslib.signer</code> module that defines Signer and Key classes:</p>
<ul>
  <li>The concept of <strong>Private key URIs</strong> is introduced – this is a relatively simple string that identifies a signing technology and encodes how to access and sign with a specific private key. Examples:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">gcpkms:projects/python-tuf-kms/locations/global/keyRings/git-repo-demo/cryptoKeys/online/cryptoKeyVersions/1</code> (A Google Cloud KMS key)</li>
      <li><code class="language-plaintext highlighter-rouge">file:/home/jku/keys/mykey?encrypted=true</code> (A key in an encrypted file)</li>
      <li><code class="language-plaintext highlighter-rouge">hsm:</code> (A hardware security module like Yubikey)</li>
    </ul>
  </li>
  <li><strong>Importing</strong> public keys and constructing private key URIs is handled by Signers (there’s no generic API though: this detail is specific to signing technology)</li>
  <li><strong>Dynamic dispatch</strong> is added for both Signers and Keys (former based on the private key URI, latter on the key content): As a result application code does not need to care about the specific technology used to sign/verify but securesystemslib can still support a wide array of signing methods – and this support can even be extended with out-of-tree implementations.</li>
</ul>

<h2 id="code-examples">Code examples</h2>

<p>These examples are slightly simplified copies from my latest repository implementation and should represent any new application code using the python-tuf Metadata API in the future<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. Some things to note in these examples:</p>
<ul>
  <li>Application code that signs does not care what signing technology is used</li>
  <li>Public key import (and related private key URI construction) is specific to the underlying signing technology</li>
  <li>Private key URIs can be stored wherever makes sense for the specific application</li>
</ul>

<h3 id="example-1-online-key-in-a-kms">Example 1: Online key in a KMS</h3>

<p>Here’s an example where the private key URI is stored in a custom field in the metadata (this makes sense for online keys). First, the setup code that imports a key from Google Cloud KMS – this code runs in a repository maintainer tool:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">import_google_cloud_key</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Key</span>
    <span class="n">gcp_key_id</span> <span class="o">=</span> <span class="nb">input</span><span class="p">(</span><span class="s">"Please enter the Google Cloud KMS key id"</span><span class="p">)</span>
    <span class="n">uri</span><span class="p">,</span> <span class="n">key</span> <span class="o">=</span> <span class="n">GCPSigner</span><span class="p">.</span><span class="n">import_</span><span class="p">(</span><span class="n">gcp_key_id</span><span class="p">)</span>
    <span class="c1"># embed the uri in the public key metadata
</span>    <span class="n">key</span><span class="p">.</span><span class="n">unrecognized_fields</span><span class="p">[</span><span class="s">"x-online-uri"</span><span class="p">]</span> <span class="o">=</span> <span class="n">uri</span>
    <span class="k">return</span> <span class="n">key</span>
</code></pre></div></div>

<p>Then signing with the same key – this code runs in the online repository component and only needs the public key as an argument since we embedded the private key URI in the public key metadata. It does require the <code class="language-plaintext highlighter-rouge">cloudkms.signer</code> role permissions on Google Cloud though:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">sign_online</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">md</span><span class="p">:</span> <span class="n">Metadata</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="n">Key</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
     <span class="n">uri</span> <span class="o">=</span> <span class="n">key</span><span class="p">.</span><span class="n">unrecognized_fields</span><span class="p">[</span><span class="s">"x-online-uri"</span><span class="p">]</span>
     <span class="n">signer</span> <span class="o">=</span> <span class="n">Signer</span><span class="p">.</span><span class="n">from_priv_key_uri</span><span class="p">(</span><span class="n">uri</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
     <span class="n">md</span><span class="p">.</span><span class="n">sign</span><span class="p">(</span><span class="n">signer</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="example-2-maintainer-key-on-a-yubikey">Example 2: Maintainer key on a Yubikey</h3>

<p>This time we’re importing the maintainers Yubikey:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">import_yubikey</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="n">ConfigParser</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Key</span>
    <span class="nb">input</span><span class="p">(</span><span class="s">"Insert your HW key and press enter"</span><span class="p">)</span>
    <span class="n">uri</span><span class="p">,</span> <span class="n">key</span> <span class="o">=</span> <span class="n">HSMSigner</span><span class="p">.</span><span class="n">import_</span><span class="p">()</span>
    <span class="c1"># store the uri in application configuration
</span>    <span class="n">config</span><span class="p">[</span><span class="s">"keyring"</span><span class="p">][</span><span class="n">key</span><span class="p">.</span><span class="n">keyid</span><span class="p">]</span> <span class="o">=</span> <span class="n">uri</span>
    <span class="k">return</span> <span class="n">key</span>
</code></pre></div></div>

<p>Later we sign with the Yubikey:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">sign_local</span><span class="p">(</span><span class="n">md</span><span class="p">:</span> <span class="n">Metadata</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="n">Key</span><span class="p">,</span> <span class="n">config</span><span class="p">:</span> <span class="n">ConfigParser</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
     <span class="n">uri</span> <span class="o">=</span> <span class="n">config</span><span class="p">[</span><span class="s">"keyring"</span><span class="p">][</span><span class="n">key</span><span class="p">.</span><span class="n">keyid</span><span class="p">]</span>
     <span class="n">signer</span> <span class="o">=</span> <span class="n">Signer</span><span class="p">.</span><span class="n">from_priv_key_uri</span><span class="p">(</span><span class="n">uri</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
     <span class="n">md</span><span class="p">.</span><span class="n">sign</span><span class="p">(</span><span class="n">signer</span><span class="p">)</span>
</code></pre></div></div>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>The new signer API is not used in python-tuf quite yet: follow Pull Request <a href="https://github.com/theupdateframework/python-tuf/pull/2165">#2165</a> to see when the support is merged. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Jussi Kukkonen</name></author><summary type="html"><![CDATA[Things should be made as simple as possible – but no simpler. - sometimes attributed to Einstein]]></summary></entry><entry><title type="html">Python-tuf source code audit</title><link href="https://theupdateframework.github.io/python-tuf/2022/10/21/python-tuf-security-assessment.html" rel="alternate" type="text/html" title="Python-tuf source code audit" /><published>2022-10-21T00:00:00+00:00</published><updated>2022-10-21T00:00:00+00:00</updated><id>https://theupdateframework.github.io/python-tuf/2022/10/21/python-tuf-security-assessment</id><content type="html" xml:base="https://theupdateframework.github.io/python-tuf/2022/10/21/python-tuf-security-assessment.html"><![CDATA[<p>We are pleased to announce completion of a source code audit of the recently
refactored python-tuf codebase.</p>

<h1 id="background">Background</h1>

<p>In February 2022 the python-tuf team <a href="https://theupdateframework.github.io/python-tuf/2022/02/21/release-1-0-0.html">released version 1.0</a>. This release was the product of a significant refactoring effort with the
code being rewritten from scratch to provide two new stable API’s:</p>
<ul>
  <li>A low-level interface for creating and consuming TUF metadata</li>
  <li>A robust and pluggable client implementation</li>
</ul>

<p>Unifying both of these APIs is a focus on developer ergonomics and flexibility
of the API.</p>

<p>While the new python-tuf codebase is much leaner, a mere 1,400 lines of code at
release, compared to the legacy code’s 4,700 lines, and builds on the lessons
learned from development (and developers) on the prior versions of python-tuf,
we were very conscious of the fact that our first major release of a security
project was made up of newly authored code.</p>

<p>To improve our confidence in this newly authored code we engaged with the Open
Source Technology Improvement Fund (OSTIF) to have an independent security
assessment of the new python-tuf code. OSTIF connected us with the team at X41
D-Sec who performed a thorough source code audit, the results of which we are
releasing today.</p>

<h1 id="results-and-resolutions">Results and resolutions</h1>

<p>The report prepared by X41 included one medium severity and three low severity
issues, we describe below how we are addressing each of those reported items.</p>

<p><strong>Private Key World-Readable (TUF-CR-22-01) – Medium</strong></p>

<p>This vulnerability is not in any code called by python-tuf, but was included in
demonstrative code the python-tuf team provided to the X41 team. The underlying
issue is in 
<a href="https://github.com/secure-systems-lab/securesystemslib">securesystemslib</a>, a
utility library used by python-tuf which provides a consistent interface around
various cryptography APIs and related functionality, where any files were
created with the default permissions of the running process.</p>

<p>We resolved this issue by <a href="https://github.com/secure-systems-lab/securesystemslib/pull/231/files">adding an optional restrict parameter</a>
to the <code class="language-plaintext highlighter-rouge">storage.put()</code> interface and in the corresponding filesystem
implementation of the interface ensuring that when <code class="language-plaintext highlighter-rouge">restrict=True</code> files are
created with octal permissions <code class="language-plaintext highlighter-rouge">0o600</code> (read and write for the user only).</p>

<p>This enhancement has been included in the recent release of 
<a href="https://github.com/secure-systems-lab/securesystemslib/releases/tag/v0.25.0">securesystemslib 0.25.0</a>.</p>

<p><strong>Shallow Build Artifact Verification (TUF-CR-22-02) – Low</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">verify_release</code> script, run by python-tuf developers as part of the
release process and available to users to verify that a release on GitHub or
PyPI matches a build of source code from the repository, was only performing
a shallow comparison of files. That is, only the type, size, and modification
times were compared. We have <a href="https://github.com/theupdateframework/python-tuf/pull/2122/files">modified the script</a> to perform a deep comparison of the contents and attributes of files being
verified.</p>

<p><strong>Quadratic Complexity in JSON Number Parsing (TUF-CR-22-03) – Low</strong></p>

<p>This issue was not in python-tuf itself, rather the problem was in Python’s
built-in json module.</p>

<p>Fortunately, we did not need to take any action for this issue as it was
independently reported upstream and has been fixed in Python. Find more details
in <a href="https://github.com/python/cpython/issues/95778">CVE-2020-10735: Prevent DoS by large int&lt;-&gt;str conversions</a> on Python’s issue tracker.</p>

<p><strong>Release Signatures Add No Protection (TUF-CR-22-04) – Low</strong></p>

<p>python-tuf releases are built by GitHub Actions in response to a developer
pushing a tag. However, before those releases are published to the project’s
GitHub releases page and PyPI a developer must verify (using the
<code class="language-plaintext highlighter-rouge">verify_release</code> script discussed earlier) and approve the release. Part of the
approval includes creating a detached signature and including that in the
release artifacts. While these do not add any additional protection, we do
believe that the additional authenticity signal is worthwhile to users.</p>

<p>Furthermore, along with the above notice and the recommendations in the
informational notes we will continue to iterate on our build and release
process to provide additional security for users of python-tuf.</p>

<h1 id="thank-you">Thank you</h1>

<p>We are extremely grateful to X41 for their thorough audit of the python-tuf
code, to <a href="https://ostif.org">Open Source Technology Improvement Fund</a> (OSTIF)
for connecting us with the <a href="https://x41-dsec.de">X41 D-Sec, GMBH</a> team, and to
the <a href="https://www.cncf.io">Cloud Native Computing Foundation</a> (CNCF) for funding
the source code audit – thank you all.</p>

<p>Read the full report here: <a href="https://theupdateframework.io/audits/x41-python-tuf-audit-2022-09-09.pdf">Source Code Audit on The Update Framework for Open Source Technology Improvement Fund (OSTIF)</a>.</p>]]></content><author><name>Joshua Lock</name></author><summary type="html"><![CDATA[We are pleased to announce completion of a source code audit of the recently refactored python-tuf codebase.]]></summary></entry><entry><title type="html">Testing Tricky Edge Cases in a TUF Client</title><link href="https://theupdateframework.github.io/python-tuf/2022/06/15/testing-ngclient.html" rel="alternate" type="text/html" title="Testing Tricky Edge Cases in a TUF Client" /><published>2022-06-15T00:00:00+00:00</published><updated>2022-06-15T00:00:00+00:00</updated><id>https://theupdateframework.github.io/python-tuf/2022/06/15/testing-ngclient</id><content type="html" xml:base="https://theupdateframework.github.io/python-tuf/2022/06/15/testing-ngclient.html"><![CDATA[<p>Usually the TUF Specification creates an impression of simple and straightforward approach to address software update systems security gaps. In the next few paragraphs we’ll try to convince you that the devil is in the details.</p>

<p>With the <a href="https://blogs.vmware.com/opensource/2022/02/22/python-tuf-reaches-version-1-0-0/">v1.0.0 release</a> we can say that the current reference implementation is finally in a good place, although it wouldn’t be so trustworthy without all the awesome test functionality it provides. Therein lies some interesting surprises, for the conformance tests reflect use cases and tricky details that wouldn’t easily come to mind. TUF, in fact, is capable of managing some tricky business!</p>

<p>Before looking into them, let’s first introduce the test functionality itself.</p>

<h2 id="some-repository-simulator-magic">Some repository simulator magic</h2>

<p>The test suite is heavily based on <a href="https://github.com/theupdateframework/python-tuf/blob/develop/tests/repository_simulator.py">RepositorySimulator</a>, which allows you to play with repository metadata by modifying it, signing and storing new roles versions, while serving older ones in the client test code. You can also simulate downloading new metadata from a remote without the need of file access or network connections, and modify expiry dates and time.</p>

<p>Even though <code class="language-plaintext highlighter-rouge">RepositorySimulator</code> hosts repos purely in memory, you can supply the <code class="language-plaintext highlighter-rouge">--dump</code> flag to write its contents to a temporary directory on the local filesystem with “/metadata/…” and “/targets/…” URL paths that host metadata and targets respectively in order to audit the metadata. The test suite provides you with the ability to see the “live” test repository state for debugging purposes.</p>

<p>Let’s cite a specific example with testing expired metadata to demonstrate the cool thing the <code class="language-plaintext highlighter-rouge">RepositorySimulator</code> provides, i.e. the capability to simulate real repository chains of updates as suggested by the spec, and not just modify individual metadata.</p>

<p>More specifically, we would like to simulate a workflow in which a <a href="https://theupdateframework.github.io/specification/latest/#targets">targets</a> version is being increased and a <a href="https://theupdateframework.github.io/specification/latest/#timestamp">timestamp</a> expiry date is being changed. We are going to elaborate below on how this can be used to test the <code class="language-plaintext highlighter-rouge">Updater</code> above all programmatically. Now, let’s just focus on how to verify that the <code class="language-plaintext highlighter-rouge">RepositorySimulator</code> did what we expected.</p>

<p>Let’s assume we did the following:</p>
<ul>
  <li>Upgraded <code class="language-plaintext highlighter-rouge">targets</code> to v2</li>
  <li>Changed <code class="language-plaintext highlighter-rouge">timestamp</code> v2 expiry date</li>
</ul>

<p>We can verify that the metadata looks as expected, without the need to implement file access.</p>

<p>First, we need to find the corresponding temporary directory:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ python3 test_updater_top_level_update.py TestRefresh.test_expired_metadata --dump
Repository Simulator dumps in /var/folders/pr/b0xyysh907s7mvs3wxv7vvb80000gp/T/tmpzvr5xah_
</code></pre></div></div>

<p>Once we know it, we can verify that the metadata has 2 cached versions:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ tree /var/folders/pr/b0xyysh907s7mvs3wxv7vvb80000gp/T/tmpzvr5xah_/test_expired_metadata
/var/folders/pr/b0xyysh907s7mvs3wxv7vvb80000gp/T/tmpzvr5xah_/test_expired_metadata
├── 1
│   ├── 1.root.json
│   ├── snapshot.json
│   ├── targets.json
│   └── timestamp.json
└── 2
    ├── 2.root.json
    ├── snapshot.json
    ├── targets.json
    └── timestamp.json
</code></pre></div></div>

<p>And now we can also see that after bumping the version and moving timestamp v2 expiry date two weeks forward from v1, the v2 corresponding timestamp metadata has recorded that expiry date correctly:</p>

<p>Timestamp v1:</p>
<pre><code>$ cat /var/folders/pr/b0xyysh907s7mvs3wxv7vvb80000gp/T/tmpzvr5xah_/test_expired_metadata/1/timestamp.json 
{
 "signatures": [{...}],
 "signed": {
  "_type": "timestamp",
  <b>"expires": "2022-03-30T00:18:31Z"</b>,
  "meta": { "snapshot.json": {"version": 1}},
  "spec_version": "1.0.28",
  "version": 1
 }}
</code></pre>

<p>Timestamp v2:</p>

<pre><code>$ cat /var/folders/pr/b0xyysh907s7mvs3wxv7vvb80000gp/T/tmpzvr5xah_/test_expired_metadata/2/timestamp.json 
{
 "signatures": [{...}],
 "signed": {
  "_type": "timestamp",
  <b>"expires": "2022-04-13T00:18:31Z"</b>,
  "meta": { "snapshot.json": {"version": 2}},
  "spec_version": "1.0.28",
  "version": 2
 }}
</code></pre>

<p>As you can see, the first date is 30 Mar and the second - 13 Apr, which is exactly 14 days later. This is a great way to observe what the tests really do and check if they do it successfully.</p>

<h2 id="when-we-talk-about-security-edge-cases-are-the-norm">When we talk about security, edge cases are the norm</h2>

<p>Now, let’s take a closer look at two edge cases, using in this test the cool things the RepositorySimulator provides:</p>

<h3 id="example-with-expired-metadata">Example with expired metadata:</h3>

<p>Imagine that we have performed an update and stored metadata in a cache. And the locally stored timestamp/snapshot has expired. But we still need it to perform an update from remote by verifying the signatures and we need to use the expired timestamp.</p>

<p>We can play with versions and expiry to verify that this scenario not explicitly mentioned in the spec works correctly and safely. By using the simulator, we can do the following:</p>
<ol>
  <li>Set the timestamp expiry one week ahead (to day 7)</li>
  <li>On the very first day (day 0) download, verify, and load metadata for the <a href="https://theupdateframework.github.io/specification/latest/#roles-and-pki">top-level roles</a> following the TUF specification order. This is done by simply calling <code class="language-plaintext highlighter-rouge">updater.refresh()</code>.</li>
  <li>Then we bump <a href="https://theupdateframework.github.io/specification/latest/#update-snapshot">snapshot</a> and <a href="https://theupdateframework.github.io/specification/latest/#targets">targets</a> versions to v2 in the repository on the same day (day 0)</li>
  <li>Set v2 expiry dates three weeks ahead (to day 21)</li>
  <li>Travel in time somewhere between day 7 and day 21</li>
  <li>Perform a successful <code class="language-plaintext highlighter-rouge">refresh</code> (with <code class="language-plaintext highlighter-rouge">updater.refresh()</code> call) with the expired locally cached timestamp</li>
  <li>Check that the final repository version of the snapshot and targets roles is v2.</li>
</ol>

<p>This is a not so obvious use-case to keep in mind when thinking about updates. You can see how it looks in practice in the <a href="https://github.com/theupdateframework/python-tuf/blob/develop/tests/test_updater_top_level_update.py#:~:text=test_expired_metadata">reference implementation</a>.</p>

<h3 id="example-rollback-protection-check-with-expired-metadata">Example rollback protection check with expired metadata:</h3>

<p>Now let’s see if a rollback attack protection can be performed when the local timestamp has expired. In this case we need at least two timestamp and snapshot versions, an expired older version of timestamp, and a verification that a rollback check is performed with the old version.</p>

<p>For a timestamp rollback, the case is pretty similar to the use of expired metadata. We can do the following:</p>
<ol>
  <li>Set timestamp v1 expiry one week ahead (to day 7)</li>
  <li>Perform <code class="language-plaintext highlighter-rouge">updater.refresh()</code> on the very first day</li>
  <li>Publish timestamp v2 in the repository with expiry three weeks ahead (to day 21)</li>
  <li>Perform <code class="language-plaintext highlighter-rouge">updater.refresh()</code> somewhere between day 7 and day 21</li>
  <li>Verify that rollback check uses the expired timestamp v1. (For reference, see the implementation <a href="https://github.com/theupdateframework/python-tuf/blob/develop/tests/test_updater_top_level_update.py#:~:text=test_expired_timestamp_version_rollback">example</a>).</li>
</ol>

<p>A similar approach can be used when testing both timestamp and snapshot rollback protection. We just need to guarantee that after the last snapshot update, the snapshot version is not the latest in order to verify a rollback check is performed both with expired timestamp and an older snapshot. Sounds complicated, but it’s pretty easy with the simulator and <a href="https://github.com/theupdateframework/python-tuf/blob/develop/tests/test_updater_top_level_update.py#:~:text=test_expired_timestamp_snapshot_rollback">this example</a> illustrates it pretty well.</p>

<h2 id="the-devil-is-in-the-details">The devil is in the details</h2>

<p>One of the great things about a reference implementation is that one can learn a lot about the TUF specification by looking at the tests, which are full of examples that would hardly come to mind when you read the abstract straightforward workflow explained in the spec. And those tests most likely do not cover everything…</p>

<p>Do you have a comment about the TUF spec or the cited examples? An idea? Please share it with us!</p>]]></content><author><name>Ivana Atanasova</name></author><summary type="html"><![CDATA[Usually the TUF Specification creates an impression of simple and straightforward approach to address software update systems security gaps. In the next few paragraphs we’ll try to convince you that the devil is in the details.]]></summary></entry><entry><title type="html">What’s new in Python-TUF ngclient?</title><link href="https://theupdateframework.github.io/python-tuf/2022/05/04/ngclient-design.html" rel="alternate" type="text/html" title="What’s new in Python-TUF ngclient?" /><published>2022-05-04T00:00:00+00:00</published><updated>2022-05-04T00:00:00+00:00</updated><id>https://theupdateframework.github.io/python-tuf/2022/05/04/ngclient-design</id><content type="html" xml:base="https://theupdateframework.github.io/python-tuf/2022/05/04/ngclient-design.html"><![CDATA[<p>We recently released a new TUF client implementation, <code class="language-plaintext highlighter-rouge">ngclient</code>, in Python-TUF. This post explains why we ended up doing that when a client already existed.</p>

<h1 id="simpler-implementation-correct-abstractions">Simpler implementation, “correct” abstractions</h1>

<p>The legacy code had a few problems that could be summarized as non-optimal abstractions: Significant effort had been put to code reuse, but not enough attention had been paid to ensure the expectations and promises of that shared code were the same in all cases of reuse. This combined with Pythons type ambiguity, use of dictionaries as “blob”-like data structures and extensive use of global state meant touching the shared functions was a gamble: there was no way to be sure something wouldn’t break.</p>

<p>During the redesign, we really concentrated on finding abstractions that fit the processes we wanted to implement. It may be worth mentioning that in some cases this meant abstractions that have no equivalent in the TUF specification: some of the issues in the legacy implementation look like the result of mapping the TUF specifications <a href="https://theupdateframework.github.io/specification/latest/#detailed-client-workflow"><em>Detailed client workflow</em></a> directly into code.</p>

<p>Here are the core abstractions we ended up with (number of lines of code in parenthesis to provide a bit of context, alongside links to sources and docs):</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">Metadata</code> (900 SLOC, <a href="https://theupdateframework.readthedocs.io/en/latest/api/tuf.api.html">docs</a>) handles everything related to individual pieces of TUF metadata: deserialization, signing, and verifying</li>
  <li><code class="language-plaintext highlighter-rouge">TrustedMetadataSet</code> (170 SLOC) is a collection of local, trusted metadata. It defines rules for how new metadata can be added into the set and ensures that metadata in it is always consistent and valid: As an example, if <code class="language-plaintext highlighter-rouge">TrustedMetadataSet</code> contains a targets metadata, the set guarantees that the targets metadata is signed by trusted keys and is part of a currently valid TUF snapshot</li>
  <li><code class="language-plaintext highlighter-rouge">Updater</code> (250 SLOC, <a href="https://theupdateframework.readthedocs.io/en/latest/api/tuf.ngclient.updater.html">docs</a>) makes decisions on what metadata should be loaded into <code class="language-plaintext highlighter-rouge">TrustedMetadataSet</code>, both from the local cache and from a remote repository. While <code class="language-plaintext highlighter-rouge">TrustedMetadataSet</code> always raises an exception if a metadata is not valid, <code class="language-plaintext highlighter-rouge">Updater</code> considers the context and handles some failures as a part of the process and some as actual errors. <code class="language-plaintext highlighter-rouge">Updater</code> also handles persisting validated metadata and targets onto local storage and provides the user-facing API</li>
  <li><code class="language-plaintext highlighter-rouge">FetcherInterface</code> (100 SLOC, <a href="https://theupdateframework.readthedocs.io/en/latest/api/tuf.ngclient.fetcher.html">docs</a>) is the abstract file downloader. By default, a Requests-based implementation is used but clients can use custom fetchers to tweak how downloads are done</li>
</ul>

<p>No design is perfect but so far we’re quite happy with the above split. It has dramatically simplified the implementation: The code is subjectively easier to understand but also has significantly lower code branching counts for the same operations.</p>

<h1 id="pypi-client-requirements">PyPI client requirements</h1>

<p>A year ago we added TUF support into pip as a prototype: this revealed some design issues that made the integration more difficult than it needed to be. As the potential pip integration is a goal for Python-TUF we wanted to smooth those rough edges.</p>

<p>The main addition here was the <code class="language-plaintext highlighter-rouge">FetcherInterface</code>: it allows pip to keep doing all of the HTTP tweaks they have collected over the years.</p>

<p>There were a bunch of smaller API tweaks as well: as an example, legacy Python-TUF had not anticipated downloading target files from a different host than it downloads metadata from. This is the design that PyPI uses with pypi.org and files.pythonhosted.org.</p>

<h1 id="better-api">better API</h1>

<p>Since we knew we had to break API with the legacy implementation anyway, we also fixed multiple paper cuts in the API:</p>
<ul>
  <li>Actual data structures are now exposed instead of dictionary “blobs”</li>
  <li>Configuration was removed or made non-global</li>
  <li>Exceptions are defined in a way that is useful to client applications</li>
</ul>

<h1 id="plain-old-software-engineering">Plain old software engineering</h1>

<p>In addition to the big-ticket items, the rewrite allowed loads of improvements in project engineering practices. Some highlights:</p>
<ul>
  <li>Type annotations are now used extensively</li>
  <li>Coding style is now consistent (and is now a common Python style)</li>
  <li>There is a healthy culture of review in the project: bar for accepting changes is where it should be for a security project</li>
  <li>Testing has so many improvements they probably need a blog post of their own</li>
</ul>

<p>These are not <code class="language-plaintext highlighter-rouge">ngclient</code> features as such but we expect they will show in the quality of products built with it.</p>]]></content><author><name>Jussi Kukkonen</name></author><summary type="html"><![CDATA[We recently released a new TUF client implementation, ngclient, in Python-TUF. This post explains why we ended up doing that when a client already existed.]]></summary></entry><entry><title type="html">Python-TUF reaches version 1.0.0</title><link href="https://theupdateframework.github.io/python-tuf/2022/02/21/release-1-0-0.html" rel="alternate" type="text/html" title="Python-TUF reaches version 1.0.0" /><published>2022-02-21T00:00:00+00:00</published><updated>2022-02-21T00:00:00+00:00</updated><id>https://theupdateframework.github.io/python-tuf/2022/02/21/release-1-0-0</id><content type="html" xml:base="https://theupdateframework.github.io/python-tuf/2022/02/21/release-1-0-0.html"><![CDATA[<p>The Python-TUF community is proud to announce the release of Python-TUF 1.0.0.
The release, which is available on <a href="https://pypi.org/project/tuf/">PyPI</a> and
<a href="https://github.com/theupdateframework/python-tuf/">GitHub</a>, introduces new
stable and more ergonomic APIs.</p>

<p><img align="right" src="../../../tuf-icon-200.png" width="200" /></p>

<p>Python-TUF is the reference implementation of <a href="https://theupdateframework.io/">The Update
Framework</a> specification, an open source
framework for securing content delivery and updates. It protects against
various types of supply chain attacks and provides resilience to compromise.</p>

<p>For the past 7 releases the project has introduced new designs and
implementations, which have gradually formed two new stable APIs:</p>
<ul>
  <li><a href="https://theupdateframework.readthedocs.io/en/latest/api/tuf.ngclient.html"><code class="language-plaintext highlighter-rouge">ngclient</code></a>:
A client API that offers a robust internal design providing implementation
safety and flexibility to application developers.</li>
  <li><a href="https://theupdateframework.readthedocs.io/en/latest/api/tuf.api.html"><code class="language-plaintext highlighter-rouge">Metadata API</code></a>:
A low-level interface for both consuming and creating TUF metadata. Metadata
API is a flexible and easy-to-use building block for any higher level tool or
library.</li>
</ul>

<p>Python-TUF 1.0.0 is the result of a comprehensive rewrite of the project,
removing several hard to maintain modules and replacing them with safer and
easier to use APIs:</p>
<ul>
  <li>The project was reduced from 4700 lines of hard to maintain code to 1400
lines of modern, maintainable code</li>
  <li>The implementation details are now easier to reason about, which should
accelerate future improvements on the project</li>
  <li>Metadata API provides a solid base to build other tools on top of – as proven
by the ngclient implementation and the <a href="https://github.com/theupdateframework/python-tuf/tree/develop/examples/repository">repository code
examples</a></li>
  <li>Both new APIs are highly extensible and allow application developers to
include custom network stacks, file storage systems or public-key
cryptography algorithms, while providing easy-to-use default implementations</li>
</ul>

<p>With this foundation laid, Python-TUF developers are currently planning next
steps. At the very least, you can expect improved repository side tooling, but
we’re also open to new ideas. Pop in to
<a href="https://cloud-native.slack.com/archives/C8NMD3QJ3">#tuf</a> on CNCF Slack or
<a href="https://github.com/theupdateframework/python-tuf/issues/new">Github issues</a>
and let’s talk.</p>]]></content><author><name>Jussi Kukkonen and Lukas Pühringer</name></author><summary type="html"><![CDATA[The Python-TUF community is proud to announce the release of Python-TUF 1.0.0. The release, which is available on PyPI and GitHub, introduces new stable and more ergonomic APIs.]]></summary></entry></feed>