Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/crypto/openpgp: cannot encrypt a message to key id 83378a94fa6c4994 because it has no encryption keys #26468

Closed
ghost opened this issue Jul 19, 2018 · 20 comments
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@ghost
Copy link

ghost commented Jul 19, 2018

What version of Go are you using (go version)?

go version go1.10.2 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOBIN=""
GOCACHE="/home/rbt/.cache/go-build"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/rbt/go"
GORACE=""
GOROOT="/home/rbt/go1.10"
GOTMPDIR=""
GOTOOLDIR="/home/rbt/go1.10/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build778390304=/tmp/go-build -gno-record-gcc-switches"

What did you do?

I ran this program that I wrote. It encrypts a file to PGP keys, but some keys fail with the error below even though they are valid and have encryption sub-keys.

If possible, provide a recipe for reproducing the error.


import (
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/user"
	"path/filepath"

	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/packet"
)

func main() {
	filePath := os.Args[1]

	usr, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}

	// Read Public Key files from /home/user/keys
	// These keys **are not** ascii armored
	// gpg --export 83378a94fa6c4994 > /home/rbt/keys/phil.pub.key
	keyFiles, err := ioutil.ReadDir(usr.HomeDir + "/keys/")
	if err != nil {
		log.Fatal(err)
	}

	entities := []*openpgp.Entity{}

	for _, keyFile := range keyFiles {
		log.Print("Using key file: " + usr.HomeDir + "/keys/" + keyFile.Name())
		kf, err := os.Open(usr.HomeDir + "/keys/" + keyFile.Name())
		if err != nil {
			log.Fatalf("Open pub.key %s\n", err)
		}
		defer kf.Close()

		keyReader := packet.NewReader(kf)
		theEntity, err := openpgp.ReadEntity(keyReader)
		if err != nil {
			log.Fatalf("ReadEntity %s\n", err)
		}

		entities = append(entities, theEntity)
	}

	hints := &openpgp.FileHints{
		IsBinary: true,
	}

	for _, entity := range entities {
		log.Printf("Encrypting to Key FP: %X", entity.PrimaryKey.Fingerprint)
	}

	efilePath := "/tmp/" + filepath.Base(filePath) + ".gpg"
	out, err := os.Create(efilePath)
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()

	packetConfig := &packet.Config{
		DefaultCipher: packet.CipherAES256,
	}

	wc, err := openpgp.Encrypt(out, entities, nil, hints, packetConfig)
	if err != nil {
		log.Fatal(err)
	}
	defer wc.Close()

	plainTextFile, err := os.Open(filePath)
	if err != nil {
		log.Fatal(err)
	}
	defer plainTextFile.Close()

	buf := make([]byte, 64*1024)
	n, err := io.CopyBuffer(wc, plainTextFile, buf)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("%d bytes read into wc.\n", n)

	plainTextFile.Close()
	wc.Close()
	out.Close()
}

What did you expect to see?

I expected the file to be encrypted. The key is valid and has an encryption sub-key. And, many other keys work just fine, but several do not.

2018/07/19 09:45:19 Using key file: /home/rbt/keys/brad.pub.key
2018/07/19 09:45:19 Using key file: /home/rbt/keys/itso.pub.key
2018/07/19 09:45:19 Using key file: /home/rbt/keys/jeff.pub.key
2018/07/19 09:45:19 Using key file: /home/rbt/keys/phil.pub.key
2018/07/19 09:45:19 Using key file: /home/rbt/keys/tester.pub.key
2018/07/19 09:45:19 Encrypting to Key FP: 83CBAF6B683329125FE436CCE915EE8B2FE6EC56
2018/07/19 09:45:19 Encrypting to Key FP: F3D2F6714EF6B251BDFF18947279C76A0FAC6413
2018/07/19 09:45:19 Encrypting to Key FP: 4952772637B2B44012070E47B87FE76E05BAA569
2018/07/19 09:45:19 Encrypting to Key FP: 5CD5EFA3E1C520B1B0EDE38C83378A94FA6C4994
2018/07/19 09:45:19 Encrypting to Key FP: E2958B99360A0F93AD440FD01E7854496A3E0199
2018/07/19 09:45:19 openpgp: invalid argument: cannot encrypt a message to key id 83378a94fa6c4994 because it has no encryption keys

What did you see instead?

openpgp: invalid argument: cannot encrypt a message to key id 83378a94fa6c4994 because it has no encryption keys

Get the key that causes the error

