Skip to content

Commit

Permalink
Patch docs (#2)
Browse files Browse the repository at this point in the history
* fix test

* make iotb private

* improve docs
  • Loading branch information
sebastian-mora committed Feb 7, 2024
1 parent 3950368 commit e28d7b3
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 40 deletions.
28 changes: 17 additions & 11 deletions hotp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)

Expand Down
9 changes: 5 additions & 4 deletions hotp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down Expand Up @@ -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")
}
})
}
}
Expand Down
33 changes: 19 additions & 14 deletions otp.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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 (
Expand All @@ -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")
}
Expand Down Expand Up @@ -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)
Expand All @@ -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) |
Expand All @@ -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
Expand Down
21 changes: 10 additions & 11 deletions totp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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)
Expand All @@ -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
}
Expand All @@ -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
}

0 comments on commit e28d7b3

Please sign in to comment.