mirror of
https://github.com/FiloSottile/age.git
synced 2026-09-04 07:07:16 +00:00
agessh: don't cache mismatched encrypted identities
Reported by Joe Doyle of Trail of Bits.
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/pem"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -185,3 +186,68 @@ func TestSSHEd25519FingerprintCollision(t *testing.T) {
|
||||
t.Errorf("invalid output: %x, expected %x", out, fileKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptedSSHIdentityMismatch(t *testing.T) {
|
||||
announcedPub, _, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
embeddedPub, embeddedPriv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
announcedKey, err := ssh.NewPublicKey(announcedPub)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
embeddedKey, err := ssh.NewPublicKey(embeddedPub)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
passphrase := []byte("passphrase")
|
||||
block, err := ssh.MarshalPrivateKeyWithPassphrase(embeddedPriv, "", passphrase)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
prompts := 0
|
||||
i, err := agessh.NewEncryptedSSHIdentity(announcedKey, pem.EncodeToMemory(block), func() ([]byte, error) {
|
||||
prompts++
|
||||
return passphrase, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
announcedRecipient, err := agessh.NewEd25519Recipient(announcedKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
embeddedRecipient, err := agessh.NewEd25519Recipient(embeddedKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fileKey := make([]byte, 16)
|
||||
if _, err := rand.Read(fileKey); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stanzas, err := announcedRecipient.Wrap(fileKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
embeddedStanzas, err := embeddedRecipient.Wrap(fileKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stanzas = append(stanzas, embeddedStanzas...)
|
||||
|
||||
for attempt := 1; attempt <= 2; attempt++ {
|
||||
_, err := i.Unwrap(stanzas)
|
||||
if err == nil || err.Error() != "mismatched private and public SSH key" {
|
||||
t.Fatalf("attempt %d: unexpected error: %v", attempt, err)
|
||||
}
|
||||
if prompts != attempt {
|
||||
t.Fatalf("attempt %d: passphrase callback invoked %d times", attempt, prompts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,16 +109,17 @@ func (i *EncryptedSSHIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, er
|
||||
var pubKey interface {
|
||||
Equal(x crypto.PublicKey) bool
|
||||
}
|
||||
var decrypted age.Identity
|
||||
switch k := k.(type) {
|
||||
case *ed25519.PrivateKey:
|
||||
i.decrypted, err = NewEd25519Identity(*k)
|
||||
decrypted, err = NewEd25519Identity(*k)
|
||||
pubKey = k.Public().(ed25519.PublicKey)
|
||||
// ParseRawPrivateKey returns inconsistent types. See Issue 429.
|
||||
case ed25519.PrivateKey:
|
||||
i.decrypted, err = NewEd25519Identity(k)
|
||||
decrypted, err = NewEd25519Identity(k)
|
||||
pubKey = k.Public().(ed25519.PublicKey)
|
||||
case *rsa.PrivateKey:
|
||||
i.decrypted, err = NewRSAIdentity(k)
|
||||
decrypted, err = NewRSAIdentity(k)
|
||||
pubKey = &k.PublicKey
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected SSH key type: %T", k)
|
||||
@@ -131,5 +132,6 @@ func (i *EncryptedSSHIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, er
|
||||
return nil, fmt.Errorf("mismatched private and public SSH key")
|
||||
}
|
||||
|
||||
i.decrypted = decrypted
|
||||
return i.decrypted.Unwrap(stanzas)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user