This is a public key on public key servers. You can download it and re-create the issue.

gpg --recv-key 83378a94fa6c4994
gpg: key 83378A94FA6C4994: 171 signatures not checked due to missing keys
gpg: key 83378A94FA6C4994: 1 bad signature
gpg: key 83378A94FA6C4994: "Phillip E Benchoff <benchoff@n3pb.org>" not changed
gpg: Total number processed: 1
gpg:              unchanged: 1
@bradfitz bradfitz changed the title cannot encrypt a message to key id 83378a94fa6c4994 because it has no encryption keys x/crypto/openssh: cannot encrypt a message to key id 83378a94fa6c4994 because it has no encryption keys Jul 19, 2018
@gopherbot gopherbot added this to the Unreleased milestone Jul 19, 2018
@ghost ghost changed the title x/crypto/openssh: cannot encrypt a message to key id 83378a94fa6c4994 because it has no encryption keys x/crypto/openpgp: cannot encrypt a message to key id 83378a94fa6c4994 because it has no encryption keys Jul 19, 2018
@bcmills bcmills added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jul 19, 2018
@bcmills
Copy link
Contributor

bcmills commented Jul 19, 2018

CC @FiloSottile @agl

@Merovius
Copy link
Contributor

i tried reproducing this, but couldn't. This is what I tried:

export GNUPGHOME=$(mktemp -d)
gpg --keyserver=pgp.mit.edu  --recv-key 83378a94fa6c4994
mkdir ~/keys
gpg --export 83378a94fa6c4994 > ~/keys/foo.pub.key
echo "Hello world" > foo.txt
go run repro.go foo.txt

Output:

2018/09/27 02:11:33 Using key file: /home/mero/keys/foo.pub.key
2018/09/27 02:11:33 Encrypting to Key FP: 5CD5EFA3E1C520B1B0EDE38C83378A94FA6C4994
2018/09/27 02:11:33 12 bytes read into wc.

This is with go version go1.11 linux/amd64 and golang/crypto@0e37d00


@w8rbt Can you try to reproduce this on go1.11, the latest version of x/crypto and with a fresh GNUPGHOME? It would help to make sure that this isn't related to your config (though I'm having trouble coming up with a scenario where that would cause this).

@ghost
Copy link
Author

ghost commented Sep 27, 2018

Thanks @Merovius Yes, I still see the issue using the steps you outlined. I updated to go1.11 and ensured that I have the most recent x/crypto/openpgp code. As you can see, when I remove that key it works. When I add it it fails. I can produce other keys that cause failure too.

Of note, I can't reproduce this on Ubuntu, but can on Debian. Not sure why as in both cases I compile go from source obtained from golang.org and use go get -u golang.org/x/crypto/openpgp

$ cat /etc/issue
Debian GNU/Linux 9 \n \l

$ gpg --list-keys
/tmp/tmp.souqMPzkpT/pubring.kbx

pub rsa3072 2011-10-05 [SC] [expires: 2021-10-02]
5CD5EFA3E1C520B1B0EDE38C83378A94FA6C4994
uid [ unknown] Phillip E Benchoff benchoff@n3pb.org
uid [ unknown] Phillip E Benchoff benchoff@vt.edu
uid [ unknown] Phillip E Benchoff benchoff@bev.net
uid [ unknown] Phillip E Benchoff n3pb@blacksburgskywarn.org
uid [ unknown] [jpeg image of size 3840]
sub rsa3072 2011-10-05 [E] [expires: 2020-12-05]

$ go version
go version go1.11 linux/amd64

$ go run repro.go foo.txt
2018/09/26 21:57:26 Using key file: /home/rbt/keys/brad.pub.key
2018/09/26 21:57:26 Using key file: /home/rbt/keys/foo.pub.key
2018/09/26 21:57:26 Encrypting to Key FP: 83CBAF6B683329125FE436CCE915EE8B2FE6EC56
2018/09/26 21:57:26 Encrypting to Key FP: 5CD5EFA3E1C520B1B0EDE38C83378A94FA6C4994
2018/09/26 21:57:26 openpgp: invalid argument: cannot encrypt a message to key id 83378a94fa6c4994 because it has no encryption keys
exit status 1

$ rm /home/rbt/keys/foo.pub.key

$ go run repro.go foo.txt
2018/09/26 21:57:41 Using key file: /home/rbt/keys/brad.pub.key
2018/09/26 21:57:41 Encrypting to Key FP: 83CBAF6B683329125FE436CCE915EE8B2FE6EC56
2018/09/26 21:57:41 12 bytes read into wc.

