Skip to content

PGP public keys #381

Open
Open

Description

@silverpill

Is it possible to define a codec for PGP / GPG public keys?

The most recent OpenPGP specification seems to be RFC 9580 (published: July 2024).

Activity

  1. rvagg commented on May 26, 2025

    @rvagg
    Member

    Sure, but you'd probably want to define a usecase to drive the addition to the table rather than just adding to the table because we can. Maybe that usecase is already covered by some other entry, or maybe there is a good case for it. If you have code or a spec that wants it then that's much more compelling.

  2. silverpill commented on May 26, 2025

    @silverpill
    Author

    I am interested in creating Multikey objects that represent PGP keys. This is useful on its own, because these objects can be attached to other JSON documents, but it would also be the first step towards making PGP compatible with the Data Integrity web standard.

    Another possible use case: generation of a decentralized identifier (DID) from a PGP key (of did:key type).

  3. tesaguri commented on May 29, 2025

    @tesaguri

    I'm also interested in using my OpenPGP smart card as a multikey. But I also wonder if it would be sufficient to skip the OpenPGP protocol and identify the raw public key components with existing multicodecs.

    In GnuPG, it seems that you can export public keys as SSH keys (which in turn can be converted to PEM/DER):

    $ ssh-keygen -f <(gpg --export-ssh-key 'AA0FDB57D0C667DF804641977D0FB0D9068F977A!') -e -m pem | openssl pkey -pubin -outform der | base64
    MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxGbaTBcUWiWt194hO2Ei…

    (Unfortunately, the ssh-keygen command doesn't seem to support Ed25519 keys as of OpenSSH_9.9p2, but you can convert Ed25519 keys to PEM with a manual coding at least.)

    Also, the GnuPG agent protocol has the PKSIGN command, with which you can produce signatures for arbitrary hashes (verifiable by dependent crypto libraries).

    Example usage
    $ FINGERPRINT=F706F9BBB8F63CDE5F40298FDF5EB02BE5C2542E
    $ KEYGRIP=EFF4BC5C63B16808DA29076D3246F53CC79EA481
    $ gpg --export-ssh-key "$FINGERPRINT!" > key.pub
    $ python3 - "$(gpgconf --list-dirs agent-socket)" "$KEYGRIP" hello.txt <<PYTHON
    import re
    import sys
    
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import padding, utils
    from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
    from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
    from pyassuan import client, common
    
    sock = sys.argv[1]
    sigkey_grip = sys.argv[2]
    in_file = sys.argv[3]
    
    # Hash and sign the file:
    
    hash_algo = hashes.SHA256()
    hasher = hashes.Hash(hash_algo)
    with open(in_file, 'rb') as f:
        while (chunk := f.read(1024)):
            hasher.update(chunk)
    hash_bytes = hasher.finalize()
    
    client = client.AssuanClient('')
    client.connect(sock)
    try:
        assert client.read_response().type == 'OK'
    
        client.make_request(common.Request('SIGKEY', sigkey_grip))
        client.make_request(common.Request('SETHASH', '--hash=sha256 ' + hash_bytes.hex()))
        res = client.make_request(common.Request('PKSIGN'))
        assert res
    
        sig = res[1]
        assert sig is not None
        # Should be replaced with a proper csexp parser in a serious application.
        match = re.match(b'\\A\\(7:sig-val\\(\\d+:[^(]+(?:\\(1:r\\d+:(.+)\\))?\\(1:s\\d+:(.+)\\)\\)\\)\\Z', sig, re.DOTALL)
        assert match
        sig = (match[1] or b'') + match[2]
    finally:
        client.make_request(common.Request('BYE'))
        client.disconnect()
    
    # Verify the signature:
    
    with open('key.pub', 'rb') as key_file:
        pubkey = serialization.load_ssh_public_key(key_file.read())
    
    if isinstance(pubkey, Ed25519PublicKey):
        pubkey.verify(sig, hash_bytes)
    elif isinstance(pubkey, RSAPublicKey):
        algorithm = utils.Prehashed(hash_algo)
        padding = padding.PKCS1v15()
        pubkey.verify(sig, hash_bytes, padding=padding, algorithm=algorithm)
    else:
        raise Exception(f'Unsupported public key type: {pubkey.__class__.__name__}')
    PYTHON

    I have successfully tested it with my Ed25519 key on a Nitrokey and RSA key on an on-disk keyring.

    The agent protocol's support for signing arbitrary hashes suggests that the underlying OpenPGP smart card application supports the operation too, and in fact, the spec explicitly states in 7.2.10 PSO: COMPUTE DIGITAL SIGNATURE that:

    It is possible to use the command outside of the OpenPGP environment (e. g. S-MIME), […]

    So this seems to be an intended use of OpenPGP smart cards rather than an accidentally possible hack.

  4. rvagg commented on May 30, 2025

    @rvagg
    Member

    We already have:

    • rsa-pub / rsa-priv
    • ed25519-pub / ed25519-priv
    • p256-pub / p384-pub / p521-pub
    • secp256k1-pub / secp256k1-priv
    • x25519-pub / x25519-priv

    Plus we have a multikey entry. What additional entries are you anticipating necessary for your use case(s) that aren't covered here. Can you suggest some additional entries so we can discuss in more concrete terms because it's not obvious to me at a glance what additional would be required in the table.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions