ory/kratos · error

Private key decoding failed

Error message

Private key decoding failed

What it means

Apple Sign-In uses a private key (client_secret generated as a signed ES256 JWT). newClientSecret parses the configured PEM private key with x509.ParsePKCS8PrivateKey; this error wraps a failure to parse the PKCS#8 DER bytes inside the PEM block. The PEM was decodable but its contents are not a valid PKCS#8 private key.

Solutions

  1. Re-download the .p8 key from the Apple Developer portal and re-encode it as proper PKCS#8 PEM (openssl pkcs8 -topk8 -nocrypt -in key.pem).
  2. If configured via environment variable, ensure newlines are preserved (use \n escapes the config loader understands or a file secret).
  3. Verify with `openssl pkey -in key.pem -noout` that the key parses outside the app.
  4. Confirm the PEM block type is "PRIVATE KEY" (PKCS#8), not "EC PRIVATE KEY" or "RSA PRIVATE KEY".

Example fix

// before: APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIGT..." (literal backslash-n)
// after:  load the key from a file/secret so real newlines are preserved
Defensive patterns

Strategy: validation

Validate before calling

pemBytes := []byte(privateKey)
block, _ := pem.Decode(pemBytes)
if block == nil || block.Type != "PRIVATE KEY" { return errors.New("key is not PKCS#8 PEM") }
if _, err := x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { return fmt.Errorf("invalid PKCS#8 key: %w", err) }

Type guard

func isPKCS8ECDSAPrivateKey(pemStr string) (*ecdsa.PrivateKey, bool) {
  block, _ := pem.Decode([]byte(pemStr))
  if block == nil || block.Type != "PRIVATE KEY" { return nil, false }
  k, err := x509.ParsePKCS8PrivateKey(block.Bytes)
  if err != nil { return nil, false }
  ek, ok := k.(*ecdsa.PrivateKey)
  return ek, ok
}

Prevention

When it happens

Trigger: Calling newClientSecret (via the oauth2 flow) when the apple private_key config value contains a PEM "PRIVATE KEY" block whose DER payload fails x509.ParsePKCS8PrivateKey — e.g. corrupted base64, wrong key format (PKCS#1 / SEC1 instead of PKCS#8), or truncated key.

Common situations: Copying an Apple .p8 key with extra whitespace/newlines mangled during copy-paste, env-var round-tripping stripping newlines (\n not interpreted), or using a key exported in a non-PKCS8 format.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of ory/kratos@b86338da04 (2026-09-07). Data as JSON: /api/errors/eeb85f64140a4ed8. Report an issue: GitHub.

Appendix: source

Thrown at selfservice/strategy/oidc/provider_apple.go:53

	return &ProviderApple{
		ProviderGenericOIDC: &ProviderGenericOIDC{
			config: config,
			reg:    reg,
		},
		JWKSUrl: "https://appleid.apple.com/auth/keys",
	}
}

func (a *ProviderApple) newClientSecret() (string, error) {
	// decode the pem format
	block, _ := pem.Decode([]byte(a.config.PrivateKey))
	if block == nil || block.Type != "PRIVATE KEY" {
		return "", errors.New("failed to decode PEM block containing private key")
	}

	parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return "", errors.Wrap(err, "Private key decoding failed")
	}
	privateKey, ok := parsedKey.(*ecdsa.PrivateKey)
	if !ok {
		return "", errors.New("Private key is not ecdsa key")
	}

	now := time.Now()
	expirationTime := time.Now().Add(5 * time.Minute)

	appleToken := jwt.NewWithClaims(jwt.SigningMethodES256,
		jwt.RegisteredClaims{
			Audience:  []string{"https://appleid.apple.com"},
			ExpiresAt: jwt.NewNumericDate(expirationTime),
			IssuedAt:  jwt.NewNumericDate(now),
			Issuer:    a.config.TeamId,
			Subject:   a.config.ClientID,
		})
	appleToken.Header["kid"] = a.config.PrivateKeyId

View on GitHub (pinned to b86338da04)