OpenNHP/opennhp · critical
private key parse error
Error message
private key parse error %v
What it means
UdpAC.Start fails with "private key parse error %v" when the AC's config.PrivateKeyBase64 cannot be decoded with base64.StdEncoding. The decoded bytes must be a valid Curve25519/SM2 private key for core.NewDevice; a decode failure aborts startup before any device is created.
Solutions
- Regenerate the key with `./nhp-acd keygen --curve --json` and paste only the base64 body (no quotes, no whitespace)
- Strip whitespace/newlines before decoding: base64.StdEncoding.DecodeString(strings.TrimSpace(...)) or wrap in strings.Join(strings.Fields(k), "")
- Confirm the key matches the configured cipher scheme (curve vs SM2)
- Validate the config at load time with a base64 decode check so the failure surfaces with a clear field name
Example fix
// before
prk, err := base64.StdEncoding.DecodeString(a.config.PrivateKeyBase64)
if err != nil {
return fmt.Errorf("private key parse error %v", err)
}
// after
cleaned := strings.Join(strings.Fields(a.config.PrivateKeyBase64), "")
prk, err := base64.StdEncoding.DecodeString(cleaned)
if err != nil {
return fmt.Errorf("private key parse error (invalid base64 in config.toml PrivateKeyBase64): %w", err)
} Defensive patterns
Strategy: validation
Validate before calling
k := strings.TrimSpace(conf.PrivateKeyBase64)
if k == "" { return fmt.Errorf("privateKeyBase64 is empty") }
if _, err := base64.StdEncoding.DecodeString(k); err != nil {
return fmt.Errorf("privateKeyBase64 is not valid std base64: %w", err)
} Try / catch
prk, err := base64.StdEncoding.DecodeString(a.config.PrivateKeyBase64)
if err != nil {
return fmt.Errorf("invalid privateKeyBase64 in config.toml: %w", err)
} Prevention
- Generate keys only via `nhp-acd keygen --json` and paste the raw base64 body
- Strip whitespace/newlines from pasted keys
- Validate base64 fields at config load time, before Start
When it happens
Trigger: PrivateKeyBase64 in config.toml contains whitespace/newlines, uses URL-safe base64 or raw hex, includes the '-----BEGIN...' PEM wrapper, or is empty/placeholder text generated outside `keygen --curve/--sm2 --json`.
Common situations: Hand-editing config.toml and pasting a key with quotes/line breaks; copying an ed25519 or hex key from another tool; mixing curve and SM2 key formats; shell truncating the key during provisioning.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to create device
- private key parse error
- private key parse error
- invalid CookieSigningKeyBase64
- server peer config invalid on initial load
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/22b64baa9b517f13.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/ac/udpac.go:136
a.ipset, err = utils.NewIPSet(false)
if err != nil {
log.Error("ipset command not found")
return
}
case FilterMode_EBPFXDP:
err = ebpflocal.EbpfEngineLoad(dirPath, logLevel, a.config.ACId)
if err != nil {
return err
}
default:
log.Error("[HandleAccessControl] unsupported FilterMode: %d (expected 0=IPTABLES or 1=EBPFXDP)", a.config.FilterMode)
return
}
prk, err := base64.StdEncoding.DecodeString(a.config.PrivateKeyBase64)
if err != nil {
log.Error("private key parse error %v\n", err)
return fmt.Errorf("private key parse error %v", err)
}
a.device = core.NewDevice(core.NHP_AC, prk, nil)
if a.device == nil {
log.Critical("failed to create device %v\n", err)
return fmt.Errorf("failed to create device %v", err)
}
a.remoteConnectionMap = make(map[string]*UdpConn)
a.serverPeerMap = make(map[string]*core.UdpPeer)
a.tokenStore = common.NewTokenStore[*AccessEntry]()
if a.etcdConn != nil {
_ = a.loadRemoteConfig()
} else {
// load http config and turn on http server if needed
_ = a.loadHttpConfig()
View on GitHub (pinned to 6e04ca5ff0)