Skip to content

PGP Key Generation Issue in v8 - Missing Signature Section #47

@ArcRiiad

Description

@ArcRiiad

Hello Guys!

First, thanks for this awesome project! You rock!

We've discovered a notable issue with the pgp-generate command in v8. When generating a public GPG key, the serialized key unexpectedly lacks the signature section, which consequently results in an invalid public key. Here is the configuration used

tokens:
  gcloud:
    type: gcloud

keys:
  default:
    token: gcloud
    id: projects/sandbox/locations/global/keyRings/testing/cryptoKeys/relic/cryptoKeyVersions/4
    pgpcertificate: /tmp/mykey.pgp

Here is the executed command to generate the GPG key:

> relic pgp-generate -k default -n "pouet" -t "gcloud" > /tmp/mykey.pgp

The public key generated by the V8

> gpg --list-packets /tmp/mykey.gpg 
# off=0 ctb=c6 tag=6 hlen=3 plen=525 new-ctb
:public key packet:
        version 4, algo 1, created 1732617837, expires 0
        pkey[0]: [4096 bits]
        pkey[1]: [17 bits]
        keyid: 3BBEB9F6CC4EBE49
# off=528 ctb=cd tag=13 hlen=2 plen=5 new-ctb
:user ID packet: "pouet"

And when we try to sign using the key, we encounter this signing error.

$> relic sign-pgp -s -u default -b /tmp/pouet --digest-algo SHA-512
ERROR: openpgp: invalid data: entity without any identities

We've attempted resolving this by replacing github.com/ProtonMail/go-crypto/openpgp with golang.org/x/crypto/openpgp in cmdline/token/newpgpkeycmd.go, and this approach successfully serialized the key.

package token

import (
	...
	"golang.org/x/crypto/openpgp"  
	"golang.org/x/crypto/openpgp/armor"  
	"golang.org/x/crypto/openpgp/packet"
	...
)
# off=0 ctb=c6 tag=6 hlen=3 plen=525 new-ctb
:public key packet:
        version 4, algo 1, created 1732618389, expires 0
        pkey[0]: [4096 bits]
        pkey[1]: [17 bits]
        keyid: 847B069B51CE53F9
# off=528 ctb=cd tag=13 hlen=2 plen=5 new-ctb
:user ID packet: "pouet"
# off=535 ctb=c2 tag=2 hlen=3 plen=546 new-ctb
:signature packet: algo 1, keyid 847B069B51CE53F9
        version 4, created 1732618389, md5len 0, sigclass 0x13
        digest algo 10, begin of digest 49 51
        hashed subpkt 2 len 4 (sig created 2024-11-26)
        hashed subpkt 16 len 8 (issuer key ID 847B069B51CE53F9)
        hashed subpkt 27 len 1 (key flags: 03)
        hashed subpkt 25 len 1 (primary user ID)
        data: [4096 bits]

While investigating the serialization differences between ProtonMail and the deprecated OpenPGP from Golang Crypto, I discovered a significant distinction; In golang.org/x/crypto/openpgp, the Serialize method of the entity explicitly serializes the SelfSigned signature of identities.

//File: https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.29.0:openpgp/keys.go

func (e *Entity) Serialize(w io.Writer) error {
	...
	for _, ident := range e.Identities {
		err = ident.UserId.Serialize(w)
		if err != nil {
			return err
		}
		err = ident.SelfSignature.Serialize(w) // <= SelfSignature Serialize
		if err != nil {
			return err
		}
		for _, sig := range ident.Signatures {
			err = sig.Serialize(w)
			if err != nil {
				return err
			}
		}
	}
	...
}

However in the github.com/ProtonMail/go-crypto/openpgp implementation, the serialize function only serializes Signatures

//File: https://github.com/ProtonMail/go-crypto/blob/5521d835096caef67f37fdad5bdc8f276d999747/openpgp/keys.go

func (e *Entity) Serialize(w io.Writer) error {
	...
	for _, ident := range e.Identities {
		err = ident.UserId.Serialize(w)
		if err != nil {
			return err
		}
		for _, sig := range ident.Signatures {
			err = sig.Serialize(w)
			if err != nil {
				return err
			}
		}
	}
	...
}

Afterward, we updated the makeKey function within cmdline/token/newpgpkeycmd go by adding the SelfSignatures to the Signatures variable within Entity, and the key was subsequently generated properly.

//File: https://github.com/sassoftware/relic/blob/master/cmdline/token/newpgpkeycmd.go

func makeKey(key token.Key, uids []*packet.UserId) (*openpgp.Entity, error) {
	...
	entity.Identities[uid.Id] = &openpgp.Identity{  
	    Name:          uid.Name,  
	    UserId:        uid,  
	    SelfSignature: sig,  
	    Signatures:    []*packet.Signature{sig},  // <= Addition
	}
	...
}

The generated key after the patch

# off=0 ctb=c6 tag=6 hlen=3 plen=525 new-ctb
:public key packet:
        version 4, algo 1, created 1732619542, expires 0
        pkey[0]: [4096 bits]
        pkey[1]: [17 bits]
        keyid: EACC6CEE13FAE20B
# off=528 ctb=cd tag=13 hlen=2 plen=5 new-ctb
:user ID packet: "pouet"
# off=535 ctb=c2 tag=2 hlen=3 plen=569 new-ctb
:signature packet: algo 1, keyid EACC6CEE13FAE20B
        version 4, created 1732619542, md5len 0, sigclass 0x13
        digest algo 10, begin of digest 3d 95
        hashed subpkt 2 len 4 (sig created 2024-11-26)
        hashed subpkt 16 len 8 (issuer key ID EACC6CEE13FAE20B)
        hashed subpkt 33 len 21 (issuer fpr v4 09908E29E0DB49F16C76CAEFEACC6CEE13FAE20B)
        hashed subpkt 27 len 1 (key flags: 03)
        hashed subpkt 25 len 1 (primary user ID)
        data: [4095 bits]

And the sign-pgp works properly

> relic sign-pgp -s -u default -b /tmp/pouet --digest-algo SHA-512 > /tmp/pouet.asc
Signed /tmp/pouet

So we have two question:

  • Is this behavior expected on your side? Do you want us to open a PR that adds the identity signature to the Signatures?
  • We've also noticed that the pub signature hash is explicitly set to SHA512 within the pgp-generate. Is it possible to auto-detect the pub hash based on the private key, or at least provide a flag that allows users to specify the hash from the command line?

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