ory/kratos · error

failed to decode PEM block containing private key

Error message

failed to decode PEM block containing private key

What it means

ProviderApple.newClientSecret decodes the configured Apple private key with pem.Decode and requires a PEM block of type 'PRIVATE KEY'. If the stored value is not valid PEM or the block type differs, it returns this error instead of proceeding to PKCS8 parsing. This happens when the Apple Sign-in .p8 key is not stored in proper PEM form in configuration.

Solutions

  1. Store the full PEM text including '-----BEGIN PRIVATE KEY-----' and '-----END PRIVATE KEY-----' lines
  2. When using an env var, keep newlines as \n escapes and ensure the config loader expands them, or base64-wrap it if supported
  3. Verify the block type is 'PRIVATE KEY' (PKCS8); convert PKCS1 'EC PRIVATE KEY' keys with: openssl pkcs8 -topk8 -nocrypt -in apple.p8
  4. Trim whitespace/newline mangling caused by YAML/env transport

Example fix

// before
"private_key": "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEB..."
// after
"private_key": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEB...\n-----END PRIVATE KEY-----"
Defensive patterns

Strategy: validation

Validate before calling

if !strings.Contains(privKey, "-----BEGIN PRIVATE KEY-----") { return errors.New("apple private_key must be PKCS8 PEM with BEGIN/END PRIVATE KEY armor") }

Prevention

When it happens

Trigger: Configuring the Apple OIDC provider with a private_key value that is raw base64/DER bytes without the BEGIN/END PEM armor, or a PEM block whose Type is not exactly 'PRIVATE KEY' (e.g. 'EC PRIVATE KEY').

Common situations: Admins paste the .p8 key content without the '-----BEGIN PRIVATE KEY-----' header; newline characters are stripped when the key is stored in an environment variable; an old pem-encoded key in a different format (PKCS1) is used.

Understand the failure class

Related errors


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

Appendix: source

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

func NewProviderApple(
	config *Configuration,
	reg Dependencies,
) Provider {
	config.IssuerURL = "https://appleid.apple.com"
	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),

View on GitHub (pinned to b86338da04)