// Package auth provides TOTP and token authentication for INP2P. package auth import ( "crypto/hmac" "crypto/sha256" "encoding/binary" "fmt" "hash/crc64" "time" ) const ( // TOTPStep is the time window in seconds for TOTP validity. // A code is valid for ±1 step to allow for clock drift. TOTPStep int64 = 60 ) var crcTable = crc64.MakeTable(crc64.ECMA) // MakeToken generates a token from user+password using CRC64. func MakeToken(user, password string) uint64 { return crc64.Checksum([]byte(user+password), crcTable) } // GenTOTP generates a TOTP code for relay authentication. func GenTOTP(token uint64, ts int64) uint64 { step := ts / TOTPStep buf := make([]byte, 16) binary.BigEndian.PutUint64(buf[:8], token) binary.BigEndian.PutUint64(buf[8:], uint64(step)) mac := hmac.New(sha256.New, buf[:8]) mac.Write(buf[8:]) sum := mac.Sum(nil) return binary.BigEndian.Uint64(sum[:8]) } // VerifyTOTP verifies a TOTP code with ±1 step tolerance. func VerifyTOTP(code uint64, token uint64, ts int64) bool { for delta := int64(-1); delta <= 1; delta++ { expected := GenTOTP(token, ts+delta*TOTPStep) if code == expected { return true } } return false } // RelayToken generates a one-time relay token signed by the server. // Used for cross-user super relay authentication. type RelayToken struct { SessionID string `json:"sessionID"` From string `json:"from"` To string `json:"to"` Relay string `json:"relay"` Expires int64 `json:"expires"` Signature []byte `json:"signature"` } // SignRelayToken creates a signed one-time relay token. func SignRelayToken(secret []byte, sessionID, from, to, relay string, ttl time.Duration) RelayToken { rt := RelayToken{ SessionID: sessionID, From: from, To: to, Relay: relay, Expires: time.Now().Add(ttl).Unix(), } msg := fmt.Sprintf("%s:%s:%s:%s:%d", rt.SessionID, rt.From, rt.To, rt.Relay, rt.Expires) mac := hmac.New(sha256.New, secret) mac.Write([]byte(msg)) rt.Signature = mac.Sum(nil) return rt } // VerifyRelayToken validates a signed relay token. func VerifyRelayToken(secret []byte, rt RelayToken) bool { if time.Now().Unix() > rt.Expires { return false } msg := fmt.Sprintf("%s:%s:%s:%s:%d", rt.SessionID, rt.From, rt.To, rt.Relay, rt.Expires) mac := hmac.New(sha256.New, secret) mac.Write([]byte(msg)) expected := mac.Sum(nil) return hmac.Equal(rt.Signature, expected) }