diff --git a/internal/backend/crypto/gpg/cli/encrypt.go b/internal/backend/crypto/gpg/cli/encrypt.go index 5dc9cbde4b..b50331396f 100644 --- a/internal/backend/crypto/gpg/cli/encrypt.go +++ b/internal/backend/crypto/gpg/cli/encrypt.go @@ -26,7 +26,7 @@ func (g *GPG) Encrypt(ctx context.Context, plaintext []byte, recipients []string if err != nil { debug.Log("Failed to check key %s. Adding anyway. %s", err) } else if len(kl.UseableKeys(gpg.IsAlwaysTrust(ctx))) < 1 { - out.Printf(ctx, "Not using expired key %s for encryption", r) + out.Printf(ctx, "Not using invalid key %s for encryption. (Check its expiration date or its encryption capabilities.)", r) continue } args = append(args, "--recipient", r) diff --git a/internal/backend/crypto/gpg/colons/parse_colons.go b/internal/backend/crypto/gpg/colons/parse_colons.go index 07bd01d484..c5764c3035 100644 --- a/internal/backend/crypto/gpg/colons/parse_colons.go +++ b/internal/backend/crypto/gpg/colons/parse_colons.go @@ -89,6 +89,7 @@ func Parse(reader io.Reader) gpg.KeyList { Ownertrust: fields[8], Identities: make(map[string]gpg.Identity, 1), SubKeys: make(map[string]struct{}, 1), + Caps: parseKeyCaps(fields[11]), } case "sub": fallthrough @@ -111,6 +112,28 @@ func Parse(reader io.Reader) gpg.KeyList { return kl } +func parseKeyCaps(field string) gpg.Capabilities { + keycaps := gpg.Capabilities{} + + if strings.Contains(field, "S") { + keycaps.Sign = true + } + if strings.Contains(field, "E") { + keycaps.Encrypt = true + } + if strings.Contains(field, "C") { + keycaps.Certify = true + } + if strings.Contains(field, "A") { + keycaps.Authentication = true + } + if strings.Contains(field, "D") { + keycaps.Deactivated = true + } + + return keycaps +} + func parseColonIdentity(fields []string) gpg.Identity { for i, f := range fields { fields[i] = strings.Replace(f, "\\x3a", ":", -1) diff --git a/internal/backend/crypto/gpg/key.go b/internal/backend/crypto/gpg/key.go index 8416f34376..3f10504e71 100644 --- a/internal/backend/crypto/gpg/key.go +++ b/internal/backend/crypto/gpg/key.go @@ -17,10 +17,26 @@ type Key struct { Fingerprint string Identities map[string]Identity SubKeys map[string]struct{} + Caps Capabilities +} + +// Capabilities of a Key +type Capabilities struct { + Encrypt bool + Sign bool + Certify bool + Authentication bool + Deactivated bool } // IsUseable returns true if GPG would assume this key is useable for encryption func (k Key) IsUseable(alwaysTrust bool) bool { + if k.Caps.Deactivated { + return false + } + if !k.Caps.Encrypt { + return false + } if !k.ExpirationDate.IsZero() && k.ExpirationDate.Before(time.Now()) { return false } diff --git a/internal/backend/crypto/gpg/key_test.go b/internal/backend/crypto/gpg/key_test.go index 3a5de29555..4104e359cf 100644 --- a/internal/backend/crypto/gpg/key_test.go +++ b/internal/backend/crypto/gpg/key_test.go @@ -57,6 +57,13 @@ func genTestKey(args ...string) Key { ExpirationDate: expiration, }, }, + Caps: Capabilities{ + Encrypt: true, + Sign: false, + Certify: false, + Authentication: false, + Deactivated: false, + }, } } @@ -94,11 +101,21 @@ func TestUseability(t *testing.T) { {}, { ExpirationDate: time.Now().Add(-time.Second), + Caps: Capabilities{Encrypt: true}, }, { ExpirationDate: time.Now().Add(time.Hour), + Caps: Capabilities{Encrypt: true}, Validity: "z", }, + { + ExpirationDate: time.Now().Add(time.Hour), + Caps: Capabilities{Deactivated: true}, + }, + { + ExpirationDate: time.Now().Add(time.Hour), + Caps: Capabilities{Encrypt: false}, + }, } { assert.False(t, k.IsUseable(false)) } @@ -107,14 +124,17 @@ func TestUseability(t *testing.T) { { ExpirationDate: time.Now().Add(time.Hour), Validity: "m", + Caps: Capabilities{Encrypt: true}, }, { ExpirationDate: time.Now().Add(time.Hour), Validity: "f", + Caps: Capabilities{Encrypt: true}, }, { ExpirationDate: time.Now().Add(time.Hour), Validity: "u", + Caps: Capabilities{Encrypt: true}, }, } { assert.True(t, k.IsUseable(false))