Skip to content

Commit 8186276

Browse files
wusslerlubux
authored andcommitted
Convert all valid subkeys when issuing a forwarding key
1 parent c759ef2 commit 8186276

File tree

3 files changed

+184
-57
lines changed

3 files changed

+184
-57
lines changed

openpgp/forwarding.go

Lines changed: 128 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,66 +11,154 @@ import (
1111
"github.com/ProtonMail/go-crypto/openpgp/packet"
1212
)
1313

14-
func (e *Entity) NewForwardingEntity(name, comment, email string, config *packet.Config) (forwardeeKey *Entity, proxyParam []byte, err error) {
15-
encryptionSubKey, ok := e.EncryptionKey(config.Now())
16-
if !ok {
17-
return nil, nil, errors.InvalidArgumentError("no valid encryption key found")
18-
}
19-
20-
if encryptionSubKey.PublicKey.Version != 4 {
21-
return nil, nil, errors.InvalidArgumentError("unsupported encryption subkey version")
22-
}
14+
// ForwardingInstance represents a single forwarding instance (mapping IDs to a Proxy Param)
15+
type ForwardingInstance struct {
16+
ForwarderKeyId uint64
17+
ForwardeeKeyId uint64
18+
ProxyParameter []byte
19+
}
2320

24-
if encryptionSubKey.PrivateKey.PubKeyAlgo != packet.PubKeyAlgoECDH {
25-
return nil, nil, errors.InvalidArgumentError("encryption subkey is not algorithm 18 (ECDH)")
21+
// NewForwardingEntity generates a new forwardee key and derives the proxy parameters from the entity e.
22+
// If strict, it will return an error if encryption-capable non-revoked subkeys with a wrong algorithm are found,
23+
// instead of ignoring them
24+
func (e *Entity) NewForwardingEntity(
25+
name, comment, email string, config *packet.Config, strict bool,
26+
) (
27+
forwardeeKey *Entity, instances []ForwardingInstance, err error,
28+
) {
29+
if e.PrimaryKey.Version != 4 {
30+
return nil, nil, errors.InvalidArgumentError("unsupported key version")
2631
}
2732

28-
ecdhKey, ok := encryptionSubKey.PrivateKey.PrivateKey.(*ecdh.PrivateKey)
29-
if !ok {
30-
return nil, nil, errors.InvalidArgumentError("encryption subkey is not type ECDH")
33+
now := config.Now()
34+
i := e.PrimaryIdentity()
35+
if e.PrimaryKey.KeyExpired(i.SelfSignature, now) || // primary key has expired
36+
i.SelfSignature == nil || // user ID has no self-signature
37+
i.SelfSignature.SigExpired(now) || // user ID self-signature has expired
38+
e.Revoked(now) || // primary key has been revoked
39+
i.Revoked(now) { // user ID has been revoked
40+
return nil, nil, errors.InvalidArgumentError("primary key is expired")
3141
}
3242

43+
// Generate a new Primary key for the forwardee
3344
config.Algorithm = packet.PubKeyAlgoEdDSA
3445
config.Curve = packet.Curve25519
46+
keyLifetimeSecs := config.KeyLifetime()
3547

36-
forwardeeKey, err = NewEntity(name, comment, email, config)
48+
forwardeePrimaryPrivRaw, err := newSigner(config)
3749
if err != nil {
3850
return nil, nil, err
3951
}
4052

41-
forwardeeEcdhKey, ok := forwardeeKey.Subkeys[0].PrivateKey.PrivateKey.(*ecdh.PrivateKey)
42-
if !ok {
43-
return nil, nil, goerrors.New("wrong forwarding sub key generation")
53+
primary := packet.NewSignerPrivateKey(now, forwardeePrimaryPrivRaw)
54+
55+
forwardeeKey = &Entity{
56+
PrimaryKey: &primary.PublicKey,
57+
PrivateKey: primary,
58+
Identities: make(map[string]*Identity),
59+
Subkeys: []Subkey{},
4460
}
4561

46-
proxyParam, err = ecdh.DeriveProxyParam(ecdhKey, forwardeeEcdhKey)
62+
err = forwardeeKey.addUserId(name, comment, email, config, now, keyLifetimeSecs)
4763
if err != nil {
4864
return nil, nil, err
4965
}
5066

51-
kdf := ecdh.KDF{
52-
Version: ecdh.KDFVersionForwarding,
53-
Hash: ecdhKey.KDF.Hash,
54-
Cipher: ecdhKey.KDF.Cipher,
55-
ReplacementFingerprint: encryptionSubKey.PublicKey.Fingerprint,
67+
// Init empty instances
68+
instances = []ForwardingInstance{}
69+
70+
// Handle all forwarder subkeys
71+
for _, forwarderSubKey := range e.Subkeys {
72+
// Filter flags
73+
if !forwarderSubKey.Sig.FlagsValid || forwarderSubKey.Sig.FlagCertify || forwarderSubKey.Sig.FlagSign ||
74+
forwarderSubKey.Sig.FlagAuthenticate || forwarderSubKey.Sig.FlagGroupKey {
75+
continue
76+
}
77+
78+
// Filter expiration & revokal
79+
if forwarderSubKey.PublicKey.KeyExpired(forwarderSubKey.Sig, now) ||
80+
forwarderSubKey.Sig.SigExpired(now) ||
81+
forwarderSubKey.Revoked(now) {
82+
continue
83+
}
84+
85+
if forwarderSubKey.PublicKey.PubKeyAlgo != packet.PubKeyAlgoECDH {
86+
if strict {
87+
return nil, nil, errors.InvalidArgumentError("encryption subkey is not algorithm 18 (ECDH)")
88+
} else {
89+
continue
90+
}
91+
}
92+
93+
forwarderEcdhKey, ok := forwarderSubKey.PrivateKey.PrivateKey.(*ecdh.PrivateKey)
94+
if !ok {
95+
return nil, nil, errors.InvalidArgumentError("malformed key")
96+
}
97+
98+
err = forwardeeKey.addEncryptionSubkey(config, now, 0)
99+
if err != nil {
100+
return nil, nil, err
101+
}
102+
103+
forwardeeSubKey := forwardeeKey.Subkeys[len(forwardeeKey.Subkeys) - 1]
104+
105+
forwardeeEcdhKey, ok := forwardeeSubKey.PrivateKey.PrivateKey.(*ecdh.PrivateKey)
106+
if !ok {
107+
return nil, nil, goerrors.New("wrong forwarding sub key generation")
108+
}
109+
110+
instance := ForwardingInstance{
111+
ForwarderKeyId: forwarderSubKey.PublicKey.KeyId,
112+
}
113+
114+
instance.ProxyParameter, err = ecdh.DeriveProxyParam(forwarderEcdhKey, forwardeeEcdhKey)
115+
if err != nil {
116+
return nil, nil, err
117+
}
118+
119+
kdf := ecdh.KDF{
120+
Version: ecdh.KDFVersionForwarding,
121+
Hash: forwarderEcdhKey.KDF.Hash,
122+
Cipher: forwarderEcdhKey.KDF.Cipher,
123+
}
124+
125+
// If deriving a forwarding key from a forwarding key
126+
if forwarderSubKey.Sig.FlagForward {
127+
if forwarderEcdhKey.KDF.Version != ecdh.KDFVersionForwarding {
128+
return nil, nil, goerrors.New("malformed forwarder key")
129+
}
130+
kdf.ReplacementFingerprint = forwarderEcdhKey.KDF.ReplacementFingerprint
131+
} else {
132+
kdf.ReplacementFingerprint = forwarderSubKey.PublicKey.Fingerprint
133+
}
134+
135+
err = forwardeeSubKey.PublicKey.ReplaceKDF(kdf)
136+
if err != nil {
137+
return nil, nil, err
138+
}
139+
140+
// Set ID after changing the KDF
141+
instance.ForwardeeKeyId = forwardeeSubKey.PublicKey.KeyId
142+
143+
// 0x04 - This key may be used to encrypt communications.
144+
forwardeeSubKey.Sig.FlagEncryptCommunications = false
145+
146+
// 0x08 - This key may be used to encrypt storage.
147+
forwardeeSubKey.Sig.FlagEncryptStorage = false
148+
149+
// 0x10 - The private component of this key may have been split by a secret-sharing mechanism.
150+
forwardeeSubKey.Sig.FlagSplitKey = true
151+
152+
// 0x40 - This key may be used for forwarded communications.
153+
forwardeeSubKey.Sig.FlagForward = true
154+
155+
// Append each valid instance to the list
156+
instances = append(instances, instance)
56157
}
57158

58-
err = forwardeeKey.Subkeys[0].PublicKey.ReplaceKDF(kdf)
59-
if err != nil {
60-
return nil, nil, err
159+
if len(instances) == 0 {
160+
return nil, nil, errors.InvalidArgumentError("no valid subkey found")
61161
}
62162

63-
// 0x04 - This key may be used to encrypt communications.
64-
forwardeeKey.Subkeys[0].Sig.FlagEncryptCommunications = false
65-
66-
// 0x08 - This key may be used to encrypt storage.
67-
forwardeeKey.Subkeys[0].Sig.FlagEncryptStorage = false
68-
69-
// 0x10 - The private component of this key may have been split by a secret-sharing mechanism.
70-
forwardeeKey.Subkeys[0].Sig.FlagSplitKey = true
71-
72-
// 0x40 - This key may be used for forwarded communications.
73-
forwardeeKey.Subkeys[0].Sig.FlagForward = true
74-
75-
return forwardeeKey, proxyParam, nil
163+
return forwardeeKey, instances, nil
76164
}

openpgp/forwarding_test.go

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,23 @@ func TestForwardingFull(t *testing.T) {
7878
t.Fatal(err)
7979
}
8080

81-
charlesEntity, proxyParam, err := bobEntity.NewForwardingEntity("charles", "", "[email protected]", keyConfig)
81+
charlesEntity, instances, err := bobEntity.NewForwardingEntity("charles", "", "[email protected]", keyConfig, true)
8282
if err != nil {
8383
t.Fatal(err)
8484
}
8585

86+
if len(instances) != 1 {
87+
t.Fatalf("invalid number of instances, expected 1 got %d", len(instances))
88+
}
89+
90+
if instances[0].ForwarderKeyId != bobEntity.Subkeys[0].PublicKey.KeyId {
91+
t.Fatalf("invalid forwarder key ID, expected: %x, got: %x", bobEntity.Subkeys[0].PublicKey.KeyId, instances[0].ForwarderKeyId)
92+
}
93+
94+
if instances[0].ForwardeeKeyId != charlesEntity.Subkeys[0].PublicKey.KeyId {
95+
t.Fatalf("invalid forwardee key ID, expected: %x, got: %x", charlesEntity.Subkeys[0].PublicKey.KeyId, instances[0].ForwardeeKeyId)
96+
}
97+
8698
// Encrypt message
8799
buf := bytes.NewBuffer(nil)
88100
w, err := Encrypt(buf, []*Entity{bobEntity}, nil, nil, nil)
@@ -114,6 +126,43 @@ func TestForwardingFull(t *testing.T) {
114126
}
115127

116128
// Forward message
129+
130+
transformed := transformTestMessage(t, encrypted, instances[0])
131+
132+
// Decrypt forwarded message for Charles
133+
m, err = ReadMessage(bytes.NewBuffer(transformed), EntityList([]*Entity{charlesEntity}), nil /* no prompt */, nil)
134+
if err != nil {
135+
t.Fatal(err)
136+
}
137+
138+
dec, err = ioutil.ReadAll(m.decrypted)
139+
140+
if bytes.Compare(dec, plaintext) != 0 {
141+
t.Fatal("forwarded decrypted does not match original")
142+
}
143+
144+
// Setup further forwarding
145+
danielEntity, secondForwardInstances, err := charlesEntity.NewForwardingEntity("Daniel", "", "[email protected]", keyConfig, true)
146+
if err != nil {
147+
t.Fatal(err)
148+
}
149+
150+
secondTransformed := transformTestMessage(t, transformed, secondForwardInstances[0])
151+
152+
// Decrypt forwarded message for Charles
153+
m, err = ReadMessage(bytes.NewBuffer(secondTransformed), EntityList([]*Entity{danielEntity}), nil /* no prompt */, nil)
154+
if err != nil {
155+
t.Fatal(err)
156+
}
157+
158+
dec, err = ioutil.ReadAll(m.decrypted)
159+
160+
if bytes.Compare(dec, plaintext) != 0 {
161+
t.Fatal("forwarded decrypted does not match original")
162+
}
163+
}
164+
165+
func transformTestMessage(t *testing.T, encrypted []byte, instance ForwardingInstance) []byte {
117166
bytesReader := bytes.NewReader(encrypted)
118167
packets := packet.NewReader(bytesReader)
119168
splitPoint := int64(0)
@@ -131,9 +180,9 @@ Loop:
131180
switch p := p.(type) {
132181
case *packet.EncryptedKey:
133182
err = p.ProxyTransform(
134-
proxyParam,
135-
charlesEntity.Subkeys[0].PublicKey.KeyId,
136-
bobEntity.Subkeys[0].PublicKey.KeyId,
183+
instance.ProxyParameter,
184+
instance.ForwarderKeyId,
185+
instance.ForwardeeKeyId,
137186
)
138187
if err != nil {
139188
t.Fatalf("error transforming PKESK: %s", err)
@@ -152,15 +201,5 @@ Loop:
152201
transformed := transformedEncryptedKey.Bytes()
153202
transformed = append(transformed, encrypted[splitPoint:]...)
154203

155-
// Decrypt forwarded message for Charles
156-
m, err = ReadMessage(bytes.NewBuffer(transformed), EntityList([]*Entity{charlesEntity}), nil /* no prompt */, nil)
157-
if err != nil {
158-
t.Fatal(err)
159-
}
160-
161-
dec, err = ioutil.ReadAll(m.decrypted)
162-
163-
if bytes.Compare(dec, plaintext) != 0 {
164-
t.Fatal("forwarded decrypted does not match original")
165-
}
204+
return transformed
166205
}

openpgp/packet/encrypted_key.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,12 +463,12 @@ func SerializeEncryptedKeyWithHiddenOption(w io.Writer, pub *PublicKey, cipherFu
463463
return SerializeEncryptedKeyAEADwithHiddenOption(w, pub, cipherFunc, config.AEAD() != nil, key, hidden, config)
464464
}
465465

466-
func (e *EncryptedKey) ProxyTransform(proxyParam []byte, forwardeeKeyId, forwardingKeyId uint64) error {
466+
func (e *EncryptedKey) ProxyTransform(proxyParam []byte, forwarderKeyId, forwardeeKeyId uint64) error {
467467
if e.Algo != PubKeyAlgoECDH {
468468
return errors.InvalidArgumentError("invalid PKESK")
469469
}
470470

471-
if e.KeyId != 0 && e.KeyId != forwardingKeyId {
471+
if e.KeyId != 0 && e.KeyId != forwarderKeyId {
472472
return errors.InvalidArgumentError("invalid key id in PKESK")
473473
}
474474

0 commit comments

Comments
 (0)