After my post to -core about how to move ahead with signing, I thought
the next best place to continue is in a discussion of how Portage
handles manifests and their signatures.
First, the blatantly obvious, for the benefit of same developers, even
though it's not relevant to signing. It is still a weak-point and does
need to be addressed. Multiple-hashes!
Ok, so Wang et al. showed you can break MD5 about an hour, on modern
machines, in a specific fashion. Their paper also discussed the effects
of their new mathematical work had on SHA1, which was making it
significantly easier to break. However, the nature of the math is that
it's still too computationally intensive for anybody but NSA-type
attackers to defeat both SHA1 and MD5 at the same time.
Simple solution - just put both hashes into the Manifest/digest.
Now onto the real stuff. The existing Manifest system has a very
specific problem in ensuring the trustworthy-ness of files.
It's quickest to explain via example.
1. cat-foo/bar has a complete, signed Manifest, and all of the content
2. Dev A commits a change to the package, the Manifest is regenerated,
and Dev A doesn't sign it.
3. Dev B goes commits something to the package, maybe not even to the
same file. However Dev B does
sign the newly generated Manifest.
The change made by Dev A was not certified via Dev A's signature,
however when Dev B came to change it, it suddenly became
trusted/certified code. Now what if Dev A was an attacker with a stolen
SSH key? And worse, Dev B didn't actually check the ebuild, as he was
using ekeyword to stable a lot of packages? (ignoring the fact that
many-eyes should have looked at the ebuild, but really, how long will
it take before the change it actually noticed, esp. if there is no
accompanying version bump).
Everybody on board with the problem?
Now a moments discussion about a solution.
One of my goals in examining signing is to make true end-to-end
verification via checksums+signing possible - to avoid any and all forms
of injection attacks going unnoticed.
Once the entire tree is signed, then the process of development will be
altered towards the following:
1) [dev] cvs up + verify all incoming changes are signed (if paranoid)
2) [dev] work on something, repoman ci with initial Manifest.
3) [dev] signed manifest commited.
4) [cvs server] queue newly signed manifest for verification
5) [cvs server - async] verify all newly signed manifests, if one
fails, block CVS -> rsync update to allow manual fix/revert. (Yes, this
7) [user] emerge --sync
8) [user] emerge foobar - and everything is verified as signed
Solving the problem isn't hard. All we need is to consider each change
to a Manifest separately.
Ergo, instead of a Manifest being re-generated each time, it needs to
act like a FIFO queue.
Each queue element consists of:
- checksum/existing Manifest element of items changed in that action ONLY (however it should be possible to forcibly include files).
- Signature around the above checksum.
So now the new Manifest structure looks roughly like this (abbreviated):
This has one important implication for backwards compatibility in
checking of Manifests.
In the case that a filename appears more than once in the file, only
the last instance of it should be used, as that is the one that relates
to the current version of the file. It's 4 lines of code in the current
portage that need to be removed for this to work (see my -core post for
Generation of the above is reasonably simple, just make the checksums in
a string, clear-sign via gpg separately/tmpfile, append to Manifest.
There is one last part that does need to be taken care of.
At what point is it safe to remove a checksum/signature from the
You cannot remove a single checksum from inside a signature block, as
that invalidates the signature.
So instead, now we need to wait for the existence more recent
checksums/signatures to exist for every item in a block before the old
block can be removed
This comes into play again as us needing to be able to force recreation
of checksums/signatures - as the key that did the original signature
might no longer be valid (for various reasons). So repoman needs to
have some option to do:
repoman forcesign $FILES
There were some concerns about the speed of Manifest checking.
I did some simple benchmarking, using all existing signed Manifests
(40% of the tree). Using a Pentium-D 3Ghz, running on only one core, it
took 43 seconds to check all 4000 Manifests. 90% of the time was in the
setup/teardown for gpg.
Getting around the setup/teardown time problem basically means we need
something of our own to interface with the gpg API (gpgme). This is not
too hard, GPG ships with an example that is most of the way there
already - 'gpgv'.
I'm comfortable with the GPG/GPGME codebase, and writing crypto-related
code, so I'm going to tackle that, starting after I've eaten dinner
Even still, re-checking every digest in the tree should not happen on
every CVS->rsync window. It's computationally pointless. Just check the
Robin Hugh Johnson
E-Mail : email@example.com
GnuPG FP : 11AC BA4F 4778 E3F6 E4ED F38E B27B 944E 3488 4E85