Skip to content

Commit 2d22b6e

Browse files
authored
Load ElGamal Sign or Encrypt keys (keybase#60)
Add ability to read and parse algo 20 keys - ElGamal Encrypt or Sign. These were deprecated and are not valid anymore. After this change, these keys are now being loaded and added to BadSubkeys, instead of failing whole key reading process. If Bad ElGamal is a primary key, entire load will fail. This fixes loading key bundles of people who generated such keys pre ~2002 and bad ElGamal key is one of their subkeys. There are no downsides of loading and verifying such key if it's maintained that it can't be used for anything.
1 parent 80dd18f commit 2d22b6e

File tree

5 files changed

+168
-7
lines changed

5 files changed

+168
-7
lines changed

openpgp/bad_elgamal_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package openpgp
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
8+
"github.com/keybase/go-crypto/openpgp/clearsign"
9+
"github.com/keybase/go-crypto/openpgp/errors"
10+
"github.com/keybase/go-crypto/openpgp/packet"
11+
)
12+
13+
func TestBadElgamalSubkey(t *testing.T) {
14+
// When algo 20 key is read, we go ahead with parsing and
15+
// verifying, but the key ends up in BadSubkeys with
16+
// DeprecatedKeyError.
17+
entities, err := ReadArmoredKeyRing(strings.NewReader(publicKey))
18+
if err != nil {
19+
t.Fatalf("error opening keys: %v", err)
20+
}
21+
if len(entities) != 1 {
22+
t.Fatal("expected only 1 key")
23+
}
24+
entity := entities[0]
25+
if len(entity.Subkeys) != 1 {
26+
t.Fatal("expected 1 subkey")
27+
}
28+
if len(entity.BadSubkeys) != 1 {
29+
t.Fatal("expected 1 bad subkey")
30+
}
31+
badSubkey := entity.BadSubkeys[0]
32+
err = badSubkey.Err
33+
if _, ok := err.(errors.DeprecatedKeyError); !ok {
34+
t.Fatalf("expected DeprecatedKeyError, got: %s", err)
35+
}
36+
if algo := badSubkey.Subkey.PublicKey.PubKeyAlgo; algo != packet.PubKeyAlgoBadElGamal {
37+
t.Fatalf("Unexpected bad key PubKeyAlgo, expected 20, got %d", algo)
38+
}
39+
40+
// When reading a signature produced by algo 20 key, checking
41+
// should fail with UnsupportedError - signatures also have
42+
// algorithm field, and PubKeyAlgoBadElGamal is not recognized
43+
// there. See signature_v3.go:parse.
44+
b, _ := clearsign.Decode([]byte(clearsignMsg))
45+
if b == nil {
46+
t.Fatal("Failed to decode clearsign msg")
47+
}
48+
_, err = CheckDetachedSignature(entities, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body)
49+
if err == nil {
50+
t.Fatal("Expected to see error when checking clearsign")
51+
}
52+
if _, ok := err.(errors.UnsupportedError); !ok {
53+
t.Fatalf("Unexpected error type: %s", err)
54+
}
55+
}
56+
57+
func TestBadElgamalPrimary(t *testing.T) {
58+
// If BadElGamal is primary key, opening should fail with
59+
// error opening keys: openpgp: invalid data: primary key cannot be used for signatures
60+
// Because PublicKeyBadElGamal is neither valid for Encryption nor Signing.
61+
_, err := ReadArmoredKeyRing(strings.NewReader(badPrimaryPublicKey))
62+
if err == nil {
63+
t.Fatalf("Expected error")
64+
}
65+
}
66+
67+
const publicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
68+
Version: GnuPG v1.2.0 (GNU/Linux)
69+
70+
mQGiBFpM17MRBADdWeXsUegcrx7rUON8+a0douslKTkj/z8E1FFLP6u25UJSsLdj
71+
/39ClQJVreLNbNuDSM/Z5gX8oRIkYGMK5TAa1M47+ZOXfkbsP4NVx0iwWxcktmpG
72+
I/GOo2Wc2a8McX5HQ1o9l0AjVM+0JOvnmidlVAh8b4MuGlXnb+EpCFpOOwCgvnC3
73+
5z8lUmaDXJ5dU41UwgZcQAkD/AnB/NLrN9J6vK2hbTpCexsHrttIqLykCuwC4R5V
74+
aVM/Qy0FK9BA7Jw+P+se01qfj8r6p7H4WP7l+ByGF2SwZ50PuAdeTVMo4LqP9pXs
75+
kz7tM4uM8PBta+o2QOvnjpdlGwbN7kTd9B2UyaI8GnDL7k0el6oZB7o3R+GD8Xii
76+
pWdxA/4naRWXes0ZTER1mq8ssogNLtTrjWjF5naQE5rhPcoM+3GT0HTk3PySBRPI
77+
Dk9M9V+6OmqxqCHcUBNd58I8mqwicfBrG6I3Jb9u+YCdty7XF2AvXQwkfL35Zq8u
78+
0TRASP5PG2l5KdUpWstZOWPEGRGsZP49+ewoLeqcV6msoOsj07QORWxnYW1hbCBU
79+
ZXN0ZXKIWQQTEQIAGQUCWkzXswQLBwMCAxUCAwMWAgECHgECF4AACgkQl+HNHuDC
80+
7kWSqgCcDFgo+4EO+IiZTuXgeUWsH0alzawAnRK7rIxMqciYkrpHNsXIno1R+kJQ
81+
uM0EWkzXsxADAO6EHCPdw6EUAnZsd1GWmsYHEqdfduoqWtJCzsgDW0OSQe70bH15
82+
kaxITv/QpJr6gPs7aW13gcF4l9Q/rW+BJlSbSOwtp1ndq9GQ7E5QGCjgflFGCmZw
83+
1OLlSLZyQukVfwADBQMAwasRRlXw/uideJAgSUDcE5m7DBZrTExl2nC/oOogyIaW
84+
H5I+FFEfNXs7whjK/1ixoLJTFaiwkW4mvYYoGzDeTHIgRLeVHeAuSRfC3oBAua3f
85+
BokQ68fgEHGDADVJoQj7iEYEGBECAAYFAlpM17MACgkQl+HNHuDC7kXMDQCgp90K
86+
3OsRXnsK/LLvYeNCDrRGyrsAn3pj+2rTU75VMwyDb5mndZAGH2TjuM0EWkzX1xQD
87+
AJbyZopv9OdtX4j4to3jX8PgFrpSEEQT+qiHben8CYTtiOzWClurYHhZdHq6dhqc
88+
EACvLGNQM8EXmmGHs1Aa6eRf4WLYo8hRs2Wf7275Mu4iw5h0X2kgSj02tXEaPwkt
89+
4wADBQL+M4x1R90WDz1h92lJ/YcgFeINW8hxGVwCeeeZ+62vc4SLB3i/jfN6dx4Q
90+
9vjLd+BrnzkwFzc6QW9UqpL3TvB9xruunJJMqybAiJshyOabu6urVUPw1eMg1La8
91+
wd0afBLHiEYEGBECAAYFAlpM19cACgkQl+HNHuDC7kU26wCdEXpc0j9DutGh2ABg
92+
ygm0xrHw5xEAoJonEzW5F3oDhft9cfKk4mR+QAnv
93+
=qGLg
94+
-----END PGP PUBLIC KEY BLOCK-----`
95+
96+
const clearsignMsg = `-----BEGIN PGP SIGNED MESSAGE-----
97+
Hash: SHA1
98+
99+
aaa
100+
-----BEGIN PGP SIGNATURE-----
101+
Version: GnuPG v1.2.0 (GNU/Linux)
102+
103+
iNcDBQFaTOUbUXZ9JopEDEYUAmtOAv427hD+yJD5i8lv2HISIB4XnG5NQcX3HMbp
104+
4JzS/17T0PVzhbUaoguK4S4HbCy2TKDAiqFW+uTPVD2g/hDdz3iigdZC0q2qATfS
105+
F4cO0rBiZy0h/MadrW54md5VPd3cruQC/j9P1MQF1pzp1R8DKrI/aD2zUxzv3tR2
106+
5kMs9zLJFk+sEY3ppati3sUZpwukn4tNXsMVq5VUjKu81jUxr5Te/114gjbk6Oqo
107+
bvEOhvf8VAzGswfr7Ur2/KN0D5n1Zr5wmA==
108+
=yqX0
109+
-----END PGP SIGNATURE-----`
110+
111+
const badPrimaryPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
112+
Version: GnuPG v1.2.0 (GNU/Linux)
113+
114+
mM0EWk0OfxQDAKeN5zgL7Tlq+/gSnlt1NOmykdJMfJdZ+RAkQl3Qc+mUx3MQIAdQ
115+
6j0mnTl6S3U3ObxZApkcEbxv5TqKSkEc9YeANMA60LlyAwC/iJovXex4rdKPjqi5
116+
l5csknYcR5bI7wADBQL/dvzDc69C2zw5YJxB0LpAr4dD3i+YaxKu9EnBt3tPz7X/
117+
T+DCzVXXWcEhMGLjUmKjgfKxpVAfw+gVJ7g0JDouj0YqFJuWqoy9p3rjDHUYmvpD
118+
11dC/dAWQl8BCXb0VbYitA5QcmltYXJ5IEJhZEVsR4jxBBMUAgAZBQJaTQ5/BAsH
119+
AwIDFQIDAxYCAQIeAQIXgAAKCRD+XtaXeIetgMMfAv9c1r2E3ap6zXgVS1ynT3h3
120+
WtwyxWZUN0s5Yz6cFFOlURQg3/U/YCSVfgE4u48FKFUTrvRRtcr5dN5gU8GLBL0L
121+
WfVsz4cBaPvJS2DBburndGxKpPmk6UzNTCSBUp8qLVMC/i8C2x+aF3t29T0ZRUAv
122+
pRBgQ1yPlszdN0yLTNnBoJS/Uk50WiJLoR6KhwHzuFcB06Ct4YQ/6qh52/7jOZ1q
123+
cmK+ol7hDafxDEfviy1T/vH06ZnhZp/jO4yRm+y8jNa9eQ==
124+
=vLss
125+
-----END PGP PUBLIC KEY BLOCK-----`

