OpenNHP/opennhp · error
keystore: generate otp
Error message
keystore: generate otp: %w
What it means
GenerateOTP calls randomDigits(6) to produce the one-time code; any failure from the randomness source is wrapped as 'keystore: generate otp'. This is rare and indicates the CSPRNG failed, not a user problem.
Solutions
- Check the wrapped cause; on Linux verify /dev/urandom is readable in the container.
- Review seccomp/apparmor profiles to allow getrandom(2).
- Add a retry — transient entropy starvation is rare but a single retry usually clears it.
- Escalate if persistent: the host's RNG setup is broken and OTP issuance must halt safely.
Defensive patterns
Strategy: retry
Try / catch
code, err := store.GenerateOTP(p)
if err != nil && strings.Contains(err.Error(), "generate otp") {
// transient RNG failure: retry once, then fail closed
code, err = store.GenerateOTP(p)
if err != nil { return err }
} Prevention
- Verify /dev/urandom or getrandom(2) availability in containers
- Review seccomp profiles for crypto syscalls
- Fail closed: never issue OTPs with a weak fallback RNG
When it happens
Trigger: crypto/rand read failure — e.g. exhausted entropy, restricted /dev/urandom in a hardened container, or the randomDigits implementation failing to assemble digits.
Common situations: Containers with blocked device nodes, exotic sandboxes/seccomp profiles denying getrandom(2), or modified crypto sources in FIPS-restricted environments.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- failed to generate UUID:
- failed to create device
- failed to create device from new key
- keystore: insert otp
- keystore: query otp
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/9b10dabd80a9d6f3.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/server/keystore.go:226
// Cap distinct deviceIds per user per cooldown window. This
// bounds the disk-growth DoS vector: even if the attacker
// distributes requests across deviceIds to stay under the
// per-user OTP cap, they can only create so many distinct
// rows before the sweep cleans them up.
var distinctDevices int
if err := s.db.QueryRow(
`SELECT COUNT(DISTINCT dev_id) FROM otp_records
WHERE usr_id = ? AND created_at > ?`,
p.UserId, cutoff,
).Scan(&distinctDevices); err == nil && distinctDevices >= MaxDistinctDevicesPerUserPerWindow {
return "", common.ErrOTPCooldown
}
}
code, err := randomDigits(6)
if err != nil {
return "", fmt.Errorf("keystore: generate otp: %w", err)
}
now := time.Now().Unix()
expires := time.Now().Add(p.TTL).Unix()
// Invalidate previous unused OTPs for this user+device.
_, _ = s.db.Exec(
`UPDATE otp_records SET used = 1 WHERE usr_id = ? AND dev_id = ? AND used = 0`,
p.UserId, p.DeviceId,
)
_, err = s.db.Exec(
`INSERT INTO otp_records (usr_id, dev_id, otp_code, pub_key, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?)`,
p.UserId, p.DeviceId, code, p.PublicKey, now, expires,
)
if err != nil {
return "", fmt.Errorf("keystore: insert otp: %w", err)
}View on GitHub (pinned to 6e04ca5ff0)