router-for-me/CLIProxyAPI · error

client key pem is invalid

Error message

client key pem is invalid

What it means

Returned by parseRSAPrivateKeyPEM in internal/home/certificate.go when pem.Decode returns nil for the client key bytes — the file is not PEM at all. The enrollment/client-cert path requires an RSA private key in PEM form (PKCS#1 'RSA PRIVATE KEY' or PKCS#8 'PRIVATE KEY').

Source

Thrown at internal/home/certificate.go:264

	if errWrite := os.WriteFile(path, raw, 0o600); errWrite != nil {
		return errWrite
	}
	return os.Chmod(path, 0o600)
}

func chmodCertificateFiles(paths certificatePaths) error {
	for _, path := range []string{paths.ClientCert, paths.ClientKey, paths.CACert} {
		if errChmod := os.Chmod(path, 0o600); errChmod != nil {
			return errChmod
		}
	}
	return nil
}

func parseRSAPrivateKeyPEM(raw []byte) (*rsa.PrivateKey, error) {
	block, _ := pem.Decode(raw)
	if block == nil {
		return nil, fmt.Errorf("client key pem is invalid")
	}
	switch block.Type {
	case "RSA PRIVATE KEY":
		return x509.ParsePKCS1PrivateKey(block.Bytes)
	case "PRIVATE KEY":
		key, errParse := x509.ParsePKCS8PrivateKey(block.Bytes)
		if errParse != nil {
			return nil, errParse
		}
		rsaKey, ok := key.(*rsa.PrivateKey)
		if !ok {
			return nil, fmt.Errorf("client key is not rsa")
		}
		return rsaKey, nil
	default:
		return nil, fmt.Errorf("client key pem type %q is unsupported", block.Type)
	}
}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Check the first line is -----BEGIN ... PRIVATE KEY----- (RSA PRIVATE KEY or PRIVATE KEY)
  2. If the key is DER, convert: openssl rsa -in key.der -inform DER -out key.pem
  3. Re-run enrollment to regenerate the client key pair if the original is lost

Example fix

# before
home:
  client-key: /etc/cliproxy/client.key.der

# after
openssl rsa -in /etc/cliproxy/client.key.der -inform DER -out /etc/cliproxy/client.key
home:
  client-key: /etc/cliproxy/client.key
Defensive patterns

Strategy: validation

Validate before calling

if raw, err := os.ReadFile(cfg.ClientKey); err != nil {
    return fmt.Errorf("client key unreadable: %w", err)
} else if _, ok := pem.Decode(raw); !ok {
    return fmt.Errorf("client key %s is not PEM encoded", cfg.ClientKey)
}

Prevention

When it happens

Trigger: Client key path points to a non-PEM file: a DER key, a JSON/seed file, an empty file, or a placeholder created by touch; key file truncated during provisioning.

Common situations: Provisioning script wrote the key in the wrong format; key downloaded via a channel that mangled it (HTML-escaped, base64-not-decoded); container secret mounted incorrectly leaving an empty file; file saved with a BOM.

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/b403748efee8d457. Report an issue: GitHub.