openpgp/errors/errors.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,11 @@ type UnknownPacketTypeError uint8
7070
func (upte UnknownPacketTypeError) Error() string {
7171
return "openpgp: unknown packet type: " + strconv.Itoa(int(upte))
7272
}
73+
74+
// DeprecatedKeyError indicates that the key was read and verified
75+
// properly, but uses a deprecated algorithm and can't be used.
76+
type DeprecatedKeyError string
77+
78+
func (d DeprecatedKeyError) Error() string {
79+
return "openpgp: key is deprecated: " + string(d)
80+
}

openpgp/keys.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,15 @@ func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *p
645645
}
646646
}
647647
}
648+
649+
if subKey.Sig != nil {
650+
if err := subKey.PublicKey.ErrorIfDeprecated(); err != nil {
651+
// Key passed signature check but is deprecated.
652+
subKey.Sig = nil
653+
lastErr = err
654+
}
655+
}
656+
648657
if subKey.Sig != nil {
649658
e.Subkeys = append(e.Subkeys, subKey)
650659
} else {

openpgp/packet/packet.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,12 @@ const (
413413
PubKeyAlgoElGamal PublicKeyAlgorithm = 16
414414
PubKeyAlgoDSA PublicKeyAlgorithm = 17
415415
// RFC 6637, Section 5.
416-
PubKeyAlgoECDH PublicKeyAlgorithm = 18
417-
PubKeyAlgoECDSA PublicKeyAlgorithm = 19
416+
PubKeyAlgoECDH PublicKeyAlgorithm = 18
417+
PubKeyAlgoECDSA PublicKeyAlgorithm = 19
418+
419+
PubKeyAlgoBadElGamal PublicKeyAlgorithm = 20 // Reserved (deprecated, formerly ElGamal Encrypt or Sign)
418420
// RFC -1
419-
PubKeyAlgoEdDSA PublicKeyAlgorithm = 22
421+
PubKeyAlgoEdDSA PublicKeyAlgorithm = 22
420422
)
421423

422424
// CanEncrypt returns true if it's possible to encrypt a message to a public

openpgp/packet/public_key.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,14 @@ func (pk *PublicKey) parse(r io.Reader) (err error) {
393393
return
394394
}
395395
pk.PublicKey, err = pk.ec.newECDH()
396+
case PubKeyAlgoBadElGamal:
397+
// Key has ElGamal format but nil-implementation - it will
398+
// load but it's not possible to do any operations using this
399+
// key.
400+
err = pk.parseElGamal(r)
401+
if err != nil {
402+
pk.PublicKey = nil
403+
}
396404
default:
397405
err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
398406
}
@@ -508,7 +516,7 @@ func (pk *PublicKey) SerializeSignaturePrefix(h io.Writer) {
508516
pLength += 2 + uint16(len(pk.q.bytes))
509517
pLength += 2 + uint16(len(pk.g.bytes))
510518
pLength += 2 + uint16(len(pk.y.bytes))
511-
case PubKeyAlgoElGamal:
519+
case PubKeyAlgoElGamal, PubKeyAlgoBadElGamal:
512520
pLength += 2 + uint16(len(pk.p.bytes))
513521
pLength += 2 + uint16(len(pk.g.bytes))
514522
pLength += 2 + uint16(len(pk.y.bytes))
@@ -539,7 +547,7 @@ func (pk *PublicKey) Serialize(w io.Writer) (err error) {
539547
length += 2 + len(pk.q.bytes)
540548
length += 2 + len(pk.g.bytes)
541549
length += 2 + len(pk.y.bytes)
542-
case PubKeyAlgoElGamal:
550+
case PubKeyAlgoElGamal, PubKeyAlgoBadElGamal:
543551
length += 2 + len(pk.p.bytes)
544552
length += 2 + len(pk.g.bytes)
545553
length += 2 + len(pk.y.bytes)
@@ -587,7 +595,7 @@ func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) {
587595
return writeMPIs(w, pk.n, pk.e)
588596
case PubKeyAlgoDSA:
589597
return writeMPIs(w, pk.p, pk.q, pk.g, pk.y)
590-
case PubKeyAlgoElGamal:
598+
case PubKeyAlgoElGamal, PubKeyAlgoBadElGamal:
591599
return writeMPIs(w, pk.p, pk.g, pk.y)
592600
case PubKeyAlgoECDSA:
593601
return pk.ec.serialize(w)
@@ -910,7 +918,7 @@ func (pk *PublicKey) BitLength() (bitLength uint16, err error) {
910918
bitLength = pk.n.bitLength
911919
case PubKeyAlgoDSA:
912920
bitLength = pk.p.bitLength
913-
case PubKeyAlgoElGamal:
921+
case PubKeyAlgoElGamal, PubKeyAlgoBadElGamal:
914922
bitLength = pk.p.bitLength
915923
case PubKeyAlgoECDH:
916924
ecdhPublicKey := pk.PublicKey.(*ecdh.PublicKey)
@@ -928,3 +936,12 @@ func (pk *PublicKey) BitLength() (bitLength uint16, err error) {
928936
}
929937
return
930938
}
939+
940+
func (pk *PublicKey) ErrorIfDeprecated() error {
941+
switch pk.PubKeyAlgo {
942+
case PubKeyAlgoBadElGamal:
943+
return errors.DeprecatedKeyError("ElGamal Encrypt or Sign (algo 20) is deprecated")
944+
default:
945+
return nil
946+
}
947+
}

0 commit comments

Comments
 (0)