@Merovius
Copy link
Contributor

Of note, I can't reproduce this on Ubuntu, but can on Debian. Not sure why

Just to be absolutely sure: Can you check that you run your self-built version of go (i.e. that $GOROOT/bin is in $PATH, easiest to check with which go) under Debian? Because I'm really having trouble coming up with a reason why this would depend on the distro, for a source-built Go - whereas I can totally imagine that Debian either ships an older version of Go or patches out some algorithms it deems problematic.

@Merovius
Copy link
Contributor

@w8rbt Can you maybe upload an exported public key file which reproduces this issue? i.e. don't send it to a keyserver, but upload a single foo.pub.key that fails to work somewhere. I have a suspicion of what the bug is - and I think a good candidate of a culprit either way would be different encodings used by the gpg versions in Debian and Ubuntu (e.g. one of them being aliased to gpg2?). Either way, if we get the actual file without re-encoding by the keyserver, we can eliminate more outside noise :)

@ghost
Copy link
Author

ghost commented Sep 27, 2018

OK @Merovius here's a different key and the original one exported from the Debian 9 box that has the issue. Also, I am using the go binary built from golang.org on all my machines.

$ which go
/home/rbt/go1.11/bin/go

$ go run repro.go foo.txt
2018/09/27 09:30:42 Using key file: /home/rbt/keys/brad.pub.key
2018/09/27 09:30:42 Using key file: /home/rbt/keys/defreeu.pub.key
2018/09/27 09:30:42 Using key file: /home/rbt/keys/deyoung.pub.key
2018/09/27 09:30:42 Using key file: /home/rbt/keys/itso.pub.key
2018/09/27 09:30:42 Using key file: /home/rbt/keys/jeff.pub.key
2018/09/27 09:30:42 Using key file: /home/rbt/keys/jerad.pub.key
2018/09/27 09:30:42 Using key file: /home/rbt/keys/pdk.pub.key
2018/09/27 09:30:42 Using key file: /home/rbt/keys/shane.pub.key
2018/09/27 09:30:42 Encrypting to Key FP: 83CBAF6B683329125FE436CCE915EE8B2FE6EC56
2018/09/27 09:30:42 Encrypting to Key FP: 0D64AB6B1DBCEE859A5573EF54C3499FF3B7ED2C
2018/09/27 09:30:42 Encrypting to Key FP: D5309BC4FCF2143C9AED70A2A8249B2E74C94FD6
2018/09/27 09:30:42 Encrypting to Key FP: F3D2F6714EF6B251BDFF18947279C76A0FAC6413
2018/09/27 09:30:42 Encrypting to Key FP: 4952772637B2B44012070E47B87FE76E05BAA569
2018/09/27 09:30:42 Encrypting to Key FP: 1E4674AAA3940EAA6596FDF07D9D159F210BDF5A
2018/09/27 09:30:42 Encrypting to Key FP: 3592821032E144143BC62B54E5BAA10FF471EF57
2018/09/27 09:30:42 Encrypting to Key FP: A23C06D5B1E170AA70965450E62EA2331C853B0B
2018/09/27 09:30:42 openpgp: invalid argument: cannot encrypt a message to key id 7d9d159f210bdf5a because it has no encryption keys
exit status 1

gpg -a --export 7D9D159F210BDF5A > keys/jerad.pub.key.asc.txt
gpg -a --export 83378a94fa6c4994 > keys/phil.pub.key.asc.txt

I could not upload the binary keys here, only the ASCII armored ones.

jerad.pub.key.asc.txt
phil.pub.key.asc.txt

Edit: Also, I obtained both of those from key servers in the past. If you mean a key that has never been uploaded to a key server, I can try to generate some locally and (hopefully find one that fails) and post that directly here.

@Merovius
Copy link
Contributor

Hooray, I managed to reproduce \o/

I used this little program to strip the armor:

package main

import (
	"io"
	"os"

	"golang.org/x/crypto/openpgp/armor"
)

func main() {
	b, err := armor.Decode(os.Stdin)
	if err != nil {
		panic(err)
	}
	_, err = io.Copy(os.Stdout, b.Body)
	if err != nil {
		panic(err)
	}
}

When dearmoring the keys to ~/keys, I get the same error as you. When first importing the armored keys and then exporting them without armor, I don't get the error. This seems to confirm that it's something specific to the version of gpg you are using - it seems to encode something wrongly.

I can look into this a bit further now :)

