How can I validate GPG signatures in pure Rust?


#1

[multirust-rs] (https://github.com/Diggsey/multirust-rs) eventually should validate its downloads using the Rust signing key. I don’t want to deal with the unreliability of hoping gpg exists locally or of bundling more C components, so I want to use a Rust solution. It doesn’t look to me like there’s a pure-Rust crate specifically for this, so what needs to happen to do basic GPG signature validation in Rust?

multirust-rs already has access to OpenSSL, so I can reuse code from that, but I would prefer pure-Rust.

Here’s the pgpdump of the Rust pubkey, if that helps identify exactly which crypto is needed.

I seriously don’t know much here. Please tell me exactly what needs to happen.


Help with rustup signature verification / TUF
#2
  1. A production-ready RSA implementation. There isn’t one in pure Rust, AFAIK. Since rust-openssl exposes RSA, it’s probably best to use that.

  2. Hash functions, which exist, e.g. in rust-crypto.

  3. The mechanics of signature verification itself: key parsing, hash and signature calculation etc. Doesn’t exist yet, but given 1) and 2) above, should be doable without extraordinary effort.


#3

Thanks for the advice. It sounds like the crypto pieces are available. Perhaps the next thing to do is read the PGP RFCS and write a parser for its formats.


#4

If you’re willing to go with a non-pure-rust solution, you can use ring which will do the RSA bits as shown in this example from the documentation: https://briansmith.org/rustdoc/ring/signature/fn.verify.html#verify-a-rsa-pkcs1-signature-that-uses-the-sha-256-digest

use ring::input::Input;
use ring::signature;

fn verify_rsa_pkcs1_sha256(public_key: &[u8], msg: &[u8], sig: &[u8])
                           -> Result<(), ()> {
   let public_key = try!(Input::new(public_key));
   let msg = try!(Input::new(msg));
   let sig = try!(Input::new(sig));
   signature::verify(&signature::RSA_PKCS1_2048_8192_SHA256_VERIFY,
                     public_key, msg, sig)
}

Notice that it takes care of the hashing and the minimum/maximum key size checks automatically.

Also, the ring::input submodule makes it relatively easy to safely parse formats like OpenPGP’s. However, OpenPGP is not a great format for any application, so I recommend that you find a simpler format.

Timing side channels and stuff like that don’t matter for verification, and the performance is unlikely to matter either. However, there are a lot of other issues that do matter and which are easy to get wrong.

If feasible, I recommend that you switch to Ed25519, ECDSA-P-256-SHA-256, or ECDSA-P-384-SHA-384, instead of using RSA. There are fewer things to get wrong with the ECC algorithms compared to RSA, assuming the math is implemented correctly in all cases.


#5

I am not sure how the RSA vs. ECC story turns out. RSA offline signatures are actually pretty robust - just be sure you verify with create-signature-and-compare rather than by extracting the signature and you are golden (with online signatures, you have all the side-channel attacks to care about, but these are on the signing side, not the verifying side).

Of course, Ed25519 is excellent too. Do we have a good implementation?


#6

Actually, it’s pretty likely that you’d have to use RSA PKCS#1 1.5 anyway because that’s probably all that’s supported by the hardware security module that you’d generate & store the private key in.

just be sure you verify with create-signature-and-compare rather than by extracting the signature and you are golden

It is nicer to do that, but in practice there may be other considerations (in particular, the need to minimize stack usage and/or avoid heap allocations) that shift things the other way. Regardless, I agree that it is important to be careful with the padding part. (And other parts.)


#7

In case you want to use Ed25519 anyway, ring has an implementation that it shares with BoringSSL and OpenSSL and which it exposes using the same API as RSA, as documented at https://briansmith.org/rustdoc/ring/signature/fn.verify.html#verify-an-ed25519-signature (Just subsitute signature::ED25519_VERIFY for signature::RSA_PKCS1_2048_8192_SHA256_VERIFY).

libsodium has its own implementation. libsodium has Rust bindings called Sodium Oxide.

The main problem with both of these implementations is that they are hard to read and thus very few people have verified that they are correct, except by doing black-box-like testing on them. In general, lack of testing and lack of sufficient code review are significant risks with any implementation.


#8

@brson is writing a package manager. I doubt that mallocing a few kilobytes of RAM will make a difference. I would not like to play with signature extraction unless I was writing some high-performance protocol parser.


#9

@briansmith Thanks for the ring example! I will definitely look into that.

My impression has been that RSA is the IBM of crypto - tried and tested. Last time I generated a GPG key it’s what GPG itself recommended. My worry about using anything else would mostly be that we’re doing something different than established practice. Secondarily, I’m reluctant to generate another signing key and invalidate the current one.


#10

What alternatives are there? If we went with a different format then it will be harder for others to validate signatures and we might want to sign our artifacts twice, once with gpg and once with whatever method rustup uses?


#11

I don’t know of any good standardized “envelope” formats - OpenPGP’s is perhaps the sanest. The problem is that these formats are bikesheddable to the extreme, so every time someone tries to come up with one (ASN.1, XML encryption) a committee forms and tries to add every possible feature (and there are reasons to believe that the NSA comes along to their help).

The best way to handle the craziness here is to decode the data (using a safe language and no secrets, but that’s a given here) to some un-crazy format, encode it using some injective function, and compare that to the signature. If you have a library that does that, you should be fine.


#12

I would strongly prefer Ed25519 over RSA given the choice. The advantages are:

  • Much faster signatures.
  • Much smaller public and private keys.
  • High-performance implementation that is not vulnerable to timing attacks. RSA is much harder to implement without side-channel vulnerabilities. This is a problem for signing.
  • Faster verification (vs. RSA) at 128-bit security level.
  • Completely deterministic signatures.
  • Harder to misuse. No need to worry about RSA PKCS#1 1.5 vs RSA-PSS. Ed25519 has no need for padding.
  • libsodium already has Rust bindings (sodiumoxide on crates.io) and is extremely simple to use.

However, in your case, you are verifying an RSA key. I second the suggestion to use ring. It is based on the same software (BoringSSL) as used by Google production servers.


#13

FWIW, I wish that Rust used native .deb/.rpm/.msi/etc. formats so that the existing software on my computer can deal with the package management.

However, if you’re going to use a custom format you might consider using Mozilla’s own MAR file format, which was designed to be extremely simple to process.

If you want to optimize for others being able to validate the signatures, then distributing .deb/.rpm/.msi/etc. will do that.


#14

You will continue to have these options. Rust will still produce individual msis, and Debian will still package debs. Rust might eventually produce their own universal debs, but it’s a low priority.

I intend to distribute rustup itself as an msi too.