ory/hydra · error

unsupported private / public key pairs: %T, %T

Error message

unsupported private / public key pairs: %T, %T

What it means

jwt.DefaultSigner.Generate only supports known private key types: ECDSA (ECPrivateKey), RSA (RSAPrivateKey), ed25519 (Ed25519PrivateKey) and their *jose.JSONWebKey/e.JWK wrappers. Falling into the key-pair 'default' branch means the key pair type (the second %T) is not one of these, so no token can be signed.

Source

Thrown at fosite/token/jwt/jwt.go:75

		return generateToken(claims, header, jose.ES256, t)
	case jose.OpaqueSigner:
		switch tt := t.Public().Key.(type) {
		case *rsa.PrivateKey:
			alg := jose.RS256
			if len(t.Algs()) > 0 {
				alg = t.Algs()[0]
			}

			return generateToken(claims, header, alg, t)
		case *ecdsa.PrivateKey:
			alg := jose.ES256
			if len(t.Algs()) > 0 {
				alg = t.Algs()[0]
			}

			return generateToken(claims, header, alg, t)
		default:
			return "", "", errors.Errorf("unsupported private / public key pairs: %T, %T", t, tt)
		}
	default:
		return "", "", errors.Errorf("unsupported private key type: %T", t)
	}
}

// Validate validates a token and returns its signature or an error if the token is not valid.
func (j *DefaultSigner) Validate(ctx context.Context, token string) (string, error) {
	key, err := j.GetPrivateKey(ctx)
	if err != nil {
		return "", err
	}

	if t, ok := key.(*jose.JSONWebKey); ok {
		key = t.Key
	}

	switch t := key.(type) {

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Provide a supported key type: *ecdsa.PrivateKey, *rsa.PrivateKey, ed25519.PrivateKey, or a *jose.JSONWebKey wrapping one of them
  2. Parse PEM/DER into the concrete Go key type before passing it to the signer (e.g. x509.ParseECPrivateKey / ParsePKCS8PrivateKey with type switch)
  3. If keys come from a KMS/HSM, wrap them in a type the signer understands or implement your own Signer interface
  4. Log the key's %T at startup to confirm which type is actually configured

Example fix

// before
signer := jwt.NewSignerRS256("key-id", pemBytes) // raw bytes, wrong type
// after
block, _ := pem.Decode(pemBytes)
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
rsaKey, ok := key.(*rsa.PrivateKey)
if !ok { return errors.New("expected RSA private key") }
signer := jwt.NewSignerRS256("key-id", rsaKey)
Defensive patterns

Strategy: type-guard

Validate before calling

func assertSignableKey(k interface{}) error {
    switch t := k.(type) {
    case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *jose.JSONWebKey, *jwt.ECDSAKeyPair, *jwt.RSAKeyPair, *jwt.Ed25519KeyPair:
        return nil
    default:
        return fmt.Errorf("unsupported signing key type %T", k)
    }
}

Type guard

func isSupportedJWTKey(k interface{}) bool {
    switch k.(type) {
    case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *jose.JSONWebKey,
         *jwt.ECDSAKeyPair, *jwt.RSAKeyPair, *jwt.Ed25519KeyPair:
        return true
    }
    return false
}

Try / catch

token, sig, err := signer.Generate(ctx, claims, header)
if err != nil && strings.Contains(err.Error(), "unsupported private") {
    log.Fatalf("signing key misconfigured: %v", err)
}

Prevention

When it happens

Trigger: Calling Generate (public) with a key pair whose underlying key type is unsupported — e.g. a custom signer exposing a *jose.JSONWebKey whose embedded key is an unsupported algorithm/key type, or passing a pointer-wrapper/PEM string instead of a parsed private key.

Common situations: Loading keys with a parser that yields an unexpected type (e.g. PKCS#1 parsed into interface{}, opaque crypto.Signer wrappers, HSM keys); swapping key providers (KMS, vault) without adapting to jose JSONWebKey; typos when constructing the signer's key interface.

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/72ebca78c2d99707. Report an issue: GitHub.