Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [ main ]
pull_request:
branches: [ main, v3 ]
branches: [ main, Proton ]

permissions: {}

Expand All @@ -23,7 +23,7 @@ jobs:
- name: Set up Go 1.x
uses: actions/setup-go@v6
with:
go-version: ^1.16
go-version: '^1.16'
id: go

- name: Install NDK
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [ main ]
pull_request:
branches: [ main, v3 ]
branches: [ main, Proton ]

permissions: {}

Expand All @@ -23,7 +23,7 @@ jobs:
- name: Set up Go 1.x
uses: actions/setup-go@v6
with:
go-version: ^1.16
go-version: '^1.22'
id: go

- name: Checkout
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sop-test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: SOP interoperability test suite

on:
pull_request:
branches: [ main, v3 ]
branches: [ main, Proton ]

permissions: {}

Expand Down
2 changes: 2 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ issues:
- param max has same name as predeclared identifier
- G115
- the methods of "signatureCollector"
- SA1019
- commentFormatting
exclude-rules:
- path: crypto/key_clear.go
text: "SA1019"
Expand Down
4 changes: 2 additions & 2 deletions crypto/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ func readTestFile(name string, trimNewlines bool) string {
func init() {
testPGP = PGP()
testPGP.defaultTime = NewConstantClock(testTime) // 2019-05-13T13:37:07+00:00
testProfiles = []*profile.Custom{profile.Default(), profile.RFC4880(), profile.RFC9580()}
testProfileNames = []string{"Default", "RFC4880", "RFC9580"}
testProfiles = []*profile.Custom{profile.Default(), profile.RFC4880(), profile.RFC9580(), profile.Symmetric(), profile.PQC()}
testProfileNames = []string{"Default", "RFC4880", "RFC9580", "Symmetric", "PQC"}
initEncDecTest()
initGenerateKeys()
initArmoredKeys()
Expand Down
25 changes: 25 additions & 0 deletions crypto/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,31 @@ func (key *Key) GetSHA256Fingerprint() (fingerprint string) {
return hex.EncodeToString(getSHA256FingerprintBytes(key.entity.PrimaryKey))
}

// IsForwardingKey checks if the given key is a Proton forwarding key.
func (key *Key) IsForwardingKey() bool {
decryptionKeys := key.entity.DecryptionKeys(0, time.Time{}, nil)
allForwarding := len(decryptionKeys) > 0
for _, key := range decryptionKeys {
if !isForwardingKey(key) {
allForwarding = false
break
}
}
return allForwarding
}

// isForwardingKey determines if the given openpgp.Key is a forwarding key.
func isForwardingKey(key openpgp.Key) bool {
curve, err := key.PublicKey.Curve()
keyCheck := key.PublicKey.IsSubkey &&
key.PublicKey.Version == 4 &&
key.PublicKey.PubKeyAlgo == packet.PubKeyAlgoECDH &&
err == nil && curve == packet.Curve25519
hasForwardFlag := key.SelfSignature != nil &&
key.SelfSignature.FlagForward
return keyCheck && hasForwardFlag
}

// GetSHA256Fingerprints computes the SHA256 fingerprints of the key and subkeys.
func (key *Key) GetSHA256Fingerprints() (fingerprints []string) {
fingerprints = append(fingerprints, key.GetSHA256Fingerprint())
Expand Down
3 changes: 1 addition & 2 deletions crypto/key_clear.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package crypto
import (
"crypto/dsa"
"crypto/rsa"
"errors"
"math/big"

"github.com/ProtonMail/go-crypto/openpgp/ecdh"
Expand Down Expand Up @@ -76,7 +75,7 @@ func clearPrivateKey(privateKey interface{}) error {
case *ed448.PrivateKey:
return clearEd448PrivateKey(priv)
default:
return errors.New("gopenpgp: unknown private key")
return nil
}
}

Expand Down
13 changes: 13 additions & 0 deletions crypto/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,19 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err
return filteredKeys, nil
}

// WithoutForwardingKeys returns a new keyring containing only non-forwarding keys.
func (keyRing *KeyRing) WithoutForwardingKeys() (*KeyRing, error) {
filtered := &KeyRing{}
for _, key := range keyRing.GetKeys() {
if !key.IsForwardingKey() {
if err := filtered.AddKey(key); err != nil {
return nil, fmt.Errorf("gopenpgp: failed to add key to filtered keyring: %w", err)
}
}
}
return filtered, nil
}

// FirstKey returns a KeyRing with only the first key of the original one.
func (keyRing *KeyRing) FirstKey() (*KeyRing, error) {
if len(keyRing.entities) == 0 {
Expand Down
119 changes: 119 additions & 0 deletions crypto/proton_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package crypto

import (
"encoding/base64"
"testing"

"github.com/stretchr/testify/assert"
)

func TestForwardeeDecryption(t *testing.T) {
//pgp.latestServerTime = 1679044110

forwardeeKey, err := NewKeyFromArmored(readTestFile("key_forwardee", false))
if err != nil {
t.Fatal("Expected no error while unarmoring private keyring, got:", err)
}

forwardeeKeyRing, err := NewKeyRing(forwardeeKey)
if err != nil {
t.Fatal("Expected no error while building private keyring, got:", err)
}

pgpMessage := readTestFile("message_forwardee", false)
decryptor, err := PGP().Decryption().
DecryptionKeys(forwardeeKeyRing).
VerifyTime(1679044110).
New()
if err != nil {
t.Fatal(err)
}
plainMessage, err := decryptor.Decrypt([]byte(pgpMessage), Armor)
if err != nil {
t.Fatal("Expected no error while decrypting/verifying, got:", err)
}

assert.Exactly(t, "Message for Bob", plainMessage.String())
}

func TestFowardingKeyCheck(t *testing.T) {
forwardingKey, err := NewKeyFromArmored(readTestFile("key_forwardee", false))
if err != nil {
t.Fatal("Expected no error while unarmoring private keyring, got:", err)
}

nonForwardingKey, err := NewKeyFromArmored(readTestFile("keyring_userKey", false))
if err != nil {
t.Fatal("Expected no error while unarmoring private keyring, got:", err)
}

if !forwardingKey.IsForwardingKey() {
t.Fatal("Expected a forwarding key")
}

if nonForwardingKey.IsForwardingKey() {
t.Fatal("Expected non-forwarding key")
}

kr, err := NewKeyRing(forwardingKey)
if err != nil {
t.Fatal(err)
}

if err := kr.AddKey(nonForwardingKey); err != nil {
t.Fatal(err)
}

krWithoutForwarding, err := kr.WithoutForwardingKeys()
if err != nil {
t.Fatal(err)
}

assert.Exactly(t, 1, krWithoutForwarding.CountEntities())

key, err := krWithoutForwarding.GetKey(0)
if err != nil {
t.Fatal(err)
}
assert.False(t, key.IsForwardingKey())
}

func TestSymmetricKeys(t *testing.T) {
symmetricKey, err := NewKeyFromArmored(readTestFile("key_symmetric", false))
if err != nil {
t.Fatal("Expected no error while unarmoring private keyring, got:", err)
}

symmetricKeyRing, err := NewKeyRing(symmetricKey)
if err != nil {
t.Fatal("Expected no error while building private keyring, got:", err)
}

binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
pgp := PGP()
encryptor, err := pgp.Encryption().
Recipients(symmetricKeyRing).
SignTime(1679044110).
New()
if err != nil {
t.Fatal(err)
}

ciphertext, err := encryptor.Encrypt(binData)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}

decryptor, err := pgp.Decryption().
DecryptionKeys(symmetricKeyRing).
VerifyTime(1679044110).
New()
if err != nil {
t.Fatal(err)
}
decrypted, err := decryptor.Decrypt(ciphertext.Bytes(), Bytes)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, binData, decrypted.Bytes())
}
15 changes: 15 additions & 0 deletions crypto/testdata/key_forwardee
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----

xVgEZAdtGBYJKwYBBAHaRw8BAQdAcNgHyRGEaqGmzEqEwCobfUkyrJnY8faBvsf9
R2c5ZzYAAP9bFL4nPBdo04ei0C2IAh5RXOpmuejGC3GAIn/UmL5cYQ+XzRtjaGFy
bGVzIDxjaGFybGVzQHByb3Rvbi5tZT7CigQTFggAPAUCZAdtGAmQFXJtmBzDhdcW
IQRl2gNflypl1XjRUV8Vcm2YHMOF1wIbAwIeAQIZAQILBwIVCAIWAAIiAQAAJKYA
/2qY16Ozyo5erNz51UrKViEoWbEpwY3XaFVNzrw+b54YAQC7zXkf/t5ieylvjmA/
LJz3/qgH5GxZRYAH9NTpWyW1AsdxBGQHbRgSCisGAQQBl1UBBQEBB0CxmxoJsHTW
TiETWh47ot+kwNA1hCk1IYB9WwKxkXYyIBf/CgmKXzV1ODP/mRmtiBYVV+VQk5MF
EAAA/1NW8D8nMc2ky140sPhQrwkeR7rVLKP2fe5n4BEtAnVQEB3CeAQYFggAKgUC
ZAdtGAmQFXJtmBzDhdcWIQRl2gNflypl1XjRUV8Vcm2YHMOF1wIbUAAAl/8A/iIS
zWBsBR8VnoOVfEE+VQk6YAi7cTSjcMjfsIez9FYtAQDKo9aCMhUohYyqvhZjn8aS
3t9mIZPc+zRJtCHzQYmhDg==
=lESj
-----END PGP PRIVATE KEY BLOCK-----
15 changes: 15 additions & 0 deletions crypto/testdata/key_symmetric
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----

xVgEYs/4KxYJKwYBBAHaRw8BAQdA7tIsntXluwloh/H62PJMqasjP00M86fv
/Pof9A968q8AAQDYcgkPKUdWAxsDjDHJfouPS4q5Me3ks+umlo5RJdwLZw4k
zQ1TeW1tZXRyaWMgS2V5wowEEBYKAB0FAmLP+CsECwkHCAMVCAoEFgACAQIZ
AQIbAwIeAQAhCRDkNhFDvaU8vxYhBDJNoyEFquVOCf99d+Q2EUO9pTy/5XQA
/1F2YPouv0ydBDJU3EOS/4bmPt7yqvzciWzeKVEOkzYuAP9OsP7q/5ccqOPX
mmRUKwd82/cNjdzdnWZ8Tq89XMwMAMdqBGLP+CtkCfFyZxOMF0BWLwAE8pLy
RVj2n2K7k6VvrhyuTqDkFDUFALiSLrEfnmTKlsPYS3/YzsODF354ccR63q73
3lmCrvFRyaf6AHvVrBYPbJR+VhuTjZTwZKvPPKv0zVdSqi5JDEQiocJ4BBgW
CAAJBQJiz/grAhsMACEJEOQ2EUO9pTy/FiEEMk2jIQWq5U4J/3135DYRQ72l
PL+fEQEA7RaRbfa+AtiRN7a4GuqVEDZi3qtQZ2/Qcb27/LkAD0sA/3r9drYv
jyu46h1fdHHyo0HS2MiShZDZ8u60JnDltloD
=8TxH
-----END PGP PRIVATE KEY BLOCK-----
8 changes: 8 additions & 0 deletions crypto/testdata/message_forwardee
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN PGP MESSAGE-----

wV4DB27Wn97eACkSAQdA62TlMU2QoGmf5iBLnIm4dlFRkLIg+6MbaatghwxK+Ccw
yGZuVVMAK/ypFfebDf4D/rlEw3cysv213m8aoK8nAUO8xQX3XQq3Sg+EGm0BNV8E
0kABEPyCWARoo5klT1rHPEhelnz8+RQXiOIX3G685XCWdCmaV+tzW082D0xGXSlC
7lM8r1DumNnO8srssko2qIja
=pVRa
-----END PGP MESSAGE-----
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/ProtonMail/gopenpgp/v3
go 1.23.0

require (
github.com/ProtonMail/go-crypto v1.4.1
github.com/ProtonMail/go-crypto v1.4.1-proton
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f
github.com/stretchr/testify v1.10.0
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/ProtonMail/go-crypto v1.4.1-proton h1:I4nanwGUmEeu7bTP9pkVWBbebGaxGyeMPnPRKmL++DY=
github.com/ProtonMail/go-crypto v1.4.1-proton/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
Expand Down
Loading
Loading