jstedfast / gmime

A C/C++ MIME creation and parser library with support for S/MIME, PGP, and Unix mbox spools.
GNU Lesser General Public License v2.1
113 stars 36 forks source link

GMimeCertificate API is problematic when verifying signatures from certificates with multiple user IDs #103

Open dkg opened 3 years ago

dkg commented 3 years ago

The GMimeCertificate interface only presents a single (highest-validity) user ID and e-mail address.

Many certificates (both OpenPGP and X.509) have multiple user IDs and multiple e-mail addresses in them. My own OpenPGP certificate has three User IDs, two of which contain e-mail addresses:

pub   ed25519 2020-12-27 [C] [expires: 2023-12-24]
      C29F8A0C01F35E34D816AA5CE092EB3A5CA10DBA
uid            Daniel Kahn Gillmor
uid            <dkg@debian.org>
uid            <dkg@fifthhorseman.net>
sub   ed25519 2020-12-27 [S] [expires: 2023-12-24]
sub   cv25519 2020-12-27 [E] [expires: 2023-12-24]

For X.509, each subjectAltName can contain a distinct e-mail address.

When validating a signature over a particular e-mail message, one question a reasonable MUA wants to ask is "does the signature come from a certificate that has an e-mail address that matches the apparent sender of the message?"

But if the certificate information provided happens to pick the e-mail address from the wrong User ID, then it will look like a mismatch.

For example, if i send a signed e-mail from my dkg@fifthhorseman.net account, but if you happen to have my OpenPGP certificate stored in the order that it shows up here (with dkg@debian.org first), then the GMimeCertificate's email member will contain dkg@debian.org instead of the actual From: address.

How should a GMime user deal with this? I know that an API to supportmultiple e-mail addresses (and/or multiple User IDs) is more complex, as would be the storage data structure, and that would require an API change.

Another approach would be to retain some contextual information about the message being validated during signature validation, and to preferentially select e-mail addresses (of the highest validity) that do match the sending context.

This was all mentioned in the commit message d6701744522167728e074c44800e1714a39ea562 back in 2017, but i don't think anyone has done the work to provide the improved interface.

How should this be fixed?

jstedfast commented 3 years ago

My initial thoughts are to make GMimeCertificate hold a list of email addresses and keep the existing set_email/get_email() for compatibility, but to offer new set_emails()/get_emails() that would provide the full list?

But maybe each email address also needs a trust level?

I'm ok with adding fields to the GMimeCertificate even though it technically breaks ABI.

The next possibility that I can think of is to change the GPG verify logic to return a different GMimeSignature per uid?

That might confuse apps, though.

dkg commented 3 years ago

I like the idea of something like a set_emails/get_emails approach, but i do think that each User ID/e-mail address needs its own validity level (not "trust" -- in GnuPG terms, "trust" is mostly orthogonal to validity). For an X.509 certificate, all UIDs have the same validity, but for an OpenPGP certificate, the UID validity can differ within the cert.

It'd be nice to be able to reuse InternetAddressList here, but i don't see how to do that and still associate a validity with each address.

And, while I like the simplicity of leaning toward just handling e-mail addresses (this is an e-mail implementation, of course), i have a minor worry that it will trip up some use cases too. For example, some OpenPGP certificates (like 7B96D396E6471601754BE4DB53B620D01CE0C630) have a user ID that includes an e-mail address but also includes a comment in the user ID that indicates it is not supposed to be used for e-mail (at least, i think that's what Werner means when he writes (dist sig) -- that this is just for signing software distributions, not for signing mails). Maybe we don't care about those details? Werner's newer "dist sig" certificates don't include an e-mail address in the user ID at all.

If you don't care about the non-email parts, and you don't want to go through the overhead of an entirely new g_mime_cert_email_list or g_mime_cert_uid_list, i can imagine a minimal UI:

int g_mime_certfificate_get_email_count (GMimeCertificate *cert);
const char *g_mime_certificate_get_email_n (GMimeCertificate *cert, int n, GMimeValidity *validityout);
void g_mime_certificate_append_email (GMimeCertificate *cert, const char *address, GMimeValidity validity);

The next possibility that I can think of is to change the GPG verify logic to return a different GMimeSignature per uid?

This would be a mistake, because it's also possible for there to be distinct GMimeSignature objects in a single message. it'd be weird if GMime conflated the case of two signatures from two different certificates with the case of one signature from a certificate that has two user IDs.

jstedfast commented 3 years ago

Thanks for pointing out that, technically, we should have an array of user, email, and validity and not just emails.

dkg commented 3 years ago

Note that GnuPG upstream has some pretty challenging usability issues related to e-mail addresses, User IDs, and different certificate types :/

In GnuPG's model, an e-mail address is always a part of a single User ID (though not all User IDs do contain e-mail addresses), and validity is attached to the User ID itself.

For X.509, every user ID in a given certificate will have the same validity (because the cert is valid or not as a whole). But for OpenPGP the validity of each user ID (and e-mail address within it) can vary, because different identities can carry distinct certifications.

jstedfast commented 3 years ago

Interesting...

jstedfast commented 3 years ago

@dkg could you create a test message for this?