Documentation
¶
Overview ¶
Package apppass is the app-password mechanism the stateful personalities share: argon2id hashing in PHC form, constant-time verification, a dummy verify that hides account existence, two generators (a grouped token and a BIP-39 word phrase), and a verified-login cache. The IMAP head (chassis/server/personality/imap) and the calendar head (chassis/server/personality/calendar) authenticate the same way, so one password minted for a persona can open both — the product decides whether the credential is shared by passing the same value to both account ops.
Index ¶
- Constants
- Variables
- func GeneratePassword() string
- func GenerateWordPassword(n int) string
- func HashPassword(password string) (string, error)
- func LoginKey(username, pwHash, password string) string
- func VerifyDummy(password string) bool
- func VerifyPassword(phc, password string) (bool, error)
- type LoginCache
Constants ¶
const WordPasswordDefaultWords = 5
WordPasswordDefaultWords is the default word count for GenerateWordPassword: 5 × 11 bits = 55 bits, enough that an offline attack on the argon2id hash is measured in millennia while the phrase stays typable on a phone. Four words (44 bits) is defensible only because the head throttles LOGIN; six (66 bits) costs one more word.
Variables ¶
var ( // ErrBadHash is returned when a stored hash is not a PHC argon2id string // this package produced. ErrBadHash = errors.New("apppass: malformed password hash") )
Functions ¶
func GeneratePassword ¶
func GeneratePassword() string
GeneratePassword returns a fresh random password in four-character groups ("xxxx-xxxx-xxxx-xxxx-xxxx-xxxx", ~116 bits) for the `password=""` path of the account ops. It is returned to the caller exactly once and only its hash is stored.
func GenerateWordPassword ¶
GenerateWordPassword returns n words from the BIP-39 list joined by hyphens ("river-galaxy-bamboo-orbit-velvet"). n < 1 uses the default. Each word is one unbiased 11-bit draw from crypto/rand: 65536 is a multiple of 2048, so the modulo introduces no bias. Returned to the caller exactly once by the account ops; only its hash is stored.
func HashPassword ¶
HashPassword returns a PHC-formatted argon2id string ($argon2id$v=19$m=…,t=…,p=…$<salt>$<hash>, base64 without padding) for storage in an accounts table (imap_accounts.pw_hash, calendar_accounts.pw_hash).
func VerifyDummy ¶
VerifyDummy burns one argon2id verify against a fixed hash so a LOGIN for an unknown username costs the same as one for a known username (no account-existence timing oracle). The result is always false.
func VerifyPassword ¶
VerifyPassword reports whether password matches the PHC hash. The comparison is constant-time; parameters are read from the hash so a later cost bump verifies old rows unchanged. A malformed hash is an error (never a silent false-negative that hides a corrupt row).
Types ¶
type LoginCache ¶
type LoginCache struct {
// contains filtered or unexported fields
}
LoginCache remembers recently verified (username, hash, password) triples so a desktop client's 5–10 parallel LOGINs per wake — or a CalDAV client's dozens of Basic-authenticated requests per refresh — cost one argon2id verify, not one each. The key is a hash of all three, so a rotated password (new pw_hash) misses by construction, and a stale entry can only ever admit a password that was correct for that hash.
func NewLoginCache ¶
func NewLoginCache(ttl time.Duration, max int) *LoginCache
NewLoginCache builds a cache holding at most max entries for ttl each.
func (*LoginCache) Hit ¶
func (c *LoginCache) Hit(key string) bool
Hit reports whether key was verified within the TTL.
func (*LoginCache) SetClock ¶
func (c *LoginCache) SetClock(now func() time.Time)
SetClock pins the cache's clock (tests).