micahflee / sigbin

Other
2 stars 0 forks source link

Failure to recognize imported keys with non-standard subkey layouts #5

Open garrettr opened 8 years ago

garrettr commented 8 years ago

STR

  1. Create a GPG key that uses separate subkeys for signing and certification.
  2. Submit a message with this key.
  3. Sigbin redirects you to the homepage with a flashed error notification: "Failed to import signing key from key server"
  4. Wait a few seconds, then try again with the same message.
  5. This time, verifying the signature succeeds, and you are redirected to /update/1. This indicates that the signing key was successfully imported from the key server after all.

    Cause

I have a key with the following subkeys:

$ gpg --edit-key 0xAB85B8259D8CEFF1
gpg (GnuPG/MacGPG2) 2.0.28; Copyright (C) 2015 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

pub  4096R/0xAB85B8259D8CEFF1  created: 2015-05-15  expires: 2020-05-13  usage: C   
                               trust: ultimate      validity: ultimate
sub  4096R/0xE0AF3051294E1E5B  created: 2015-05-15  expires: never       usage: S   
sub  4096R/0xA6F916DB5C00B5D9  created: 2015-05-15  expires: never       usage: E   
sub  4096R/0x440912775128B241  created: 2015-05-15  expires: never       usage: A   
[ultimate] (1). Garrett Robinson <garrett@freedom.press>
[ultimate] (2)  Garrett Robinson <garrett.f.robinson@gmail.com>
[ultimate] (3)  Garrett Robinson <garrettr@riseup.net>

Note that I have separate subkeys for Certification and Signing. This is valid and common among those that use a smartcard setup that separates their primary key from their subkeys. However, it is not the default subkey layout that is created by gpg --gen-key or any of the GUI key generation tools (e.g. Enigmail, GPGTools) that I know of. Most of these tools use the same subkey for signing and certification, e.g my old GPG key:

$ gpg --edit-key 0x464F0A89D3EF9CAE
gpg (GnuPG/MacGPG2) 2.0.28; Copyright (C) 2015 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

pub  4096R/0x464F0A89D3EF9CAE  created: 2013-09-04  expires: 2018-09-03  usage: SCA 
                               trust: ultimate      validity: ultimate
*** This key has been disabled
sub  4096R/0x0C2CC50511AD9BEC  created: 2013-09-04  expires: 2018-09-03  usage: E   
[ultimate] (1). Garrett Robinson <garrett@freedom.press>
[ultimate] (2)  Garrett Robinson <garrettr@riseup.net>
[ revoked] (3)  Garrett Robinson (Work) <grobinson@mozilla.com>
[ultimate] (4)  Garrett Robinson (FPF) <garrett@pressfreedomfoundation.org>

The problematic code here is:

        # Do we have the signing key?
        if "Can't check signature: No public key" in err or "Can't check signature: public key not found" in err:
            keyid = ''
            for line in err.split('\n'):
                if line.startswith('gpg: Signature made'):
                    keyid = line.split()[-1]
            if re.match(r'^[a-fA-F\d]{8}$', keyid):
                # Try to fetch the signing key from key server
                out, err = self._gpg(['--recv-keys', keyid])

                if "key {} not found on keyserver".format(keyid) in err:
                    return ("The signing key was not found on key servers", None)
                else:
                    import_success = False
                    for line in err.split('\n'):
                        if line.startswith('gpg: key {}: public key "'.format(keyid)) and line.endswith('" imported'):
                            import_success = True

                    if not import_success:
                        return ('Failed to import signing key from key server', None)

Specifically, trying to verify that the imported keyid is the same as the signing subkey keyid. This is a valid assumption for many but not all GPG keys.

Here are some logs from an example STR:

127.0.0.1 - - [22/Sep/2015 12:34:40] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [22/Sep/2015 12:34:40] "GET /favicon.ico HTTP/1.1" 200 -
stderr gpg: keyring `/Users/garrett/code/github.com/firstlook/sigbin/homedir/pubring.gpg' created
gpg: Signature made Tue Sep 22 12:35:06 2015 PDT using RSA key ID 294E1E5B
gpg: Can't check signature: No public key

stderr gpg: keyring `/Users/garrett/code/github.com/firstlook/sigbin/homedir/secring.gpg' created
gpg: requesting key 294E1E5B from hkp server pool.sks-keyservers.net
gpg: /Users/garrett/code/github.com/firstlook/sigbin/homedir/trustdb.gpg: trustdb created
gpg: key 9D8CEFF1: public key "Garrett Robinson <garrett@freedom.press>" imported
gpg: no ultimately trusted keys found
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)

As you can see, the keyid that made the signature (the S subkey) is not the same as the keyid of the key itself (the C subkey). This causes the check of the imported keyid to fail, even though the key was successfully imported. This check should be modified to work even if the signing subkey and the certifying subkey are not the same.

micahflee commented 8 years ago

Excellent catch.