FiloSottile/age · error

pk does not implement ssh.CryptoPublicKey

Error message

pk does not implement ssh.CryptoPublicKey

What it means

NewRSARecipient requires the ssh.PublicKey to implement the ssh.CryptoPublicKey interface so the raw *rsa.PublicKey can be extracted. This error means the value passed does not implement that interface, so age cannot perform RSA-OAEP encryption. With keys from the standard x/crypto/ssh parsers this essentially never happens; it indicates a hand-rolled ssh.PublicKey type.

Source

Thrown at agessh/agessh.go:64

var _ age.Recipient = &RSARecipient{}

func NewRSARecipient(pk ssh.PublicKey) (*RSARecipient, error) {
	if pk.Type() != "ssh-rsa" {
		return nil, errors.New("SSH public key is not an RSA key")
	}
	r := &RSARecipient{
		sshKey: pk,
	}

	if pk, ok := pk.(ssh.CryptoPublicKey); ok {
		if pk, ok := pk.CryptoPublicKey().(*rsa.PublicKey); ok {
			r.pubKey = pk
		} else {
			return nil, errors.New("unexpected public key type")
		}
	} else {
		return nil, errors.New("pk does not implement ssh.CryptoPublicKey")
	}
	if r.pubKey.N.BitLen() < 2048 {
		return nil, errors.New("RSA key size is too small")
	}
	return r, nil
}

func (r *RSARecipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
	if r.pubKey.N.BitLen() < 2048 {
		return nil, errors.New("RSA key size is too small")
	}
	l := &age.Stanza{
		Type: "ssh-rsa",
		Args: []string{sshFingerprint(r.sshKey)},
	}

	wrappedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader,
		r.pubKey, fileKey, []byte(oaepLabel))

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Use keys from golang.org/x/crypto/ssh (ParsePublicKey, NewPublicKey, ParseAuthorizedKey), which implement CryptoPublicKey.
  2. Add a CryptoPublicKey() crypto.PublicKey method returning the *rsa.PublicKey to your custom key type.
  3. Guard with a type assertion to ssh.CryptoPublicKey before calling.

Example fix

// before
rec, err := agessh.NewRSARecipient(pk) // pk is a custom type
// after
if _, ok := pk.(ssh.CryptoPublicKey); !ok {
    return fmt.Errorf("unsupported ssh public key type %T", pk)
}
rec, err = agessh.NewRSARecipient(pk)
Defensive patterns

Strategy: type-guard

Validate before calling

if _, ok := pk.(ssh.CryptoPublicKey); !ok {
    return fmt.Errorf("key type %T does not implement ssh.CryptoPublicKey", pk)
}

Type guard

func supportsCrypto(pk ssh.PublicKey) bool {
    _, ok := pk.(ssh.CryptoPublicKey)
    return ok
}

Try / catch

rec, err := agessh.NewRSARecipient(pk)
if err != nil {
    if strings.Contains(err.Error(), "ssh.CryptoPublicKey") {
        return fmt.Errorf("unsupported key implementation %T", pk)
    }
    return err
}

Prevention

When it happens

Trigger: Calling agessh.NewRSARecipient(pk) where pk does not implement ssh.CryptoPublicKey() crypto.PublicKey — only realistic with a custom ssh.PublicKey implementation or a nil/malformed wrapper.

Common situations: Mock ssh.PublicKey values in tests; keys constructed by third-party SSH libraries that do not implement CryptoPublicKey.

Related errors


AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31). Data as JSON: /api/errors/0581c21993622ec7. Report an issue: GitHub.