age: use native identities first in Decrypt

This commit is contained in:
Filippo Valsorda
2025-12-07 18:51:19 +01:00
committed by Filippo Valsorda
parent c6fcb5300c
commit 78947d862d
2 changed files with 63 additions and 0 deletions

19
age.go
View File

@@ -214,6 +214,7 @@ func (*NoIdentityMatchError) Error() string {
//
// It returns a Reader reading the decrypted plaintext of the age file read
// from src. All identities will be tried until one successfully decrypts the file.
// Native, non-interactive identities are tried before any other identities.
//
// If no identity matches the encrypted file, the returned error will be of type
// [NoIdentityMatchError].
@@ -240,6 +241,24 @@ func decryptHdr(hdr *format.Header, identities ...Identity) ([]byte, error) {
if len(identities) == 0 {
return nil, errors.New("no identities specified")
}
slices.SortStableFunc(identities, func(a, b Identity) int {
var aIsNative, bIsNative bool
switch a.(type) {
case *X25519Identity, *HybridIdentity, *ScryptIdentity:
aIsNative = true
}
switch b.(type) {
case *X25519Identity, *HybridIdentity, *ScryptIdentity:
bIsNative = true
}
if aIsNative && !bIsNative {
return -1
}
if !aIsNative && bIsNative {
return 1
}
return 0
})
stanzas := make([]*Stanza, 0, len(hdr.Recipients))
for _, s := range hdr.Recipients {

View File

@@ -285,6 +285,50 @@ func TestLabels(t *testing.T) {
}
}
// testIdentity is a non-native identity that records if Unwrap is called.
type testIdentity struct {
called bool
}
func (ti *testIdentity) Unwrap(stanzas []*age.Stanza) ([]byte, error) {
ti.called = true
return nil, age.ErrIncorrectIdentity
}
func TestDecryptNativeIdentitiesFirst(t *testing.T) {
correct, err := age.GenerateX25519Identity()
if err != nil {
t.Fatal(err)
}
unrelated, err := age.GenerateX25519Identity()
if err != nil {
t.Fatal(err)
}
buf := &bytes.Buffer{}
w, err := age.Encrypt(buf, correct.Recipient())
if err != nil {
t.Fatal(err)
}
if err := w.Close(); err != nil {
t.Fatal(err)
}
nonNative := &testIdentity{}
// Pass identities: unrelated native, non-native, correct native.
// Native identities should be tried first, so correct should match
// before nonNative is ever called.
_, err = age.Decrypt(bytes.NewReader(buf.Bytes()), unrelated, nonNative, correct)
if err != nil {
t.Fatal(err)
}
if nonNative.called {
t.Error("non-native identity was called, but native identities should be tried first")
}
}
func TestDetachedHeader(t *testing.T) {
i, err := age.GenerateX25519Identity()
if err != nil {