mirror of
https://github.com/H0llyW00dzZ/fiber2fa.git
synced 2025-02-06 10:24:03 +00:00
Improve [HOTP & TOTP] Separate signature verification logic (#94)
- [+] refactor(otpverifier): separate signature verification logic and add GenerateToken method - [+] feat(otpverifier): add support for verifying token without signature - [+] test(otpverifier): add tests for missing signature and signature mismatch scenarios
This commit is contained in:
parent
82159c8725
commit
0d47e07cc0
@ -269,8 +269,8 @@ func TestMiddleware_Handle(t *testing.T) {
|
||||
Secret: info.Secret,
|
||||
})
|
||||
|
||||
validToken, _ := totp.GenerateToken()
|
||||
totp.Verify(validToken, "")
|
||||
validToken := totp.GenerateToken()
|
||||
totp.Verify(validToken)
|
||||
|
||||
// Create a separate instance of the Middleware struct for testing
|
||||
testMiddleware := &twofa.Middleware{
|
||||
@ -1279,8 +1279,8 @@ func TestMiddlewareUUIDContextKey_Handle(t *testing.T) {
|
||||
Secret: info.Secret,
|
||||
})
|
||||
|
||||
validToken, _ := totp.GenerateToken()
|
||||
totp.Verify(validToken, "")
|
||||
validToken := totp.GenerateToken()
|
||||
totp.Verify(validToken)
|
||||
|
||||
// Create a separate instance of the Middleware struct for testing
|
||||
testMiddleware := &twofa.Middleware{
|
||||
|
@ -90,8 +90,8 @@ func BenchmarkJSONSonicWithValid2FA(b *testing.B) {
|
||||
Secret: twoFAConfig.Secret,
|
||||
})
|
||||
|
||||
token, _ := totp.GenerateToken()
|
||||
totp.Verify(token, "")
|
||||
token := totp.GenerateToken()
|
||||
totp.Verify(token)
|
||||
|
||||
// Create a valid 2FA cookie
|
||||
cookieValue := twoFAMiddleware.GenerateCookieValue(time.Now().Add(time.Duration(86400) * time.Second))
|
||||
@ -154,8 +154,8 @@ func BenchmarkJSONSonicWithValidCookie(b *testing.B) {
|
||||
Secret: twoFAConfig.Secret,
|
||||
})
|
||||
|
||||
token, _ := totp.GenerateToken()
|
||||
totp.Verify(token, "")
|
||||
token := totp.GenerateToken()
|
||||
totp.Verify(token)
|
||||
|
||||
// Create a valid 2FA cookie
|
||||
cookieValue := twoFAMiddleware.GenerateCookieValue(time.Now().Add(time.Duration(86400) * time.Second))
|
||||
@ -280,8 +280,8 @@ func BenchmarkJSONStdLibraryMiddlewareWithValid2FA(b *testing.B) {
|
||||
Secret: twoFAConfig.Secret,
|
||||
})
|
||||
|
||||
token, _ := totp.GenerateToken()
|
||||
totp.Verify(token, "")
|
||||
token := totp.GenerateToken()
|
||||
totp.Verify(token)
|
||||
|
||||
// Create a valid 2FA cookie
|
||||
cookieValue := twoFAMiddleware.GenerateCookieValue(time.Now().Add(time.Duration(86400) * time.Second))
|
||||
@ -344,8 +344,8 @@ func BenchmarkJSONStdLibraryWithValidCookie(b *testing.B) {
|
||||
Secret: twoFAConfig.Secret,
|
||||
})
|
||||
|
||||
token, _ := totp.GenerateToken()
|
||||
totp.Verify(token, "")
|
||||
token := totp.GenerateToken()
|
||||
totp.Verify(token)
|
||||
|
||||
// Create a valid 2FA cookie
|
||||
cookieValue := twoFAMiddleware.GenerateCookieValue(time.Now().Add(time.Duration(86400) * time.Second))
|
||||
|
@ -41,8 +41,8 @@ func BenchmarkTOTPVerify(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Note: This now each token are different
|
||||
token, _ := verifier.GenerateToken()
|
||||
verifier.Verify(token, "")
|
||||
token := verifier.GenerateToken()
|
||||
verifier.Verify(token)
|
||||
}
|
||||
})
|
||||
|
||||
@ -58,7 +58,7 @@ func BenchmarkTOTPVerify(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Note: This now each token are different
|
||||
token, signature := verifier.GenerateToken()
|
||||
token, signature := verifier.GenerateTokenWithSignature()
|
||||
verifier.Verify(token, signature)
|
||||
}
|
||||
})
|
||||
@ -96,8 +96,8 @@ func BenchmarkHOTPVerify(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Note: This now each token are different
|
||||
token, _ := verifier.GenerateToken()
|
||||
verifier.Verify(token, "")
|
||||
token := verifier.GenerateToken()
|
||||
verifier.Verify(token)
|
||||
}
|
||||
})
|
||||
|
||||
@ -113,7 +113,7 @@ func BenchmarkHOTPVerify(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Note: This now each token are different
|
||||
token, signature := verifier.GenerateToken()
|
||||
token, signature := verifier.GenerateTokenWithSignature()
|
||||
verifier.Verify(token, signature)
|
||||
}
|
||||
})
|
||||
|
@ -63,26 +63,31 @@ func NewHOTPVerifier(config ...Config) *HOTPVerifier {
|
||||
// A successful verification will result in the counter being updated to the next expected value.
|
||||
//
|
||||
// Note: A firm grasp of the sync window concept is essential for understanding its role in the verification process.
|
||||
func (v *HOTPVerifier) Verify(token, signature string) bool {
|
||||
func (v *HOTPVerifier) Verify(token string, signature ...string) bool {
|
||||
if v.config.SyncWindow < 0 {
|
||||
panic("hotp: SyncWindow must be greater than or equal to zero")
|
||||
}
|
||||
|
||||
if v.config.UseSignature {
|
||||
if len(signature) == 0 {
|
||||
panic("hotp: Signature is required but not provided")
|
||||
}
|
||||
|
||||
return v.verifyWithSignature(token, signature[0])
|
||||
}
|
||||
|
||||
return v.verifyWithoutSignature(token)
|
||||
}
|
||||
|
||||
// verifyWithoutSignature checks if the provided token is valid for the specified counter value without signature verification.
|
||||
func (v *HOTPVerifier) verifyWithoutSignature(token string) bool {
|
||||
// Check if sync window is applied
|
||||
// Note: Understanding this sync window requires skilled mathematical reasoning.
|
||||
if v.config.SyncWindow > 0 {
|
||||
for i := 0; i <= v.config.SyncWindow; i++ {
|
||||
expectedCounter := int(v.config.Counter) + i
|
||||
generatedToken := v.Hotp.At(expectedCounter)
|
||||
tokenMatch := subtle.ConstantTimeCompare([]byte(token), []byte(generatedToken)) == 1
|
||||
signatureMatch := true // Assume true if not using signatures.
|
||||
|
||||
if v.config.UseSignature {
|
||||
generatedSignature := v.generateSignature(generatedToken)
|
||||
signatureMatch = subtle.ConstantTimeCompare([]byte(signature), []byte(generatedSignature)) == 1
|
||||
}
|
||||
|
||||
if tokenMatch && signatureMatch {
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(generatedToken)) == 1 {
|
||||
// Update the stored counter to the next expected value after a successful match
|
||||
v.config.Counter = uint64(expectedCounter + 1)
|
||||
return true
|
||||
@ -94,15 +99,7 @@ func (v *HOTPVerifier) Verify(token, signature string) bool {
|
||||
|
||||
// Default case when sync window is not applied
|
||||
generatedToken := v.Hotp.At(int(v.config.Counter))
|
||||
tokenMatch := subtle.ConstantTimeCompare([]byte(token), []byte(generatedToken)) == 1
|
||||
signatureMatch := true // Assume true if not using signatures.
|
||||
|
||||
if v.config.UseSignature {
|
||||
generatedSignature := v.generateSignature(generatedToken)
|
||||
signatureMatch = subtle.ConstantTimeCompare([]byte(signature), []byte(generatedSignature)) == 1
|
||||
}
|
||||
|
||||
if tokenMatch && signatureMatch {
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(generatedToken)) == 1 {
|
||||
// Increment the counter value after successful verification
|
||||
v.config.Counter++
|
||||
return true
|
||||
@ -110,13 +107,47 @@ func (v *HOTPVerifier) Verify(token, signature string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateToken generates a token and signature for the current counter value.
|
||||
func (v *HOTPVerifier) GenerateToken() (string, string) {
|
||||
token := v.Hotp.At(int(v.config.Counter))
|
||||
signature := ""
|
||||
if v.config.UseSignature {
|
||||
signature = v.generateSignature(token)
|
||||
// verifyWithSignature checks if the provided token and signature are valid for the specified counter value.
|
||||
func (v *HOTPVerifier) verifyWithSignature(token, signature string) bool {
|
||||
// Check if sync window is applied
|
||||
// Note: Understanding this sync window requires skilled mathematical reasoning.
|
||||
if v.config.SyncWindow > 0 {
|
||||
for i := 0; i <= v.config.SyncWindow; i++ {
|
||||
expectedCounter := int(v.config.Counter) + i
|
||||
generatedToken := v.Hotp.At(expectedCounter)
|
||||
generatedSignature := v.generateSignature(generatedToken)
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(generatedToken)) == 1 &&
|
||||
subtle.ConstantTimeCompare([]byte(signature), []byte(generatedSignature)) == 1 {
|
||||
// Update the stored counter to the next expected value after a successful match
|
||||
v.config.Counter = uint64(expectedCounter + 1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
// If no match is found within the synchronization window, authentication fails
|
||||
return false
|
||||
}
|
||||
|
||||
// Default case when sync window is not applied
|
||||
generatedToken := v.Hotp.At(int(v.config.Counter))
|
||||
generatedSignature := v.generateSignature(generatedToken)
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(generatedToken)) == 1 &&
|
||||
subtle.ConstantTimeCompare([]byte(signature), []byte(generatedSignature)) == 1 {
|
||||
// Increment the counter value after successful verification
|
||||
v.config.Counter++
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateToken generates a token for the current counter value.
|
||||
func (v *HOTPVerifier) GenerateToken() string {
|
||||
return v.Hotp.At(int(v.config.Counter))
|
||||
}
|
||||
|
||||
// GenerateTokenWithSignature generates a token and signature for the current counter value.
|
||||
func (v *HOTPVerifier) GenerateTokenWithSignature() (string, string) {
|
||||
token := v.Hotp.At(int(v.config.Counter))
|
||||
signature := v.generateSignature(token)
|
||||
return token, signature
|
||||
}
|
||||
|
||||
|
@ -111,8 +111,9 @@ type TimeSource func() time.Time
|
||||
|
||||
// OTPVerifier is an interface that defines the behavior of an OTP verifier.
|
||||
type OTPVerifier interface {
|
||||
Verify(token, signature string) bool
|
||||
GenerateToken() (string, string)
|
||||
Verify(token string, signature ...string) bool
|
||||
GenerateToken() string
|
||||
GenerateTokenWithSignature() (string, string)
|
||||
SetCounter(counter uint64)
|
||||
GetCounter() uint64
|
||||
GenerateOTPURL(issuer, accountName string) string
|
||||
|
@ -63,7 +63,7 @@ func TestTOTPVerifier_Verify(t *testing.T) {
|
||||
verifier := otpverifier.NewTOTPVerifier(config)
|
||||
|
||||
// Generate a token and signature using the verifier
|
||||
token, signature := verifier.GenerateToken()
|
||||
token, signature := verifier.GenerateTokenWithSignature()
|
||||
|
||||
// Verify the token and signature (should succeed)
|
||||
isValid := verifier.Verify(token, signature)
|
||||
@ -86,7 +86,7 @@ func TestTOTPVerifier_Verify(t *testing.T) {
|
||||
// Switch to a non-signature mode and test again
|
||||
config.UseSignature = false
|
||||
verifier = otpverifier.NewTOTPVerifier(config)
|
||||
token, _ = verifier.GenerateToken()
|
||||
token, _ = verifier.GenerateTokenWithSignature()
|
||||
|
||||
// Verify the token without a signature (should succeed)
|
||||
isValid = verifier.Verify(token, "")
|
||||
@ -129,7 +129,7 @@ func TestDefaultConfigTOTPVerifier_Verify(t *testing.T) {
|
||||
verifier := otpverifier.NewTOTPVerifier(config)
|
||||
|
||||
// Generate a token and signature using the verifier
|
||||
token, signature := verifier.GenerateToken()
|
||||
token, signature := verifier.GenerateTokenWithSignature()
|
||||
|
||||
// Verify the token and signature (should succeed)
|
||||
isValid := verifier.Verify(token, signature)
|
||||
@ -152,7 +152,7 @@ func TestDefaultConfigTOTPVerifier_Verify(t *testing.T) {
|
||||
// Switch to a non-signature mode and test again
|
||||
config.UseSignature = false
|
||||
verifier = otpverifier.NewTOTPVerifier(config)
|
||||
token, _ = verifier.GenerateToken()
|
||||
token, _ = verifier.GenerateTokenWithSignature()
|
||||
|
||||
// Verify the token without a signature (should succeed)
|
||||
isValid = verifier.Verify(token, "")
|
||||
@ -183,8 +183,8 @@ func TestTOTPVerifier_PeriodicCleanup(t *testing.T) {
|
||||
verifier := otpverifier.NewTOTPVerifier(config)
|
||||
|
||||
// Simulate used tokens
|
||||
token1, _ := verifier.GenerateToken()
|
||||
verifier.Verify(token1, "")
|
||||
token1 := verifier.GenerateToken()
|
||||
verifier.Verify(token1)
|
||||
|
||||
// Wait for periodic cleanup to occur (less than the token validity period)
|
||||
time.Sleep(time.Duration(period*3/4) * time.Second)
|
||||
@ -227,7 +227,7 @@ func TestHOTPVerifier_Verify(t *testing.T) {
|
||||
verifier := otpverifier.NewHOTPVerifier(config)
|
||||
|
||||
// Generate a token and signature using the verifier
|
||||
token, signature := verifier.GenerateToken()
|
||||
token, signature := verifier.GenerateTokenWithSignature()
|
||||
|
||||
// Verify the token and signature
|
||||
isValid := verifier.Verify(token, signature)
|
||||
@ -239,7 +239,7 @@ func TestHOTPVerifier_Verify(t *testing.T) {
|
||||
initialCounter++
|
||||
config.Counter = initialCounter
|
||||
verifier = otpverifier.NewHOTPVerifier(config)
|
||||
newToken, newSignature := verifier.GenerateToken()
|
||||
newToken, newSignature := verifier.GenerateTokenWithSignature()
|
||||
|
||||
// Verify the new token and signature
|
||||
isValid = verifier.Verify(newToken, newSignature)
|
||||
@ -258,10 +258,10 @@ func TestHOTPVerifier_Verify(t *testing.T) {
|
||||
verifier = otpverifier.NewHOTPVerifier(config)
|
||||
|
||||
// Generate a token using the verifier
|
||||
token, _ = verifier.GenerateToken()
|
||||
token = verifier.GenerateToken()
|
||||
|
||||
// Verify the token
|
||||
isValid = verifier.Verify(token, "")
|
||||
isValid = verifier.Verify(token)
|
||||
if !isValid {
|
||||
t.Errorf("Token should be valid (hash function: %s)", hashFunc)
|
||||
}
|
||||
@ -299,7 +299,7 @@ func TestDefaultConfigHOTPVerifier_Verify(t *testing.T) {
|
||||
verifier := otpverifier.NewHOTPVerifier(config)
|
||||
|
||||
// Generate a token and signature using the verifier
|
||||
token, signature := verifier.GenerateToken()
|
||||
token, signature := verifier.GenerateTokenWithSignature()
|
||||
|
||||
// Verify the token and signature
|
||||
isValid := verifier.Verify(token, signature)
|
||||
@ -311,7 +311,7 @@ func TestDefaultConfigHOTPVerifier_Verify(t *testing.T) {
|
||||
initialCounter++
|
||||
config.Counter = initialCounter
|
||||
verifier = otpverifier.NewHOTPVerifier(config)
|
||||
newToken, newSignature := verifier.GenerateToken()
|
||||
newToken, newSignature := verifier.GenerateTokenWithSignature()
|
||||
|
||||
// Verify the new token and signature
|
||||
isValid = verifier.Verify(newToken, newSignature)
|
||||
@ -330,7 +330,7 @@ func TestDefaultConfigHOTPVerifier_Verify(t *testing.T) {
|
||||
verifier = otpverifier.NewHOTPVerifier(config)
|
||||
|
||||
// Generate a token using the verifier
|
||||
token, _ = verifier.GenerateToken()
|
||||
token, _ = verifier.GenerateTokenWithSignature()
|
||||
|
||||
// Verify the token
|
||||
isValid = verifier.Verify(token, "")
|
||||
@ -390,7 +390,7 @@ func TestOTPFactory(t *testing.T) {
|
||||
totpConfig.UseSignature = true
|
||||
totpConfig.TimeSource = timeSource
|
||||
totpVerifier = otpverifier.NewTOTPVerifier(totpConfig)
|
||||
totpToken, totpSignature := totpVerifier.GenerateToken()
|
||||
totpToken, totpSignature := totpVerifier.GenerateTokenWithSignature()
|
||||
if !totpVerifier.Verify(totpToken, totpSignature) {
|
||||
t.Errorf("TOTP token and signature should be valid (hash function: %s)", hashFunc)
|
||||
}
|
||||
@ -398,7 +398,7 @@ func TestOTPFactory(t *testing.T) {
|
||||
// Test HOTPVerifier token generation and verification
|
||||
hotpConfig.UseSignature = true
|
||||
hotpVerifier = otpverifier.NewHOTPVerifier(hotpConfig)
|
||||
hotpToken, hotpSignature := hotpVerifier.GenerateToken()
|
||||
hotpToken, hotpSignature := hotpVerifier.GenerateTokenWithSignature()
|
||||
if !hotpVerifier.Verify(hotpToken, hotpSignature) {
|
||||
t.Errorf("HOTP token and signature should be valid (hash function: %s)", hashFunc)
|
||||
}
|
||||
@ -877,15 +877,17 @@ func TestTOTPVerifier_VerifyPanic(t *testing.T) {
|
||||
|
||||
// Create a TOTPVerifier with a negative sync window
|
||||
config := otpverifier.Config{
|
||||
Secret: secret,
|
||||
SyncWindow: -1,
|
||||
Secret: secret,
|
||||
SyncWindow: -1,
|
||||
UseSignature: true,
|
||||
Hash: otpverifier.SHA256,
|
||||
}
|
||||
|
||||
// Create a new TOTPVerifier instance
|
||||
verifier := otpverifier.NewTOTPVerifier(config)
|
||||
|
||||
// Generate a token and signature using the verifier
|
||||
token, signature := verifier.GenerateToken()
|
||||
token, signature := verifier.GenerateTokenWithSignature()
|
||||
|
||||
// Expect a panic when calling Verify with a negative sync window
|
||||
defer func() {
|
||||
@ -910,13 +912,14 @@ func TestHOTPVerifier_VerifyPanic(t *testing.T) {
|
||||
config := otpverifier.Config{
|
||||
Secret: secret,
|
||||
SyncWindow: -1,
|
||||
Hash: otpverifier.SHA256,
|
||||
}
|
||||
|
||||
// Create a new HOTPVerifier instance
|
||||
verifier := otpverifier.NewHOTPVerifier(config)
|
||||
|
||||
// Generate a token and signature using the verifier
|
||||
token, signature := verifier.GenerateToken()
|
||||
token, signature := verifier.GenerateTokenWithSignature()
|
||||
|
||||
// Expect a panic when calling Verify with a negative sync window
|
||||
defer func() {
|
||||
@ -933,3 +936,95 @@ func TestHOTPVerifier_VerifyPanic(t *testing.T) {
|
||||
// Call Verify, which should panic
|
||||
verifier.Verify(token, signature)
|
||||
}
|
||||
|
||||
func TestTOTPVerifier_VerifyMissingSignature(t *testing.T) {
|
||||
secret := gotp.RandomSecret(16)
|
||||
|
||||
config := otpverifier.Config{
|
||||
Secret: secret,
|
||||
UseSignature: true,
|
||||
Hash: otpverifier.SHA256,
|
||||
}
|
||||
|
||||
verifier := otpverifier.NewTOTPVerifier(config)
|
||||
|
||||
token, _ := verifier.GenerateTokenWithSignature()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("Expected Verify to panic with missing signature, but it didn't")
|
||||
} else {
|
||||
expectedPanicMessage := "totp: Signature is required but not provided"
|
||||
if r != expectedPanicMessage {
|
||||
t.Errorf("Expected panic message: %s, but got: %s", expectedPanicMessage, r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
verifier.Verify(token)
|
||||
}
|
||||
|
||||
func TestTOTPVerifier_VerifySignatureMismatch(t *testing.T) {
|
||||
secret := gotp.RandomSecret(16)
|
||||
|
||||
config := otpverifier.Config{
|
||||
Secret: secret,
|
||||
UseSignature: true,
|
||||
Hash: otpverifier.SHA256,
|
||||
}
|
||||
|
||||
verifier := otpverifier.NewTOTPVerifier(config)
|
||||
|
||||
token, _ := verifier.GenerateTokenWithSignature()
|
||||
invalidSignature := "invalid_signature"
|
||||
|
||||
if verifier.Verify(token, invalidSignature) {
|
||||
t.Errorf("Expected Verify to return false for signature mismatch, but it returned true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHOTPVerifier_VerifyMissingSignature(t *testing.T) {
|
||||
secret := gotp.RandomSecret(16)
|
||||
|
||||
config := otpverifier.Config{
|
||||
Secret: secret,
|
||||
UseSignature: true,
|
||||
Hash: otpverifier.SHA256,
|
||||
}
|
||||
|
||||
verifier := otpverifier.NewHOTPVerifier(config)
|
||||
|
||||
token, _ := verifier.GenerateTokenWithSignature()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("Expected Verify to panic with missing signature, but it didn't")
|
||||
} else {
|
||||
expectedPanicMessage := "hotp: Signature is required but not provided"
|
||||
if r != expectedPanicMessage {
|
||||
t.Errorf("Expected panic message: %s, but got: %s", expectedPanicMessage, r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
verifier.Verify(token)
|
||||
}
|
||||
|
||||
func TestHOTPVerifier_VerifySignatureMismatch(t *testing.T) {
|
||||
secret := gotp.RandomSecret(16)
|
||||
|
||||
config := otpverifier.Config{
|
||||
Secret: secret,
|
||||
UseSignature: true,
|
||||
Hash: otpverifier.SHA256,
|
||||
}
|
||||
|
||||
verifier := otpverifier.NewHOTPVerifier(config)
|
||||
|
||||
token, _ := verifier.GenerateTokenWithSignature()
|
||||
invalidSignature := "invalid_signature"
|
||||
|
||||
if verifier.Verify(token, invalidSignature) {
|
||||
t.Errorf("Expected Verify to return false for signature mismatch, but it returned true")
|
||||
}
|
||||
}
|
||||
|
@ -71,11 +71,59 @@ func NewTOTPVerifier(config ...Config) *TOTPVerifier {
|
||||
//
|
||||
// Note: This TOTP verification using [crypto/subtle] requires careful consideration
|
||||
// when setting TimeSource and Period to ensure correct usage.
|
||||
func (v *TOTPVerifier) Verify(token, signature string) bool {
|
||||
func (v *TOTPVerifier) Verify(token string, signature ...string) bool {
|
||||
if v.config.SyncWindow < 0 {
|
||||
panic("totp: SyncWindow must be greater than or equal to zero")
|
||||
}
|
||||
|
||||
if v.config.UseSignature {
|
||||
if len(signature) == 0 {
|
||||
panic("totp: Signature is required but not provided")
|
||||
}
|
||||
|
||||
return v.verifyWithSignature(token, signature[0])
|
||||
}
|
||||
|
||||
return v.verifyWithoutSignature(token)
|
||||
}
|
||||
|
||||
// verifyWithoutSignature checks if the provided token is valid for the current time without signature verification.
|
||||
func (v *TOTPVerifier) verifyWithoutSignature(token string) bool {
|
||||
currentTimestamp := v.config.TimeSource().Unix()
|
||||
currentTimeStep := currentTimestamp / int64(v.config.Period)
|
||||
|
||||
// Check the syncWindow periods before and after the current time step
|
||||
for offset := -v.config.SyncWindow; offset <= v.config.SyncWindow; offset++ {
|
||||
expectedTimeStep := currentTimeStep + int64(offset)
|
||||
expectedTimestamp := expectedTimeStep * int64(v.config.Period)
|
||||
|
||||
v.m.Lock()
|
||||
if _, found := v.UsedTokens[expectedTimeStep]; found {
|
||||
v.m.Unlock()
|
||||
continue // Skip this step as the token has already been used
|
||||
}
|
||||
v.m.Unlock()
|
||||
|
||||
// Verify the token for this time step
|
||||
// This should be safe now because a synchronization window similar to HOTP is implemented.
|
||||
// The token will be marked as used even if there is still time remaining in the period (e.g., 30 seconds).
|
||||
// Without implementing a synchronization window similar to HOTP, this can lead to a high vulnerability
|
||||
// where a used token is still considered valid due to the period.
|
||||
generatedToken := v.totp.At(expectedTimestamp)
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(generatedToken)) == 1 {
|
||||
v.m.Lock()
|
||||
v.UsedTokens[expectedTimeStep] = token // Record the token as used
|
||||
v.m.Unlock()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false // Token is invalid
|
||||
}
|
||||
|
||||
// verifyWithSignature checks if the provided token and signature are valid for the current time.
|
||||
func (v *TOTPVerifier) verifyWithSignature(token, signature string) bool {
|
||||
|
||||
currentTimestamp := v.config.TimeSource().Unix()
|
||||
currentTimeStep := currentTimestamp / int64(v.config.Period)
|
||||
|
||||
@ -97,32 +145,32 @@ func (v *TOTPVerifier) Verify(token, signature string) bool {
|
||||
// Without implementing a synchronization window similar to HOTP, this can lead to a high vulnerability
|
||||
// where a used token is still considered valid due to the period.
|
||||
if v.totp.Verify(token, expectedTimestamp) {
|
||||
signatureMatch := true // Assume true if not using signatures.
|
||||
|
||||
if v.config.UseSignature {
|
||||
generatedSignature := v.generateSignature(token)
|
||||
signatureMatch = subtle.ConstantTimeCompare([]byte(signature), []byte(generatedSignature)) == 1
|
||||
if subtle.ConstantTimeCompare([]byte(signature), []byte(generatedSignature)) != 1 {
|
||||
return false // Signature mismatch
|
||||
}
|
||||
}
|
||||
|
||||
if signatureMatch {
|
||||
v.m.Lock()
|
||||
v.UsedTokens[expectedTimeStep] = token // Record the token as used
|
||||
v.m.Unlock()
|
||||
return true
|
||||
}
|
||||
v.m.Lock()
|
||||
v.UsedTokens[expectedTimeStep] = token // Record the token as used
|
||||
v.m.Unlock()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false // Token is invalid
|
||||
}
|
||||
|
||||
// GenerateToken generates a token and signature for the current time.
|
||||
func (v *TOTPVerifier) GenerateToken() (string, string) {
|
||||
// GenerateToken generates a token for the current time.
|
||||
func (v *TOTPVerifier) GenerateToken() string {
|
||||
return v.totp.Now()
|
||||
}
|
||||
|
||||
// GenerateTokenWithSignature generates a token and signature for the current time.
|
||||
func (v *TOTPVerifier) GenerateTokenWithSignature() (string, string) {
|
||||
token := v.totp.Now()
|
||||
signature := ""
|
||||
if v.config.UseSignature {
|
||||
signature = v.generateSignature(token)
|
||||
}
|
||||
signature := v.generateSignature(token)
|
||||
return token, signature
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user