@Merovius
Copy link
Contributor

Okay, so, it seems that the key you have has an expired signature on it:

mero@hix $ gpg2 --list-packets phil.pub.key.broken > broken.txt
mero@hix $ gpg2 --list-packets phil.pub.key.works > works.txt
mero@hix $ diff works.txt broken.txt 
3182a3183,3191
> 	version 4, created 1317826843, md5len 0, sigclass 0x18
> 	digest algo 8, begin of digest 81 52
> 	hashed subpkt 27 len 1 (key flags: 0C)
> 	hashed subpkt 2 len 4 (sig created 2011-10-05)
> 	hashed subpkt 9 len 4 (key expires after 5y0d0h53m)
> 	subpkt 16 len 8 (issuer key ID 83378A94FA6C4994)
> 	data: [3069 bits]
> # off=164629 ctb=89 tag=2 hlen=3 plen=421
> :signature packet: algo 1, keyid 83378A94FA6C4994

There is a second, non-expired signature after that. When reading the key, x/crypto/openpgp only uses the first signature on the key, so it only retains the expired one. gpg strips the expired signature on export, which is why importing/exporting the key repairs it.

There are multiple ways we could solve this, with different implications on usability/security. The RFC recommends (last sentence in that section)

An implementation that encounters multiple self-signatures on the
same object may resolve the ambiguity in any way it sees fit, but it
is RECOMMENDED that priority be given to the most recent self-
signature.

I'll send a CL to that effect, unless @FiloSottile wants to object.

@ghost
Copy link
Author

ghost commented Sep 27, 2018

Awesome work @Merovius Thanks for taking time to look at this.

@FiloSottile
Copy link
Contributor

This is the logic that was recently touched by @aviau in https://go-review.googlesource.com/c/crypto/+/118957, which quotes the very same spec snippet :)

@aviau
Copy link

aviau commented Sep 27, 2018

There is an implementation of this part of the spec in golang/crypto#57.

The implementation looks good, it lacks a test. I am willing to write a test if the author does not want to.

@FiloSottile
Copy link
Contributor

It looks good, happy to submit it if you chain a test CL to it and the chained CL passes tests.

@gopherbot
Copy link

Change https://golang.org/cl/135357 mentions this issue: openpgp: use latest subkey binding signature

@aviau
Copy link

aviau commented Sep 27, 2018

Okay, I will do that and ping you here when done.

Cheers,

@aviau
Copy link

aviau commented Sep 27, 2018

@Merovius
Copy link
Contributor

Just for the record, it would've been nice to at least acknowledge that I mentioned I was going to send out a CL after I spend a bunch of time debugging this today. I was in the process of adding a test, when I saw that someone else had taken over…

Yes, I know it's ultimately just less work for me. But it still doesn't send a great message.

@aviau
Copy link

aviau commented Sep 28, 2018

@Merovius I am not sure that I understand. Do you know that golang/crypto#57 was opened 14 days ago? We linked it to this issue as soon as we saw that they were related.

@Merovius
Copy link
Contributor

D'oh, apologies, I actually didn't see that it's the same repo m( now i'm embarrassed

@Merovius
Copy link
Contributor

We should close this as a dup of #26468 then, BTW. I already confirmed that it solves the problem by testing against a patched version.

@FiloSottile
Copy link
Contributor

We should close this as a dup of #26468

This is #26468, is that a typo?

@golang golang locked and limited conversation to collaborators Oct 1, 2019
chintanparikh pushed a commit to opendoor-labs/openpgp that referenced this issue Dec 11, 2019
Rather than using the first subkey binding signature encountered, use
the one with the most recent creation data, as per the recommendation from
RFC 4880:

> An implementation that encounters multiple self-signatures on the
> same object may resolve the ambiguity in any way it sees fit, but it
> is RECOMMENDED that priority be given to the most recent self-
> signature.

This allows subkeys to approach expiry then be re-signed with a new expiry.

This extends the recent commit 0e37d00 by @aviau and @FiloSottile.

Fixes golang/go#26468

Change-Id: I7f12706727373259c188bfee4254306ef9d4e935
GitHub-Last-Rev: 0da814166411a3c3334312576d3f7f8f2bba4ff4
GitHub-Pull-Request: golang/crypto#57
Reviewed-on: https://go-review.googlesource.com/135357
Reviewed-by: Filippo Valsorda <filippo@golang.org>
c-expert-zigbee pushed a commit to c-expert-zigbee/crypto_go that referenced this issue Mar 28, 2022
Rather than using the first subkey binding signature encountered, use
the one with the most recent creation data, as per the recommendation from
RFC 4880:

