(If nothing else, this posting is bound to make me eligible for one of these lovely mugs. ;)
Has the file I installed on the far-away (or out-of-my-control) server been modified since I placed it there? Good question. If I’d kept a hash of the file at the time I created it, I could easily compare that to the file’s current hash to find out. But I do tend to forget to do that…
You know the Spiel: you install a program with one or more supporting configuration files somewhere, and somebody calls you that “it” doesn’t work. “Did you change something?” you ask. “No”, comes the answer, but because you don’t believe the answer, you tell “them” to use
sha1sum and you start comparing MD5 or SHA-nnn hashes.
What if your program could determine whether its supporting file(s) have been changed since you delivered them? What, even, if your program could refuse to use the file(s) if that occurred? This becomes more important if supporting files are scripts, say, Lua or maybe Python which, if changed, could wreak havoc. Imagine your package provides special DNSSEC trust anchors (or any other keys) in external files and these are replaced: how will your application prove that?
Code-signing comes to mind … (yuck) … but that’s easier said than done. There have been some attempts at bringing code-signing to Linux, but I believe most have petered out into nothingness. Today, typical methods for signing files or program packages in a Unix/Linux environment include the use of GnuPGP signatures, signed MD5 or SHA files, unsigned MD5 hashes, and no form of verification at all.
I’ve been known for doing some (*cough*) curious things with DNS so I thought: well, why not leverage the trust obtained through DNSSEC to implement a simple form of “file-verification”? The operative word is “simple”: the verifier must be able to work on the lowliest of CPUs – I don’t want a lot of number-crunching going on.
The scheme I’ve come up with is as follows:
A file’s signature is created by obtaining its git hash which is basically the SHA-1 of the file’s content plus a header which includes the file’s size. The hex SHA-1 digest becomes the owner name (i.e. domain name) of a TXT record containing the object’s file name in rdata. For example, the git hash of a file containing the 12 bytes of the text in file
Queried directly in the DNS, the result (RRSIGs omitted) would be:
There’s only a “reverse”-type lookup from SHA-1 to file name; a forward-type lookup (filename to hash) is not necessary.
Git’s SHA-1 hashes are easy to create, and the makegitsha.py program outputs TXT records in DNS master zone file format; changing this to, say, update a PostgreSQL/MySQL database for PowerDNS or update the DNS dynamically is easy enough.
Programs can now query the DNS, preferably via DNSSEC, to verify that files they rely on haven’t been tampered with. In order to do so a program will:
- Determine the file size and content and obtain their SHA-1 hash.
- Query the DNS for the owner name in a particular zone
- Verify the response
The program can, say, refuse to operate if it detects a configuration file’s signature doesn’t match that in the DNS. Heck, the program can even check its own signature and abort if it isn’t verifiable.
- Modified files are easy to deploy: re-hash them and publish their new signature. Existing (old) signatures can remain in the DNS to support previous versions (i.e. prior content).
- I can even deploy “remote modifications” by having an administrator change a configuration file (“add this line”) while I do an identical update and create the signature in the DNS.
- Works with any DNS server (BIND, NSD, PowerDNS, etc.).
- High TTL times on the “signature” records make for effective caching in recursive DNS resolvers.
I’ve created proof of concept as a set of C functions which implement file verification as discussed above. Basic usage is like this:
Using the example program, I can demonstrate how this works. The program allocates a data structure (
dv_alloc()) and tries to verify (
dv_valid()) a file called
reference.file, printing some of the elements of the opaque data structure (
struct dvinfo *).
- The file is signed and valid. (DNS response is
- Using the same data by a different name returns bad-signature because the SHA-1 hash is in the DNS, but the rdata contains an incorrect name. (I’m not sure a test for this is useful, though.)
- After altering the reference file, it’s marked as NOT valid because the file’s SHA-1 hash isn’t in the DNS (
example.ccontains an optional test for verifying itself. I activate that and modify the executable program
example, after which the signature is obviously unavailable in the DNS – the program aborts.
The method discussed above is not real code-signing: code-signing means I can trust an operating system to refuse to load a program that’s been tampered with. This system doesn’t provide that kind of security. If a malicious person modifies the executable program to, say, disable the file-checking function (I’m assuming it’s possible to do that), it breaks the system.
- Checking Current Application or Data Versions Using DNS
- Code Signing Certificates
OSSLsigncode is a small tool based on OpenSSL and cURL that _implements part of the functionality of Microsoft’s
signcode.exe- more exactly the Authenticode signing and timestamping.