cmd/age: reject malformed supported SSH recipients

Reported by Joe Doyle of Trail of Bits.
This commit is contained in:
Filippo Valsorda
2026-08-29 19:30:10 +02:00
parent 418c7ac33b
commit 343dda9aa4
2 changed files with 69 additions and 3 deletions
+46
View File
@@ -117,6 +117,52 @@ func TestParseFileSizeLimit(t *testing.T) {
}
}
func TestUnsupportedSSHKey(t *testing.T) {
tests := []struct {
name, key, want string
ok bool
}{
{
"truncated Ed25519",
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH9pO5pz22JZEasoS2LEWWvJiUYI9M6l1uZc31FG",
"", false,
},
{
"type-only RSA",
"ssh-rsa AAAAB3NzaC1yc2E=",
"", false,
},
{
"small RSA",
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCxlfoYpG04TTnmjocggQZI5l0fjvaUMky1ZD5zwktDKAgBj441OAtFj/3m7Gujpxx/8w3jjJoUXVtU+NFbcC972ROpPI4aPq3OR5SmuuR5bCr6efeZlyAEMY/DmbkeUe1TrvdU3VMDGJAvEEzT1wzokKt9PiwbV2jNkdPjaG5LDQ==",
"ssh-rsa", true,
},
{
"invalid Ed25519 point",
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC",
"", false,
},
{
"unknown type",
"totally-not-a-key AAAAEXRvdGFsbHktbm90LWEta2V5",
"totally-not-a-key", true,
},
{
"ECDSA",
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOSh0Qo5zaBE33EiJnxwIBuZml9Nt9rCfvGi++FniWSk8X/10EO4E7KMgqGLlkCTDNIcbuKcsgfMw1/tifB8aTc=",
"ecdsa-sha2-nistp256", true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := unsupportedSSHKey(tt.key)
if got != tt.want || ok != tt.ok {
t.Errorf("unsupportedSSHKey() = %q, %v; want %q, %v", got, ok, tt.want, tt.ok)
}
})
}
}
var buildExtraCommands = sync.OnceValue(func() error {
bindir := filepath.SplitList(os.Getenv("PATH"))[0]
// Build age-keygen and age-plugin-pq into the test binary directory.
+23 -3
View File
@@ -6,6 +6,7 @@ package main
import (
"bufio"
"crypto/rsa"
"encoding/base64"
"fmt"
"io"
@@ -88,7 +89,7 @@ func parseRecipientsFile(name string) ([]age.Recipient, error) {
}
r, err := parseRecipient(line)
if err != nil {
if t, ok := sshKeyType(line); ok {
if t, unsupported := unsupportedSSHKey(line); unsupported {
// Skip unsupported but valid SSH public keys with a warning.
warningf("recipients file %q: ignoring unsupported SSH key of type %q at line %d", name, t, n)
continue
@@ -114,7 +115,7 @@ func parseRecipientsFile(name string) ([]age.Recipient, error) {
return recs, nil
}
func sshKeyType(s string) (string, bool) {
func unsupportedSSHKey(s string) (string, bool) {
// TODO: also ignore options? And maybe support multiple spaces and tabs as
// field separators like OpenSSH?
fields := strings.Split(s, " ")
@@ -132,7 +133,26 @@ func sshKeyType(s string) (string, bool) {
return "", false
}
if t := fields[0]; t == string(typeBytes) {
return t, true
switch t {
case "ssh-ed25519":
return "", false
case "ssh-rsa":
out, _, _, _, err := ssh.ParseAuthorizedKey([]byte(s))
if err != nil {
return "", false
}
cryptoKey, ok := out.(ssh.CryptoPublicKey)
if !ok {
return "", false
}
rsaKey, ok := cryptoKey.CryptoPublicKey().(*rsa.PublicKey)
if !ok {
return "", false
}
return t, rsaKey.N.BitLen() < 2048
default:
return t, true
}
}
return "", false
}