GitHub API OpenPGP key format

582 views Asked by At

What is the format of the public_key field returned from GitHub REST API v3 for GPG Keys?

For example, the command curl -v -H "Accept: application/vnd.github.cryptographer-preview" https://api.github.com/users/DurandA/gpg_keys returns the following keys:

pub   dsa2048/403094DF 2017-09-03 [SC] [expires: 2018-09-03]
uid         [ultimate] Arnaud Durand <[email protected]>
sub   elg2048/A454F414 2017-09-03 [E] [expires: 2018-09-03]

According to the API doc:

The data returned in the public_key response field is not a GPG formatted key. When a user uploads a GPG key, it is parsed and the cryptographic public key is extracted and stored. This cryptographic key is what is returned by the APIs on this page. This key is not suitable to be used directly by programs like GPG.

Is it possible to use these keys from a CLI or programmatically?

2

There are 2 answers

0
Jens Erat On BEST ANSWER

The key returned is a bare (RSA, DSA, ...) key, which cannot be used by implementations of OpenPGP without "wrapping" it in a proper OpenPGP key packet again. I would not recommend to do so, why you should be able to construct the key packet again, you will have no chance in constructing binding signatures for subkeys and user IDs (this requires access to the private keys) and will not succeed and constructing something useful therefor.

The "OpenPGP model" of sharing keys in communities is fetching a current copy from the key server network (including all current certifications and revocations) instead of relying on possibly outdated versions in "third-party-locations" like GitHub. This is possible by fingerprints and key IDs that (more or less uniquely, see below) address specific keys -- do not search for mail addresses, everybody can create keys with arbitrary user IDs and keyservers do not perform any validation.

Instead, have another look at the APIs output, which returns keyid objects for all keys (some for subkeys):

[
  {
    "id": 3,
    "primary_key_id": null,
    "key_id": "3262EFF25BA0D270",
    "public_key": "xsBNBFayYZ...",
    "emails": [
      {
        "email": "[email protected]",
        "verified": true
      }
    ],
    [snip]
  }
]

To use such a key ID, run gpg --recv-keys <key-id>. And drop GitHub a note to follow best practices and include the full fingerprint:

These 64-bit hex values (3262EFF25BA0D270 in this example) are long key IDs. While any programmatic references to keys should always include the key's fingerprint, not abbreviated key IDs, at least they do not provide short key IDs that heavily suffer under collision attacks.

0
Chih-Hsuan Yen On

As of writing, contents in public_key fields are base64-encoded OpenPGP packets, which are defined in RFC 4880. gpgpdump is useful to inspect them. For example,

$ curl -s https://api.github.com/users/DurandA/gpg_keys | jq -r '.[0].public_key' | base64 -d | ./gpgpdump
Public-Key Packet (tag 6) (814 bytes)
        Version: 4 (current)
        Public key creation time: 2017-09-04T06:53:50+08:00
                59 ac 87 fe
        Public-key Algorithm: DSA (Digital Signature Algorithm) (pub 17)
        DSA p (2048 bits)
        DSA q (q is a prime divisor of p-1) (256 bits)
        DSA g (2046 bits)
        DSA y (= g^x mod p where x is secret) (2047 bits)

As an OpenPGP key is composed of a series of OpenPGP packets, it is theoretically possible to reconstruct a key for verifying stuffs. To achieve that, an extra user ID packet and a GnuPG patch are needed. The following Python 3 script can be used to generate a user ID packet:

TAG_UID = 13

uid = '[email protected]'
# RFC 4880, Sec 4.2.1.  Old Format Packet Lengths
header = bytes([0x80 | (TAG_UID << 2), len(uid)])
packet = header + uid.encode('ascii')

sys.stdout.buffer.write(packet)

And the following GnuPG patch forces verification even if there are no signatures.

diff --git a/g10/sig-check.c b/g10/sig-check.c
index 4c172d692..eb4653535 100644
--- a/g10/sig-check.c
+++ b/g10/sig-check.c
@@ -177,7 +177,7 @@ check_signature2 (ctrl_t ctrl,
                  gnupg_compliance_option_string (opt.compliance));
       rc = gpg_error (GPG_ERR_PUBKEY_ALGO);
     }
-  else if (!pk->flags.valid)
+  else if (0)
     {
       /* You cannot have a good sig from an invalid key.  */
       rc = gpg_error (GPG_ERR_BAD_PUBKEY);

Anyway, as there are no self-signatures, the verification result should not trusted.