Skip to content

Commit f27ab7d

Browse files
committed
feat: Add symmetric keys to v2
1 parent 4017cb3 commit f27ab7d

10 files changed

+358
-7
lines changed

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

openpgp/packet/encrypted_key.go

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

224224
var key []byte
225225
switch priv.PubKeyAlgo {
226-
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH:
226+
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH, ExperimentalPubKeyAlgoAEAD:
227227
keyOffset := 0
228228
if e.Version < 6 {
229229
e.CipherFunc = CipherFunction(b[0])

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

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
}

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
}

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

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

openpgp/v2/read_test.go

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

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)