Documentation
¶
Index ¶
- Constants
- Variables
- func CreateSecret() ([]byte, error)
- func DecodeBase32(dst []byte, src string) (int, error)
- func EncodeBase32(data []byte) string
- func GenHotpDefault(secret []byte, counter uint64) (string, error)
- func GenTotpDefault(secret []byte) (string, error)
- func VerifyHotpDefault(secret []byte, code string, counter uint64) (bool, error)
- func VerifyTotpDefault(secret []byte, code string) (bool, error)
- type Algorithm
- type ClockSkewDetector
- func (d *ClockSkewDetector) CurrentOffset() int64
- func (d *ClockSkewDetector) DisableAutoAdjust()
- func (d *ClockSkewDetector) EnableAutoAdjust()
- func (d *ClockSkewDetector) IsAutoAdjust() bool
- func (d *ClockSkewDetector) Record(matchedOffset int64, windowUsed uint64)
- func (d *ClockSkewDetector) Report() SkewReport
- func (d *ClockSkewDetector) Reset()
- type GenOtpError
- type HOTP
- func (h *HOTP) ClearSecret()
- func (h *HOTP) GenBound(counter uint64, context *OtpContext) (string, error)
- func (h *HOTP) Generate(counter uint64) (string, error)
- func (h *HOTP) Verify(code string, counter uint64) (bool, error)
- func (h *HOTP) VerifyBound(code string, counter uint64, context *OtpContext) (bool, error)
- func (h *HOTP) VerifyWithResync(code string, counter uint64, lookAhead uint64) (uint64, bool, error)
- type HotpBuilder
- type HotpConfig
- type InMemoryReplayStore
- type KeyGen
- type Metrics
- func (m *Metrics) GetErrors() uint64
- func (m *Metrics) GetHotpGenerations() uint64
- func (m *Metrics) GetHotpVerifications() uint64
- func (m *Metrics) GetTotpGenerations() uint64
- func (m *Metrics) GetTotpVerifications() uint64
- func (m *Metrics) IncrementError()
- func (m *Metrics) IncrementHotpGeneration()
- func (m *Metrics) IncrementHotpVerification()
- func (m *Metrics) IncrementTotpGeneration()
- func (m *Metrics) IncrementTotpVerification()
- func (m *Metrics) Reset()
- type OtpAuthUri
- func (u *OtpAuthUri) Algorithm(algorithm Algorithm) *OtpAuthUri
- func (u *OtpAuthUri) Build() string
- func (u *OtpAuthUri) Counter(counter uint64) *OtpAuthUri
- func (u *OtpAuthUri) Digits(digits uint32) *OtpAuthUri
- func (u *OtpAuthUri) Issuer(issuer string) *OtpAuthUri
- func (u *OtpAuthUri) Period(period uint64) *OtpAuthUri
- func (u *OtpAuthUri) String() string
- type OtpContext
- type OtpContextBuilder
- func (b *OtpContextBuilder) Build() *OtpContext
- func (b *OtpContextBuilder) Custom(key, value string) *OtpContextBuilder
- func (b *OtpContextBuilder) Device(deviceID string) *OtpContextBuilder
- func (b *OtpContextBuilder) IP(ip string) *OtpContextBuilder
- func (b *OtpContextBuilder) Origin(origin string) *OtpContextBuilder
- func (b *OtpContextBuilder) Session(session string) *OtpContextBuilder
- type OtpType
- type ReplayStore
- type SkewRecommend
- type SkewReport
- type TOTP
- func (t *TOTP) ClearSecret()
- func (t *TOTP) GenBound(context *OtpContext, timeVal *uint64) (string, error)
- func (t *TOTP) Generate(timeVal *uint64) (string, error)
- func (t *TOTP) Verify(code string, timeVal *uint64, window uint64) (bool, error)
- func (t *TOTP) VerifyBound(code string, context *OtpContext, timeVal *uint64, window uint64) (bool, error)
- func (t *TOTP) VerifyTracking(code string, timeVal *uint64, window uint64, detector *ClockSkewDetector) (bool, error)
- type TotpBuilder
- type TotpConfig
- type Verifier
Constants ¶
const ( MinSecretBytes = 16 DefaultSecretBytes = 20 )
Variables ¶
var ( ErrInvalidSecret = errors.New("invalid secret key") ErrInvalidCode = errors.New("invalid OTP code") ErrInvalidDigits = errors.New("invalid number of digits") ErrInvalidAlgorithm = errors.New("invalid algorithm") ErrInvalidCounter = errors.New("invalid counter value") ErrInvalidTime = errors.New("invalid time value") ErrVerificationFailed = errors.New("OTP verification failed") ErrRateLimited = errors.New("rate limited") ErrReplayAttack = errors.New("replay attack detected") ErrDstTooSmall = errors.New("destination buffer too small") )
Functions ¶
func CreateSecret ¶
func EncodeBase32 ¶
func GenTotpDefault ¶
func VerifyHotpDefault ¶
Types ¶
type ClockSkewDetector ¶
type ClockSkewDetector struct {
// contains filtered or unexported fields
}
func NewClockSkewDetector ¶
func NewClockSkewDetector(capacity int) *ClockSkewDetector
func (*ClockSkewDetector) CurrentOffset ¶
func (d *ClockSkewDetector) CurrentOffset() int64
func (*ClockSkewDetector) DisableAutoAdjust ¶
func (d *ClockSkewDetector) DisableAutoAdjust()
func (*ClockSkewDetector) EnableAutoAdjust ¶
func (d *ClockSkewDetector) EnableAutoAdjust()
func (*ClockSkewDetector) IsAutoAdjust ¶
func (d *ClockSkewDetector) IsAutoAdjust() bool
func (*ClockSkewDetector) Record ¶
func (d *ClockSkewDetector) Record(matchedOffset int64, windowUsed uint64)
func (*ClockSkewDetector) Report ¶
func (d *ClockSkewDetector) Report() SkewReport
func (*ClockSkewDetector) Reset ¶
func (d *ClockSkewDetector) Reset()
type GenOtpError ¶
type GenOtpError struct {
Message string
}
func NewGenOtpError ¶
func NewGenOtpError(msg string) *GenOtpError
func (*GenOtpError) Error ¶
func (e *GenOtpError) Error() string
type HOTP ¶
type HOTP struct {
// contains filtered or unexported fields
}
func NewHotpFromConfig ¶
func NewHotpFromConfig(secret []byte, c HotpConfig) (*HOTP, error)
func (*HOTP) ClearSecret ¶
func (h *HOTP) ClearSecret()
func (*HOTP) GenBound ¶
func (h *HOTP) GenBound(counter uint64, context *OtpContext) (string, error)
func (*HOTP) VerifyBound ¶
type HotpBuilder ¶
type HotpBuilder struct {
// contains filtered or unexported fields
}
func NewHotpBuilder ¶
func NewHotpBuilder() *HotpBuilder
func (*HotpBuilder) Algorithm ¶
func (b *HotpBuilder) Algorithm(algorithm Algorithm) *HotpBuilder
func (*HotpBuilder) Build ¶
func (b *HotpBuilder) Build() (*HOTP, error)
func (*HotpBuilder) Digits ¶
func (b *HotpBuilder) Digits(digits uint32) *HotpBuilder
func (*HotpBuilder) Secret ¶
func (b *HotpBuilder) Secret(secret []byte) *HotpBuilder
type HotpConfig ¶
func NewHotpConfig ¶
func NewHotpConfig() HotpConfig
func (HotpConfig) WithAlgorithm ¶
func (c HotpConfig) WithAlgorithm(algorithm Algorithm) HotpConfig
func (HotpConfig) WithDigits ¶
func (c HotpConfig) WithDigits(digits uint32) HotpConfig
type InMemoryReplayStore ¶
type InMemoryReplayStore struct {
// contains filtered or unexported fields
}
InMemoryReplayStore adalah default impl — bounded map dengan TTL.
**Anti-amnesia:** versi lama Verifier melakukan `map = make(...)` saat penuh, yang menyebabkan SEMUA kode lama tiba-tiba kembali valid. Itu kelemahan yang bisa dieksploitasi attacker yang tahu cap-nya: flood dengan kode acak supaya cap terlampaui -> semua kode bekas yang dia pernah intercept jadi bisa di-replay lagi.
Implementasi ini ganti pola itu dengan:
- Sweep periodik (~tiap 30s) untuk reclaim entries yang sudah expired.
- Saat cap-hit, evict 10% entries acak (bukan clear-all). Worst case attacker bisa men-displace 10% entries terlama, bukan 100%.
Pilihan batch 10% acak (vs O(n) eviction per entry tertua):
- Map iteration di Go acak per range -> setara random eviction tanpa overhead LRU list.
- Amortized O(1) per CheckAndRecord call: setelah evict 10%, ada ruang 10% sebelum trigger eviction lagi -> cost terbagi merata.
- 90% entries bertahan dari setiap eviction -> defense-in-depth terhadap amnesia tetap kuat.
Kompleksitas:
- CheckAndRecord: O(1) amortized, O(0.1n) worst case saat cap-hit.
- Memory: bounded di maxSize entries x ~100 byte/key worst case.
func NewInMemoryReplayStore ¶
func NewInMemoryReplayStore(maxSize int) *InMemoryReplayStore
NewInMemoryReplayStore membuat store dengan kapasitas maxSize entries. Sweep periodik dilakukan ~tiap 30 detik. Untuk OTP workload normal, maxSize 10.000 menampung beberapa menit traffic, cukup untuk TTL OTP (~90 detik).
func (*InMemoryReplayStore) CheckAndRecord ¶
func (*InMemoryReplayStore) Reset ¶
func (s *InMemoryReplayStore) Reset() error
func (*InMemoryReplayStore) Size ¶
func (s *InMemoryReplayStore) Size() int
Size mengembalikan jumlah entries aktif (termasuk yang sudah expired tapi belum disweep). Buat introspeksi / metrics.
type KeyGen ¶
type KeyGen struct{}
func (*KeyGen) FillSecret ¶
func (*KeyGen) GenerateDefaultSecret ¶
type Metrics ¶
type Metrics struct {
HotpGenerations atomic.Uint64
HotpVerifications atomic.Uint64
TotpGenerations atomic.Uint64
TotpVerifications atomic.Uint64
Errors atomic.Uint64
}
func NewMetrics ¶
func NewMetrics() *Metrics
func (*Metrics) GetHotpGenerations ¶
func (*Metrics) GetHotpVerifications ¶
func (*Metrics) GetTotpGenerations ¶
func (*Metrics) GetTotpVerifications ¶
func (*Metrics) IncrementError ¶
func (m *Metrics) IncrementError()
func (*Metrics) IncrementHotpGeneration ¶
func (m *Metrics) IncrementHotpGeneration()
func (*Metrics) IncrementHotpVerification ¶
func (m *Metrics) IncrementHotpVerification()
func (*Metrics) IncrementTotpGeneration ¶
func (m *Metrics) IncrementTotpGeneration()
func (*Metrics) IncrementTotpVerification ¶
func (m *Metrics) IncrementTotpVerification()
type OtpAuthUri ¶
type OtpAuthUri struct {
// contains filtered or unexported fields
}
func NewOtpAuthUri ¶
func NewOtpAuthUri(typ OtpType, label, secret string) *OtpAuthUri
func (*OtpAuthUri) Algorithm ¶
func (u *OtpAuthUri) Algorithm(algorithm Algorithm) *OtpAuthUri
func (*OtpAuthUri) Build ¶
func (u *OtpAuthUri) Build() string
func (*OtpAuthUri) Counter ¶
func (u *OtpAuthUri) Counter(counter uint64) *OtpAuthUri
func (*OtpAuthUri) Digits ¶
func (u *OtpAuthUri) Digits(digits uint32) *OtpAuthUri
func (*OtpAuthUri) Issuer ¶
func (u *OtpAuthUri) Issuer(issuer string) *OtpAuthUri
func (*OtpAuthUri) Period ¶
func (u *OtpAuthUri) Period(period uint64) *OtpAuthUri
func (*OtpAuthUri) String ¶
func (u *OtpAuthUri) String() string
type OtpContext ¶
type OtpContext struct {
// contains filtered or unexported fields
}
func NewOtpContext ¶
func NewOtpContext() *OtpContext
func OtpContextFromBytes ¶
func OtpContextFromBytes(b []byte) *OtpContext
func (*OtpContext) Bytes ¶
func (c *OtpContext) Bytes() []byte
func (*OtpContext) IsEmpty ¶
func (c *OtpContext) IsEmpty() bool
type OtpContextBuilder ¶
type OtpContextBuilder struct {
// contains filtered or unexported fields
}
func NewOtpContextBuilder ¶
func NewOtpContextBuilder() *OtpContextBuilder
func (*OtpContextBuilder) Build ¶
func (b *OtpContextBuilder) Build() *OtpContext
func (*OtpContextBuilder) Custom ¶
func (b *OtpContextBuilder) Custom(key, value string) *OtpContextBuilder
func (*OtpContextBuilder) Device ¶
func (b *OtpContextBuilder) Device(deviceID string) *OtpContextBuilder
func (*OtpContextBuilder) IP ¶
func (b *OtpContextBuilder) IP(ip string) *OtpContextBuilder
func (*OtpContextBuilder) Origin ¶
func (b *OtpContextBuilder) Origin(origin string) *OtpContextBuilder
func (*OtpContextBuilder) Session ¶
func (b *OtpContextBuilder) Session(session string) *OtpContextBuilder
type ReplayStore ¶
type ReplayStore interface {
// CheckAndRecord mengembalikan true KALAU `key` belum pernah dicatat
// dalam window TTL-nya (firstSeen). Atomic: kalau dua caller paralel
// (atau dua replica) memanggil dengan key yang sama bersamaan, tepat
// satu yang mendapat firstSeen=true.
//
// `ttl` = berapa lama key tetap menolak resubmission. Untuk TOTP
// pilih period x (1 + 2xwindow). Mis. period=30, window=1 -> ttl=90s
// supaya kode yang valid di counter T-1 ditolak sampai jendela
// validitas alaminya lewat.
//
// Error backend (Redis down, network timeout) di-propagate via err;
// caller (Verifier) memperlakukan error sebagai "fail closed" — kode
// ditolak. Lebih baik false negative daripada bypass.
CheckAndRecord(key []byte, ttl time.Duration) (firstSeen bool, err error)
// Reset menghapus semua entries. Dipakai oleh testing dan admin
// reset. Backend distributed bisa FLUSHDB / namespaced delete.
Reset() error
}
ReplayStore abstrak penyimpanan kode OTP yang sudah pernah diterima.
**Kenapa interface dan bukan map biasa di Verifier:** library ini dirancang bisa dipakai di mode single-process (cukup InMemoryReplayStore) dan distributed (multi-replica di belakang load balancer). Tanpa abstraksi backend, replay protection pecah secara halus di lingkungan distributed:
Server A | Server B | Server C <- 3 replika di belakang LB RAM | RAM | RAM <- state TERPISAH per proses
Skenario serangan nyata:
- Attacker dapat 1 OTP valid, kirim ke LB -> di-route ke Server A.
- Server A.usedCodes catat -> accept.
- Attacker kirim ULANG kode yang sama -> kali ini di-route ke Server B.
- Server B.usedCodes kosong (state tidak ter-replikasi) -> accept lagi.
- Effective replay bypass = N kali, dengan N = jumlah replica.
Solusi: implementasikan ReplayStore dengan storage shared (Redis SET NX EX, etcd lease, sql + unique constraint, dll) supaya semua replica melihat state yang sama. Library kasih default in-memory yang aman untuk single-process; production deploy multi-replica WAJIB pakai distributed backend. Lihat docs/redis_example untuk contoh.
type SkewRecommend ¶
type SkewRecommend int
const ( InsufficientData SkewRecommend = iota NoActionNeeded ConsistentDrift WidenWindowOrCheckNtp )
func (SkewRecommend) String ¶
func (r SkewRecommend) String() string
type SkewReport ¶
type SkewReport struct {
SampleCount int
MeanOffset float64
NonZeroCount int
EdgeHitRatio float64
Recommend SkewRecommend
}
type TOTP ¶
type TOTP struct {
// contains filtered or unexported fields
}
func NewTotpFromConfig ¶
func NewTotpFromConfig(secret []byte, c TotpConfig) (*TOTP, error)
func (*TOTP) ClearSecret ¶
func (t *TOTP) ClearSecret()
func (*TOTP) GenBound ¶
func (t *TOTP) GenBound(context *OtpContext, timeVal *uint64) (string, error)
func (*TOTP) VerifyBound ¶
func (*TOTP) VerifyTracking ¶
type TotpBuilder ¶
type TotpBuilder struct {
// contains filtered or unexported fields
}
func NewTotpBuilder ¶
func NewTotpBuilder() *TotpBuilder
func (*TotpBuilder) Algorithm ¶
func (b *TotpBuilder) Algorithm(algorithm Algorithm) *TotpBuilder
func (*TotpBuilder) Build ¶
func (b *TotpBuilder) Build() (*TOTP, error)
func (*TotpBuilder) Digits ¶
func (b *TotpBuilder) Digits(digits uint32) *TotpBuilder
func (*TotpBuilder) Period ¶
func (b *TotpBuilder) Period(period uint64) *TotpBuilder
func (*TotpBuilder) Secret ¶
func (b *TotpBuilder) Secret(secret []byte) *TotpBuilder
type TotpConfig ¶
func NewTotpConfig ¶
func NewTotpConfig() TotpConfig
func (TotpConfig) WithAlgorithm ¶
func (c TotpConfig) WithAlgorithm(algorithm Algorithm) TotpConfig
func (TotpConfig) WithDigits ¶
func (c TotpConfig) WithDigits(digits uint32) TotpConfig
func (TotpConfig) WithPeriod ¶
func (c TotpConfig) WithPeriod(period uint64) TotpConfig
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
func NewVerifier ¶
NewVerifier membuat Verifier dengan InMemoryReplayStore default (10.000 entries, TTL 90 detik). Cocok untuk single-process / single-replica.
**Untuk deployment multi-replica (Kubernetes, dll):** in-memory store TIDAK memberikan replay protection lintas replica — state pisah per-process. Pakai NewVerifierWithStore dengan implementasi ReplayStore yang shared (Redis SET NX EX, dll). Lihat docs/redis_replay_store.go.example.
func NewVerifierWithCapacity ¶
NewVerifierWithCapacity sama dengan NewVerifier tapi memungkinkan override kapasitas in-memory store.
func NewVerifierWithStore ¶
func NewVerifierWithStore(maxAttempts uint32, store ReplayStore, ttl time.Duration) *Verifier
NewVerifierWithStore membuat Verifier dengan ReplayStore custom dan TTL eksplisit. Dipakai untuk inject Redis / etcd / sql backend untuk distributed replay protection.
**Catatan rate-limit (attempts counter):** masih per-instance, BUKAN distributed. Untuk distributed rate-limit, caller bertanggung jawab implement di layer di atas (mis. Redis INCR + EXPIRE di middleware gateway, atau pakai package rate-limit khusus). Library tidak mengabstraksi ini supaya scope tetap sempit.
func (*Verifier) ClearUsedCodes ¶
func (v *Verifier) ClearUsedCodes()
ClearUsedCodes membersihkan replay-set. Untuk InMemoryReplayStore, drop semua entries. Untuk Redis backend, panggil pattern delete / FLUSHDB di implementor.
func (*Verifier) IsRateLimited ¶
func (*Verifier) ResetAttempts ¶
func (v *Verifier) ResetAttempts()
func (*Verifier) VerifyWithContext ¶
func (v *Verifier) VerifyWithContext(code, expected string, issuedContext, requestContext *OtpContext) bool