Skip to content

Commit 28d1c85

Browse files
committed
feat: Add symmetric keys to v2
1 parent c99f7e5 commit 28d1c85

10 files changed

+358
-7
lines changed

Diff for: openpgp/forwarding.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,10 @@ func (e *Entity) NewForwardingEntity(
134134
instance.ForwardeeFingerprint = forwardeeSubKey.PublicKey.Fingerprint
135135

136136
// 0x04 - This key may be used to encrypt communications.
137-
forwardeeSubKey.Sig.FlagEncryptCommunications = true
137+
forwardeeSubKey.Sig.FlagEncryptCommunications = false
138138

139139
// 0x08 - This key may be used to encrypt storage.
140-
forwardeeSubKey.Sig.FlagEncryptStorage = true
140+
forwardeeSubKey.Sig.FlagEncryptStorage = false
141141

142142
// 0x10 - The private component of this key may have been split by a secret-sharing mechanism.
143143
forwardeeSubKey.Sig.FlagSplitKey = true

Diff for: openpgp/packet/encrypted_key.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error {
218218

219219
var key []byte
220220
switch priv.PubKeyAlgo {
221-
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH:
221+
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH, ExperimentalPubKeyAlgoAEAD:
222222
keyOffset := 0
223223
if e.Version < 6 {
224224
e.CipherFunc = CipherFunction(b[0])

Diff for: openpgp/v2/forwarding.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,10 @@ func (e *Entity) NewForwardingEntity(
131131
instance.ForwardeeFingerprint = forwardeeSubKey.PublicKey.Fingerprint
132132

133133
// 0x04 - This key may be used to encrypt communications.
134-
forwardeeSubKeySelfSig.FlagEncryptCommunications = true
134+
forwardeeSubKeySelfSig.FlagEncryptCommunications = false
135135

136136
// 0x08 - This key may be used to encrypt storage.
137-
forwardeeSubKeySelfSig.FlagEncryptStorage = true
137+
forwardeeSubKeySelfSig.FlagEncryptStorage = false
138138

139139
// 0x10 - The private component of this key may have been split by a secret-sharing mechanism.
140140
forwardeeSubKeySelfSig.FlagSplitKey = true

Diff for: openpgp/v2/key_generation.go

+7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
2323
"github.com/ProtonMail/go-crypto/openpgp/internal/ecc"
2424
"github.com/ProtonMail/go-crypto/openpgp/packet"
25+
"github.com/ProtonMail/go-crypto/openpgp/symmetric"
2526
"github.com/ProtonMail/go-crypto/openpgp/x25519"
2627
"github.com/ProtonMail/go-crypto/openpgp/x448"
2728
)
@@ -387,6 +388,9 @@ func newSigner(config *packet.Config) (signer interface{}, err error) {
387388
return nil, err
388389
}
389390
return priv, nil
391+
case packet.ExperimentalPubKeyAlgoHMAC:
392+
hash := algorithm.HashById[hashToHashId(config.Hash())]
393+
return symmetric.HMACGenerateKey(config.Random(), hash)
390394
default:
391395
return nil, errors.InvalidArgumentError("unsupported public key algorithm")
392396
}
@@ -429,6 +433,9 @@ func newDecrypter(config *packet.Config) (decrypter interface{}, err error) {
429433
return x25519.GenerateKey(config.Random())
430434
case packet.PubKeyAlgoEd448, packet.PubKeyAlgoX448: // When passing Ed448, we generate an x448 subkey
431435
return x448.GenerateKey(config.Random())
436+
case packet.ExperimentalPubKeyAlgoAEAD:
437+
cipher := algorithm.CipherFunction(config.Cipher())
438+
return symmetric.AEADGenerateKey(config.Random(), cipher)
432439
default:
433440
return nil, errors.InvalidArgumentError("unsupported public key algorithm")
434441
}

Diff for: openpgp/v2/keys.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,10 @@ func (e *Entity) serializePrivate(w io.Writer, config *packet.Config, reSign boo
608608
// Serialize writes the public part of the given Entity to w, including
609609
// signatures from other entities. No private key material will be output.
610610
func (e *Entity) Serialize(w io.Writer) error {
611+
if e.PrimaryKey.PubKeyAlgo == packet.ExperimentalPubKeyAlgoHMAC ||
612+
e.PrimaryKey.PubKeyAlgo == packet.ExperimentalPubKeyAlgoAEAD {
613+
return errors.InvalidArgumentError("Can't serialize symmetric primary key")
614+
}
611615
if err := e.PrimaryKey.Serialize(w); err != nil {
612616
return err
613617
}
@@ -628,6 +632,16 @@ func (e *Entity) Serialize(w io.Writer) error {
628632
}
629633
}
630634
for _, subkey := range e.Subkeys {
635+
// The types of keys below are only useful as private keys. Thus, the
636+
// public key packets contain no meaningful information and do not need
637+
// to be serialized.
638+
// Prevent public key export for forwarding keys, see forwarding section 4.1.
639+
subKeySelfSig, err := subkey.LatestValidBindingSignature(time.Time{})
640+
if subkey.PublicKey.PubKeyAlgo == packet.ExperimentalPubKeyAlgoHMAC ||
641+
subkey.PublicKey.PubKeyAlgo == packet.ExperimentalPubKeyAlgoAEAD ||
642+
(err == nil && subKeySelfSig.FlagForward) {
643+
continue
644+
}
631645
if err := subkey.Serialize(w, false); err != nil {
632646
return err
633647
}
@@ -782,5 +796,5 @@ func isValidCertificationKey(signature *packet.Signature, algo packet.PublicKeyA
782796
func isValidEncryptionKey(signature *packet.Signature, algo packet.PublicKeyAlgorithm) bool {
783797
return algo.CanEncrypt() &&
784798
signature.FlagsValid &&
785-
signature.FlagEncryptCommunications
799+
(signature.FlagEncryptCommunications || signature.FlagForward || signature.FlagEncryptStorage)
786800
}

Diff for: openpgp/v2/keys_test.go

+220
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
2323
"github.com/ProtonMail/go-crypto/openpgp/packet"
2424
"github.com/ProtonMail/go-crypto/openpgp/s2k"
25+
"github.com/ProtonMail/go-crypto/openpgp/symmetric"
2526
)
2627

2728
var hashes = []crypto.Hash{
@@ -2011,3 +2012,222 @@ NciH07RTRuMS/aRhRg4OB8PQROmTnZ+iZS0=
20112012
t.Fatal(err)
20122013
}
20132014
}
2015+
2016+
func TestAddHMACSubkey(t *testing.T) {
2017+
c := &packet.Config{
2018+
RSABits: 512,
2019+
Algorithm: packet.ExperimentalPubKeyAlgoHMAC,
2020+
}
2021+
2022+
entity, err := NewEntity("Golang Gopher", "Test Key", "[email protected]", &packet.Config{RSABits: 1024})
2023+
if err != nil {
2024+
t.Fatal(err)
2025+
}
2026+
2027+
err = entity.AddSigningSubkey(c)
2028+
if err != nil {
2029+
t.Fatal(err)
2030+
}
2031+
2032+
buf := bytes.NewBuffer(nil)
2033+
w, _ := armor.Encode(buf, "PGP PRIVATE KEY BLOCK", nil)
2034+
if err := entity.SerializePrivate(w, nil); err != nil {
2035+
t.Errorf("failed to serialize entity: %s", err)
2036+
}
2037+
w.Close()
2038+
2039+
key, err := ReadArmoredKeyRing(buf)
2040+
if err != nil {
2041+
t.Error("could not read keyring", err)
2042+
}
2043+
2044+
generatedPrivateKey := entity.Subkeys[1].PrivateKey.PrivateKey.(*symmetric.HMACPrivateKey)
2045+
parsedPrivateKey := key[0].Subkeys[1].PrivateKey.PrivateKey.(*symmetric.HMACPrivateKey)
2046+
2047+
generatedPublicKey := entity.Subkeys[1].PublicKey.PublicKey.(*symmetric.HMACPublicKey)
2048+
parsedPublicKey := key[0].Subkeys[1].PublicKey.PublicKey.(*symmetric.HMACPublicKey)
2049+
2050+
if !bytes.Equal(parsedPrivateKey.Key, generatedPrivateKey.Key) {
2051+
t.Error("parsed wrong key")
2052+
}
2053+
if !bytes.Equal(parsedPublicKey.Key, generatedPrivateKey.Key) {
2054+
t.Error("parsed wrong key in public part")
2055+
}
2056+
if !bytes.Equal(generatedPublicKey.Key, generatedPrivateKey.Key) {
2057+
t.Error("generated Public and Private Key differ")
2058+
}
2059+
2060+
if !bytes.Equal(parsedPrivateKey.HashSeed[:], generatedPrivateKey.HashSeed[:]) {
2061+
t.Error("parsed wrong hash seed")
2062+
}
2063+
2064+
if parsedPrivateKey.PublicKey.Hash != generatedPrivateKey.PublicKey.Hash {
2065+
t.Error("parsed wrong cipher id")
2066+
}
2067+
if !bytes.Equal(parsedPrivateKey.PublicKey.BindingHash[:], generatedPrivateKey.PublicKey.BindingHash[:]) {
2068+
t.Error("parsed wrong binding hash")
2069+
}
2070+
}
2071+
2072+
func TestSerializeSymmetricSubkeyError(t *testing.T) {
2073+
entity, err := NewEntity("Golang Gopher", "Test Key", "[email protected]", &packet.Config{RSABits: 1024})
2074+
if err != nil {
2075+
t.Fatal(err)
2076+
}
2077+
2078+
buf := bytes.NewBuffer(nil)
2079+
w, _ := armor.Encode(buf, "PGP PRIVATE KEY BLOCK", nil)
2080+
2081+
entity.PrimaryKey.PubKeyAlgo = 100
2082+
err = entity.Serialize(w)
2083+
if err == nil {
2084+
t.Fatal(err)
2085+
}
2086+
2087+
entity.PrimaryKey.PubKeyAlgo = 101
2088+
err = entity.Serialize(w)
2089+
if err == nil {
2090+
t.Fatal(err)
2091+
}
2092+
}
2093+
2094+
func TestAddAEADSubkey(t *testing.T) {
2095+
c := &packet.Config{
2096+
RSABits: 512,
2097+
Algorithm: packet.ExperimentalPubKeyAlgoAEAD,
2098+
}
2099+
entity, err := NewEntity("Golang Gopher", "Test Key", "[email protected]", &packet.Config{RSABits: 1024})
2100+
if err != nil {
2101+
t.Fatal(err)
2102+
}
2103+
2104+
err = entity.AddEncryptionSubkey(c)
2105+
if err != nil {
2106+
t.Fatal(err)
2107+
}
2108+
2109+
generatedPrivateKey := entity.Subkeys[1].PrivateKey.PrivateKey.(*symmetric.AEADPrivateKey)
2110+
2111+
buf := bytes.NewBuffer(nil)
2112+
w, _ := armor.Encode(buf, "PGP PRIVATE KEY BLOCK", nil)
2113+
if err := entity.SerializePrivate(w, nil); err != nil {
2114+
t.Errorf("failed to serialize entity: %s", err)
2115+
}
2116+
w.Close()
2117+
2118+
key, err := ReadArmoredKeyRing(buf)
2119+
if err != nil {
2120+
t.Error("could not read keyring", err)
2121+
}
2122+
2123+
parsedPrivateKey := key[0].Subkeys[1].PrivateKey.PrivateKey.(*symmetric.AEADPrivateKey)
2124+
2125+
generatedPublicKey := entity.Subkeys[1].PublicKey.PublicKey.(*symmetric.AEADPublicKey)
2126+
parsedPublicKey := key[0].Subkeys[1].PublicKey.PublicKey.(*symmetric.AEADPublicKey)
2127+
2128+
if !bytes.Equal(parsedPrivateKey.Key, generatedPrivateKey.Key) {
2129+
t.Error("parsed wrong key")
2130+
}
2131+
if !bytes.Equal(parsedPublicKey.Key, generatedPrivateKey.Key) {
2132+
t.Error("parsed wrong key in public part")
2133+
}
2134+
if !bytes.Equal(generatedPublicKey.Key, generatedPrivateKey.Key) {
2135+
t.Error("generated Public and Private Key differ")
2136+
}
2137+
2138+
if !bytes.Equal(parsedPrivateKey.HashSeed[:], generatedPrivateKey.HashSeed[:]) {
2139+
t.Error("parsed wrong hash seed")
2140+
}
2141+
2142+
if parsedPrivateKey.PublicKey.Cipher.Id() != generatedPrivateKey.PublicKey.Cipher.Id() {
2143+
t.Error("parsed wrong cipher id")
2144+
}
2145+
if !bytes.Equal(parsedPrivateKey.PublicKey.BindingHash[:], generatedPrivateKey.PublicKey.BindingHash[:]) {
2146+
t.Error("parsed wrong binding hash")
2147+
}
2148+
}
2149+
2150+
func TestNoSymmetricKeySerialized(t *testing.T) {
2151+
aeadConfig := &packet.Config{
2152+
RSABits: 512,
2153+
DefaultHash: crypto.SHA512,
2154+
Algorithm: packet.ExperimentalPubKeyAlgoAEAD,
2155+
DefaultCipher: packet.CipherAES256,
2156+
}
2157+
hmacConfig := &packet.Config{
2158+
RSABits: 512,
2159+
DefaultHash: crypto.SHA512,
2160+
Algorithm: packet.ExperimentalPubKeyAlgoHMAC,
2161+
DefaultCipher: packet.CipherAES256,
2162+
}
2163+
entity, err := NewEntity("Golang Gopher", "Test Key", "[email protected]", &packet.Config{RSABits: 1024})
2164+
if err != nil {
2165+
t.Fatal(err)
2166+
}
2167+
2168+
err = entity.AddEncryptionSubkey(aeadConfig)
2169+
if err != nil {
2170+
t.Fatal(err)
2171+
}
2172+
err = entity.AddSigningSubkey(hmacConfig)
2173+
if err != nil {
2174+
t.Fatal(err)
2175+
}
2176+
2177+
w := bytes.NewBuffer(nil)
2178+
entity.Serialize(w)
2179+
2180+
firstSymKey := entity.Subkeys[1].PrivateKey.PrivateKey.(*symmetric.AEADPrivateKey).Key
2181+
i := bytes.Index(w.Bytes(), firstSymKey)
2182+
2183+
secondSymKey := entity.Subkeys[2].PrivateKey.PrivateKey.(*symmetric.HMACPrivateKey).Key
2184+
k := bytes.Index(w.Bytes(), secondSymKey)
2185+
2186+
if (i > 0) || (k > 0) {
2187+
t.Error("Private key was serialized with public")
2188+
}
2189+
2190+
firstBindingHash := entity.Subkeys[1].PublicKey.PublicKey.(*symmetric.AEADPublicKey).BindingHash
2191+
i = bytes.Index(w.Bytes(), firstBindingHash[:])
2192+
2193+
secondBindingHash := entity.Subkeys[2].PublicKey.PublicKey.(*symmetric.HMACPublicKey).BindingHash
2194+
k = bytes.Index(w.Bytes(), secondBindingHash[:])
2195+
if (i > 0) || (k > 0) {
2196+
t.Errorf("Symmetric public key metadata exported %d %d", i, k)
2197+
}
2198+
2199+
}
2200+
2201+
func TestSymmetricKeys(t *testing.T) {
2202+
data := `-----BEGIN PGP PRIVATE KEY BLOCK-----
2203+
2204+
xWoEYs7w5mUIcFvlmkuricX26x138uvHGlwIaxWIbRnx1+ggPcveTcwA4zSZ
2205+
n6XcD0Q5aLe6dTEBwCyfUecZ/nA0W8Pl9xBHfjIjQuxcUBnIqxZ061RZPjef
2206+
D/XIQga1ftLDelhylQwL7R3TzQ1TeW1tZXRyaWMgS2V5wmkEEGUIAB0FAmLO
2207+
8OYECwkHCAMVCAoEFgACAQIZAQIbAwIeAQAhCRCRTKq2ObiQKxYhBMHTTXXF
2208+
ULQ2M2bYNJFMqrY5uJArIawgJ+5RSsN8VNuZTKJbG88TIedU05wwKjW3wqvT
2209+
X6Z7yfbHagRizvDmZAluL/kJo6hZ1kFENpQkWD/Kfv1vAG3nbxhsVEzBQ6a1
2210+
OAD24BaKJz6gWgj4lASUNK5OuXnLc3J79Bt1iRGkSbiPzRs/bplB4TwbILeC
2211+
ZLeDy9kngZDosgsIk5sBgGEqS9y5HiHCVQQYZQgACQUCYs7w5gIbDAAhCRCR
2212+
TKq2ObiQKxYhBMHTTXXFULQ2M2bYNJFMqrY5uJArENkgL0Bc+OI/1na0XWqB
2213+
TxGVotQ4A/0u0VbOMEUfnrI8Fms=
2214+
=RdCW
2215+
-----END PGP PRIVATE KEY BLOCK-----
2216+
`
2217+
keys, err := ReadArmoredKeyRing(strings.NewReader(data))
2218+
if err != nil {
2219+
t.Fatal(err)
2220+
}
2221+
if len(keys) != 1 {
2222+
t.Errorf("Expected 1 symmetric key, got %d", len(keys))
2223+
}
2224+
if keys[0].PrivateKey.PubKeyAlgo != packet.ExperimentalPubKeyAlgoHMAC {
2225+
t.Errorf("Expected HMAC primary key")
2226+
}
2227+
if len(keys[0].Subkeys) != 1 {
2228+
t.Errorf("Expected 1 symmetric subkey, got %d", len(keys[0].Subkeys))
2229+
}
2230+
if keys[0].Subkeys[0].PrivateKey.PubKeyAlgo != packet.ExperimentalPubKeyAlgoAEAD {
2231+
t.Errorf("Expected AEAD subkey")
2232+
}
2233+
}

Diff for: openpgp/v2/read.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ ParsePackets:
138138
switch p.Algo {
139139
case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly,
140140
packet.PubKeyAlgoElGamal, packet.PubKeyAlgoECDH,
141-
packet.PubKeyAlgoX25519, packet.PubKeyAlgoX448:
141+
packet.PubKeyAlgoX25519, packet.PubKeyAlgoX448, packet.ExperimentalPubKeyAlgoAEAD:
142142
break
143143
default:
144144
continue

Diff for: openpgp/v2/read_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -1000,3 +1000,10 @@ func testMalformedMessage(t *testing.T, keyring EntityList, message string) {
10001000
return
10011001
}
10021002
}
1003+
1004+
func TestReadKeyRingWithSymmetricSubkey(t *testing.T) {
1005+
_, err := ReadArmoredKeyRing(strings.NewReader(keyWithAEADSubkey))
1006+
if err != nil {
1007+
t.Error("could not read keyring", err)
1008+
}
1009+
}

Diff for: openpgp/v2/read_write_test_data.go

+18
Original file line numberDiff line numberDiff line change
@@ -740,3 +740,21 @@ NVniEke6hM3CNBXYPAMhQBMWhCulcoz+0lxi8L34rMN+Dsbma96psdUrn7uLaB91
740740
xqAY9Bwizt4FWgXuLm1a4+So4V9j1TRCXd12Uc2l2RNmgDE=
741741
=miES
742742
-----END PGP PRIVATE KEY BLOCK-----`
743+
744+
// A key that contains a persistent AEAD subkey
745+
const keyWithAEADSubkey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
746+
747+
xVgEYs/4KxYJKwYBBAHaRw8BAQdA7tIsntXluwloh/H62PJMqasjP00M86fv
748+
/Pof9A968q8AAQDYcgkPKUdWAxsDjDHJfouPS4q5Me3ks+umlo5RJdwLZw4k
749+
zQ1TeW1tZXRyaWMgS2V5wowEEBYKAB0FAmLP+CsECwkHCAMVCAoEFgACAQIZ
750+
AQIbAwIeAQAhCRDkNhFDvaU8vxYhBDJNoyEFquVOCf99d+Q2EUO9pTy/5XQA
751+
/1F2YPouv0ydBDJU3EOS/4bmPt7yqvzciWzeKVEOkzYuAP9OsP7q/5ccqOPX
752+
mmRUKwd82/cNjdzdnWZ8Tq89XMwMAMdqBGLP+CtkCfFyZxOMF0BWLwAE8pLy
753+
RVj2n2K7k6VvrhyuTqDkFDUFALiSLrEfnmTKlsPYS3/YzsODF354ccR63q73
754+
3lmCrvFRyaf6AHvVrBYPbJR+VhuTjZTwZKvPPKv0zVdSqi5JDEQiocJ4BBgW
755+
CAAJBQJiz/grAhsMACEJEOQ2EUO9pTy/FiEEMk2jIQWq5U4J/3135DYRQ72l
756+
PL+fEQEA7RaRbfa+AtiRN7a4GuqVEDZi3qtQZ2/Qcb27/LkAD0sA/3r9drYv
757+
jyu46h1fdHHyo0HS2MiShZDZ8u60JnDltloD
758+
=8TxH
759+
-----END PGP PRIVATE KEY BLOCK-----
760+
`

0 commit comments

Comments
 (0)