diff --git a/hotp.go b/hotp.go index 10b0601..f5516aa 100644 --- a/hotp.go +++ b/hotp.go @@ -6,8 +6,8 @@ import ( "net/url" ) -// hotp represents a Sequence-based One-Time Password generator. -type hotp struct { +// HTOP represents a Sequence-based One-Time Password generator. +type HTOP struct { otp OTP Counter int synchronizationLimit int @@ -28,31 +28,37 @@ type HOTPConfig struct { } // NewHTOP creates a new instance of hopt based on the provided HOTPConfig. -func NewHTOP(config HOTPConfig) *hotp { +func NewHTOP(config HOTPConfig) *HTOP { - return &hotp{ + return &HTOP{ otp: NewOTP(config.Secret, config.HashType, config.CodeLength), Counter: config.Counter, synchronizationLimit: config.SynchronizationLimit, } } -// Generate generates a HOTP code. -func (h *hotp) Generate() string { +// Generate returns a string representing a HOTP code. +// generating a code increments the HOTP counter +func (h *HTOP) Generate() string { code := h.otp.Generate(h.Counter) h.Counter = h.Counter + 1 return code } // Validate validates an input OTP code against the current counter value. -func (h *hotp) Validate(input string) bool { - // If synchronization limit is 0 or negative, perform a single validation - if h.synchronizationLimit <= 0 && h.otp.Generate(h.Counter) == input { +// the function will attempt to look ahead for codes using +// synchronizationLimit as the upper bound. +func (h *HTOP) Validate(input string) bool { + + // first check if input matches the current counter + if h.otp.Generate(h.Counter) == input { h.Counter++ return true } - for i := 0; i < h.synchronizationLimit; i++ { + // If we did not match, look ahead and sync if needed. + // i=1 as we have checked the first index already + for i := 1; i < h.synchronizationLimit; i++ { if h.otp.Generate(h.Counter+i) == input { h.Counter += i // Fast-forward counter to sync return true @@ -64,7 +70,7 @@ func (h *hotp) Validate(input string) bool { // URI generates the URI according to the Google Authenticator Key URI Format. // See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format -func (t *hotp) URI(label string, issuer string) string { +func (t *HTOP) URI(label string, issuer string) string { // Encode secret in Base32 without padding encodedSecret := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(t.otp.secret) diff --git a/hotp_test.go b/hotp_test.go index 8a71864..4943cda 100644 --- a/hotp_test.go +++ b/hotp_test.go @@ -80,6 +80,11 @@ func TestHTOPValidate(t *testing.T) { if !hotp.Validate(tc.Expected) { t.Errorf("Failed to validate code: %s, counter: %d", tc.Expected, hotp.Counter) } + + // ensure the counter incrementing + if hotp.Counter != tc.Counter+1 { + t.Errorf("Counter did not increment Got: %d, Expected: %d", hotp.Counter, tc.Counter) + } } } @@ -141,10 +146,6 @@ func TestHOTPSuccessfulValidationOfOutOfSync(t *testing.T) { t.Errorf("The counter was not synced correctly, Expected %d, Got: %d", 0, hopt.Counter) } - // Pass all other validations after sync - if !hopt.Validate(tc.Expected) { - t.Error("Validation failed after sync") - } }) } } diff --git a/otp.go b/otp.go index ffb3588..9b63169 100644 --- a/otp.go +++ b/otp.go @@ -1,7 +1,8 @@ +// Package basicOTP implements One-Time Password (OTP) generation according to RFC 4226 and RFC 6238. +// It provides functionality to generate OTP codes using HMAC-based algorithms like SHA-1, SHA-256, and SHA-512. +// This package is useful for implementing two-factor authentication (2FA) systems. package basicOTP -// implements https://datatracker.ietf.org/doc/html/rfc6238 - import ( "crypto/hmac" "crypto/sha1" @@ -13,7 +14,7 @@ import ( "math" ) -// HashType represents the type of hash algorithm. +// HashType represents the type of hash algorithm supported. type HashType string const ( @@ -22,16 +23,20 @@ const ( SHA512 HashType = "SHA512" ) +// OTP represents a One-Time Password generator. type OTP struct { - hashFunc func() hash.Hash - HashType HashType - secret []byte - CodeLength int + hashFunc func() hash.Hash // hashFunc is the hash function used for OTP generation. + HashType HashType // HashType is the type of hash algorithm used. + secret []byte // secret is the shared secret key used for OTP generation. + CodeLength int // CodeLength is the length of the generated OTP code. } // NewOTP creates a new instance of OTP based on the provided configuration. +// The function will substitute default values for parameters as per the specification: +// - codeLength defaults to 6. +// - hashFunc will default to SHA1. +// - A secret is required but length is not enforced. RFC recommends a shared secret of at least 128 bits. func NewOTP(secret []byte, hashType HashType, codeLength int) OTP { - if len(secret) <= 0 { panic("OTP requires a secret to be set") } @@ -61,13 +66,10 @@ func NewOTP(secret []byte, hashType HashType, codeLength int) OTP { } } -/* -This is the base implenation, the input here can be used for TOP (Time based) or HOPT (incremental) -*/ +// Generate generates an OTP code based on the provided input. func (o OTP) Generate(input int) string { - hmac := hmac.New(o.hashFunc, []byte(o.secret)) - buf := Itob(input) + buf := itob(input) hmac.Write(buf) hmacData := hmac.Sum(nil) @@ -77,6 +79,8 @@ func (o OTP) Generate(input int) string { return fmt.Sprintf(formatString, code) } +// truncate truncates the HMAC result to the desired length. +// The dynamic truncation (DT) algorithm is found in RFC 4226. func truncate(input []byte, codeLength int) int { offset := int(input[len(input)-1] & 0xf) code := ((int(input[offset]) & 0x7f) << 24) | @@ -89,7 +93,8 @@ func truncate(input []byte, codeLength int) int { return code } -func Itob(integer int) []byte { +// itob converts an integer to a big-endian byte array. +func itob(integer int) []byte { byteArr := make([]byte, 8) binary.BigEndian.PutUint64(byteArr, uint64(integer)) return byteArr diff --git a/totp.go b/totp.go index 63a175d..c10ea97 100644 --- a/totp.go +++ b/totp.go @@ -9,23 +9,22 @@ import ( // TOTP represents a Time-based One-Time Password generator. type TOTP struct { - otp OTP - TimePeriod int + otp OTP // otp is the underlying OTP generator. + TimePeriod int // TimePeriod is the time period in seconds used for TOTP generation. } // TOTPConfig holds configuration parameters for TOTP generation. type TOTPConfig struct { - TimeInterval int - CodeLength int - HashType HashType - Secret []byte + TimeInterval int // TimeInterval is the time interval in seconds for TOTP generation. + CodeLength int // CodeLength is the length of the generated TOTP code. + HashType HashType // HashType is the hash algorithm used for TOTP generation. + Secret []byte // Secret is the shared secret key used for TOTP generation. } // NewTOTP creates a new instance of TOTP based on the provided configuration. func NewTOTP(config TOTPConfig) *TOTP { - if config.TimeInterval == 0 { - // Set default time to 30 seconds, recommended in rfc6238 + // Set the default time interval to 30 seconds, recommended in RFC 6238. config.TimeInterval = 30 } @@ -42,7 +41,7 @@ func (t *TOTP) Generate() string { return t.otp.Generate(int(timecode)) } -// Generate generates a TOTP for the given Unix timestamp. +// GenerateAt generates a TOTP for the given Unix timestamp. func (t *TOTP) GenerateAt(unixTimeStamp int64) string { timeCode := int(t.timecode(unixTimeStamp)) return t.otp.Generate(timeCode) @@ -53,7 +52,7 @@ func (t *TOTP) Validate(code string) bool { return t.Generate() == code } -// ValidateAt validates a TOTP against a given unix timestamp. +// ValidateAt validates a TOTP against a given Unix timestamp. func (t *TOTP) ValidateAt(unixTimestamp int64, code string) bool { return t.GenerateAt(unixTimestamp) == code } @@ -77,7 +76,7 @@ func (t *TOTP) URI(label string, issuer string) string { t.otp.CodeLength) } -// timecode calculates the timecode based on the provided Unix timestamp. +// timecode calculates the timecode based on the provided Unix timestamp and the TimePeriod. func (t *TOTP) timecode(unixTimeStamp int64) int { return int(unixTimeStamp) / t.TimePeriod }