Skip to content

Commit 91c2e9e

Browse files
committed
feat: Add symmetric keys to v2
1 parent 3f6d02a commit 91c2e9e

10 files changed

+359
-8
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
@@ -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])

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
)
@@ -393,6 +394,9 @@ func newSigner(config *packet.Config) (signer interface{}, err error) {
393394
return nil, err
394395
}
395396
return priv, nil
397+
case packet.ExperimentalPubKeyAlgoHMAC:
398+
hash := algorithm.HashById[hashToHashId(config.Hash())]
399+
return symmetric.HMACGenerateKey(config.Random(), hash)
396400
default:
397401
return nil, errors.InvalidArgumentError("unsupported public key algorithm")
398402
}
@@ -435,6 +439,9 @@ func newDecrypter(config *packet.Config) (decrypter interface{}, err error) {
435439
return x25519.GenerateKey(config.Random())
436440
case packet.PubKeyAlgoEd448, packet.PubKeyAlgoX448: // When passing Ed448, we generate an x448 subkey
437441
return x448.GenerateKey(config.Random())
442+
case packet.ExperimentalPubKeyAlgoAEAD:
443+
cipher := algorithm.CipherFunction(config.Cipher())
444+
return symmetric.AEADGenerateKey(config.Random(), cipher)
438445
default:
439446
return nil, errors.InvalidArgumentError("unsupported public key algorithm")
440447
}

Diff for: openpgp/v2/keys.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (e *Entity) PrimaryIdentity(date time.Time, config *packet.Config) (*packet
6161
var primaryIdentityCandidatesSelfSigs []*packet.Signature
6262
for _, identity := range e.Identities {
6363
selfSig, err := identity.Verify(date, config) // identity must be valid at date
64-
if err == nil { // verification is successful
64+
if err == nil { // verification is successful
6565
primaryIdentityCandidates = append(primaryIdentityCandidates, identity)
6666
primaryIdentityCandidatesSelfSigs = append(primaryIdentityCandidatesSelfSigs, selfSig)
6767
}
@@ -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
}
@@ -784,5 +798,5 @@ func isValidCertificationKey(signature *packet.Signature, algo packet.PublicKeyA
784798
func isValidEncryptionKey(signature *packet.Signature, algo packet.PublicKeyAlgorithm) bool {
785799
return algo.CanEncrypt() &&
786800
signature.FlagsValid &&
787-
(signature.FlagEncryptCommunications || signature.FlagEncryptStorage)
801+
(signature.FlagEncryptCommunications || signature.FlagForward || signature.FlagEncryptStorage)
788802
}

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

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
@@ -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+
}

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)