> An implementation that encounters multiple self-signatures on the
> same object may resolve the ambiguity in any way it sees fit, but it
> is RECOMMENDED that priority be given to the most recent self-
> signature.

This allows subkeys to approach expiry then be re-signed with a new expiry.

This extends the recent commit 0e37d00 by @aviau and @FiloSottile.

Fixes golang/go#26468

Change-Id: I7f12706727373259c188bfee4254306ef9d4e935
GitHub-Last-Rev: 0da814166411a3c3334312576d3f7f8f2bba4ff4
GitHub-Pull-Request: golang/crypto#57
Reviewed-on: https://go-review.googlesource.com/135357
Reviewed-by: Filippo Valsorda <filippo@golang.org>
c-expert-zigbee pushed a commit to c-expert-zigbee/crypto_go that referenced this issue Mar 29, 2022
Rather than using the first subkey binding signature encountered, use
the one with the most recent creation data, as per the recommendation from
RFC 4880:

> An implementation that encounters multiple self-signatures on the
> same object may resolve the ambiguity in any way it sees fit, but it
> is RECOMMENDED that priority be given to the most recent self-
> signature.

This allows subkeys to approach expiry then be re-signed with a new expiry.

This extends the recent commit 0e37d00 by @aviau and @FiloSottile.

Fixes golang/go#26468

Change-Id: I7f12706727373259c188bfee4254306ef9d4e935
GitHub-Last-Rev: 0da814166411a3c3334312576d3f7f8f2bba4ff4
GitHub-Pull-Request: golang/crypto#57
Reviewed-on: https://go-review.googlesource.com/135357
Reviewed-by: Filippo Valsorda <filippo@golang.org>
c-expert-zigbee pushed a commit to c-expert-zigbee/crypto_go that referenced this issue Mar 29, 2022
Rather than using the first subkey binding signature encountered, use
the one with the most recent creation data, as per the recommendation from
RFC 4880:

> An implementation that encounters multiple self-signatures on the
> same object may resolve the ambiguity in any way it sees fit, but it
> is RECOMMENDED that priority be given to the most recent self-
> signature.

This allows subkeys to approach expiry then be re-signed with a new expiry.

This extends the recent commit 0e37d00 by @aviau and @FiloSottile.

Fixes golang/go#26468

Change-Id: I7f12706727373259c188bfee4254306ef9d4e935
GitHub-Last-Rev: 0da814166411a3c3334312576d3f7f8f2bba4ff4
GitHub-Pull-Request: golang/crypto#57
Reviewed-on: https://go-review.googlesource.com/135357
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LewiGoddard pushed a commit to LewiGoddard/crypto that referenced this issue Feb 16, 2023
Rather than using the first subkey binding signature encountered, use
the one with the most recent creation data, as per the recommendation from
RFC 4880:

> An implementation that encounters multiple self-signatures on the
> same object may resolve the ambiguity in any way it sees fit, but it
> is RECOMMENDED that priority be given to the most recent self-
> signature.

This allows subkeys to approach expiry then be re-signed with a new expiry.

This extends the recent commit 8edf234 by @aviau and @FiloSottile.

Fixes golang/go#26468

Change-Id: I7f12706727373259c188bfee4254306ef9d4e935
GitHub-Last-Rev: 0da814166411a3c3334312576d3f7f8f2bba4ff4
GitHub-Pull-Request: golang/crypto#57
Reviewed-on: https://go-review.googlesource.com/135357
Reviewed-by: Filippo Valsorda <filippo@golang.org>
BiiChris pushed a commit to BiiChris/crypto that referenced this issue Sep 15, 2023
Rather than using the first subkey binding signature encountered, use
the one with the most recent creation data, as per the recommendation from
RFC 4880:

> An implementation that encounters multiple self-signatures on the
> same object may resolve the ambiguity in any way it sees fit, but it
> is RECOMMENDED that priority be given to the most recent self-
> signature.

This allows subkeys to approach expiry then be re-signed with a new expiry.

This extends the recent commit 0e37d00 by @aviau and @FiloSottile.

Fixes golang/go#26468

Change-Id: I7f12706727373259c188bfee4254306ef9d4e935
GitHub-Last-Rev: 0da8141
GitHub-Pull-Request: golang#57
Reviewed-on: https://go-review.googlesource.com/135357
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants