Skip to content

Instantly share code, notes, and snippets.

@phoe
Last active March 17, 2021 19:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phoe/8ccba5fd13e6ab53b1b19922f3868b34 to your computer and use it in GitHub Desktop.
Save phoe/8ccba5fd13e6ab53b1b19922f3868b34 to your computer and use it in GitHub Desktop.
Quicklisp PGP - proposal

Quicklisp with PGP

This is a proposal that describes the hypothetical policy and structure of Quicklisp's implementation of public key cryptography used for signing Quicklisp releases and software.

Key structure

  • A single private key becomes the root of trust. This key has an expiration date of five years and is used to sign release subkeys.
    • Alternative: we might want to go for a shorter expiration time, e.g. one year. In such a situation, each root should be signed by all of the previous roots to make migration easier for people who have gone a whole year+ without updating the root.
  • Release subkeys are keys that are used to sign individual dist releases. These keys are generated during a dist release and have an expiration date set at a week.
    • For often-releasing dists such as Ultralisp, it might be equivalent to say that a release key is generated at the beginning of every month, it has an expiration date until the end of the month, and is used throughout the month.

Key distribution

  • Key and signatures are distributed via HTTP.
  • Public keys for the root of trust are available at http://beta.quicklisp.org/keys/root.zip.
    • The public key of the root of trust can be distributed within the Quicklisp installer for bootstrapping purposes.
    • The Quicklisp installer itself can be signed with the root of trust.
  • Public keys for every release are available at http://beta.quicklisp.org/keys/version.zip where version is the version of the dist.
    • Because the keys are small, we can consider avoiding distributed them in a ZIP file and send them over plaintext instead, which will also avoid the issue of sending maliciously crafted ZIP files meant to attack the inflating function.
  • Dist signatures are available at http://beta.quicklisp.org/signature/.../foo.sig where ... in the URL depends on the URL of the signed file.
    • http://beta.quicklisp.org/signature/dist/quicklisp/2020-12-20/distinfo.txt.sig for http://beta.quicklisp.org/dist/quicklisp/2020-12-20/distinfo.txt
    • The signed dist file contains SHA256 sums for all tarballs and other files that are a part of the dist.
  • Revocation certificates are available at http://beta.quicklisp.org/keys/revocations.zip.
    • If a release key is revoked, a new one is generated for that release and everything is resigned with it.

Key usage and rotation

  • When the Quicklisp client notices that its root of trust expired, it downloads a new archive containing the root of trust public key and checks if it is signed by one of the old ones. If so, it adds it to the internal keyring to replace the old one.
  • When the Quicklisp client notices that it does not have a key for a given release, it downloads it and checks if it is signed by the root of trust. If so, it adds it to the internal keyring.
  • If a release is signed by a key that had expired by the time the release was created (e.g. a May 2021 release signed with a Apr 2021 key), the client must signal an error.
@daewok
Copy link

daewok commented Jan 30, 2021

Root Key

Honestly, one year seems like a short amount of time for a key that should be kept offline.

Should the root key be rotated or just have its expiry date extended? Should this be mandated or should we leave it up to the dist maintainers to best fit their security models?

When the Quicklisp client notices that its root of trust expired, it downloads a new archive containing the root of trust public key and checks if it is signed by the old one.

What if someone goes over a year without updating the root? Should the new roots be signed by every previous root key?

Signing Files

It seems like overkill to sign every single tarball. What about signing only the Quicklisp metadata? If the hash contained in the metadata is switched from MD5 to something cryptographically secure it should give the same integrity guarantees. This would also loosen the requirement of keeping old keys around.

Revoking

Do we want to handle revoking keys? If so, need to specify where revocation certificates can be found.

Presumably if a release key is revoked, a new one would be generated for that release and everything resigned. This is another case where signing only the metadata would be a boon (fewer things to resign/redownload).

@phoe
Copy link
Author

phoe commented Jan 30, 2021

Should the root key be rotated or just have its expiry date extended? Should this be mandated or should we leave it up to the dist maintainers to best fit their security models?

PGP states that the owner of a key is capable of extending a key's expiration date indefinitely by adding a new self-signature on the key. Still, it can be more than a year, no problem - five years perhaps?

What if someone goes over a year without updating the root? Should the new roots be signed by every previous root key?

Yes, I think so.

It seems like overkill to sign every single tarball. What about signing only the Quicklisp metadata? If the hash contained in the metadata is switched from MD5 to something cryptographically secure it should give the same integrity guarantees.

Sure, this sounds good - it should be enough to sign the dist file that contains SHA256 sums of all tarballs.

Do we want to handle revoking keys? If so, need to specify where revocation certificates can be found.

OK - I'll add this to the proposal.


I'll edit the proposal now and let you know once the update is ready.

@phoe
Copy link
Author

phoe commented Jan 30, 2021

Done. What do you think about the current version?

@daewok
Copy link

daewok commented Feb 4, 2021

Sorry for the delay! It's looking pretty good now, but I have four more intertwined concerns.

First, we need to still consider that people like to fetch old dist versions for testing/pining/etc. If the release keys for that version were signed by a master key that the client has never seen (or has been removed because it expired) how does the client verify them? The two obvious solutions seem to be that every time the master key is rotated all previous release keys are resigned, or all master keys are always kept available and the client must choose the right one to use based on expiration dates.

Second, choosing between these has made me realize that we haven't stated a threat model. If we're only concerned about an attacker that can MITM HTTP requests, the latter approach is fine. However, if our threat model includes someone that can crack a key given enough time (as well as MITM HTTP) only the former would suffice. The reason is that under that scenario we have no way to be sure of when a signature was made. The timestamp in the signature itself could be forged and we can't trust the date in the URL as the attacker controls that as well. So the only way to be secure is to consider an expiration the same as a revocation and resign everything with the new keys.

Third, as you pointed out, someone who has the master key can extend its expiry time at will. So if our threat model includes someone who can crack the keys we either need to communicate the expiry time out of band (e.g., code in the client that it will never be more than a year) or publish a revocation certificate when the key is allowed to truly expire.

Fourth, if revocation certificates are fetched over HTTP, requests for those could be could be intercepted to prevent clients from seeing that a cracked key has been revoked.

So I think the next step has to be to determine the threat model. That'll tell us exactly which of these are actually a concern. In my personal opinion, just caring about HTTP MITM is sufficient because if someone that can crack keys decides to go after me I'm probably already screwed. But a threat model decision has to be made considering the community needs as a whole.

You might be interested in looking at The Update Framework if you haven't seen it already. While I'm not advocating adopting it (it is probably way too complex of a beast for QL), they do a decent job describing the various attacks that can be made against software repos and their chosen solution (it involves many keys).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment