• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

H0llyW00dzZ / fiber2fa / #4

12 Jun 2024 11:32AM UTC coverage: 85.819% (+0.1%) from 85.714%
#4

push

web-flow
Improve Performance for HOTP OCRA (#196)

* Refactor OCRA Verify

- [+] refactor(otpverifier): simplify OCRA token comparison logic

* Reduce allocations per operation

- [+] refactor(benchmark_test.go): update benchmark results for OCRAVerify with reduced allocations per operation
- [+] refactor(hotp_ocra.go): optimize generateOCRA by preallocating data slice and using copy instead of append
- [+] refactor(hotp_ocra.go): simplify HOTP string formatting using fmt.Sprintf with width and padding options

6 of 6 new or added lines in 1 file covered. (100.0%)

14 existing lines in 3 files now uncovered.

1174 of 1368 relevant lines covered (85.82%)

65.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.69
/internal/otpverifier/otpverifier.go
1
// Copyright (c) 2024 H0llyW00dz All rights reserved.
2
//
3
// License: BSD 3-Clause License
4

5
package otpverifier
6

7
import (
8
        "crypto/rand"
9
        "crypto/sha1"
10
        "crypto/sha256"
11
        "crypto/sha512"
12
        "encoding/base32"
13
        "fmt"
14
        "image"
15
        "image/color"
16
        "io"
17
        "math/big"
18
        "net/url"
19
        "strings"
20
        "time"
21

22
        blake2botp "github.com/H0llyW00dzZ/fiber2fa/internal/crypto/hash/blake2botp"
23
        "github.com/H0llyW00dzZ/fiber2fa/internal/crypto/hash/blake3otp"
24
        "github.com/skip2/go-qrcode"
25
        "github.com/xlzd/gotp"
26
        "golang.org/x/image/font"
27
        "golang.org/x/image/font/basicfont"
28
)
29

30
const (
31
        // SHA1 represents the SHA-1 hash function.
32
        // SHA-1 produces a 160-bit (20-byte) hash value.
33
        // It is considered less secure compared to newer variants due to potential vulnerabilities.
34
        SHA1 = "SHA1"
35

36
        // SHA224 represents the SHA-224 hash function.
37
        // SHA-224 produces a 224-bit (28-byte) hash value.
38
        // It is a truncated version of SHA-256 and provides a balance between security and performance.
39
        //
40
        // Note: Some 2FA mobile apps might not support SHA-224 due to poor ecosystem.
41
        SHA224 = "SHA224"
42

43
        // SHA256 represents the SHA-256 hash function.
44
        // SHA-256 produces a 256-bit (32-byte) hash value.
45
        // It provides a higher level of security compared to SHA-1 and is recommended for newer applications.
46
        SHA256 = "SHA256"
47

48
        // SHA384 represents the SHA-384 hash function.
49
        // SHA-384 produces a 384-bit (48-byte) hash value.
50
        // It is a truncated version of SHA-512 and provides a balance between security and performance.
51
        //
52
        // Note: Some 2FA mobile apps might not support SHA-384 due to poor ecosystem.
53
        SHA384 = "SHA384"
54

55
        // SHA512 represents the SHA-512 hash function.
56
        // SHA-512 produces a 512-bit (64-byte) hash value.
57
        // It offers the highest level of security among the commonly used SHA variants.
58
        SHA512 = "SHA512"
59

60
        // SHA512S224 represents the SHA-512/224 hash function.
61
        // SHA-512/224 produces a 224-bit (28-byte) hash value.
62
        // It is a truncated version of SHA-512 and provides a balance between security and performance.
63
        //
64
        // Note: Some 2FA mobile apps might not support SHA-512/224 due to poor ecosystem.
65
        SHA512S224 = "SHA512/224"
66

67
        // SHA512S256 represents the SHA-512/256 hash function.
68
        // SHA-512/256 produces a 256-bit (32-byte) hash value.
69
        // It is a truncated version of SHA-512 and provides a balance between security and performance.
70
        //
71
        // Note: Some 2FA mobile apps might not support SHA-512/256 due to poor ecosystem.
72
        SHA512S256 = "SHA512/256"
73

74
        // BLAKE2b256 represents the secure BLAKE2b hash function with a 256-bit output size.
75
        // It provides a 256-bit (32-byte) hash value.
76
        //
77
        // Note: Some 2FA mobile apps might not support (poor ecosystems) this hash function, so it is recommended to build your own 2FA mobile apps.
78
        BLAKE2b256 = "BLAKE2b256"
79

80
        // BLAKE2b384 represents the secure BLAKE2b hash function with a 384-bit output size.
81
        // It provides a 384-bit (48-byte) hash value.
82
        //
83
        // Note: Some 2FA mobile apps might not support (poor ecosystems) this hash function, so it is recommended to build your own 2FA mobile apps.
84
        BLAKE2b384 = "BLAKE2b384"
85

86
        // BLAKE2b512 represents the secure BLAKE2b hash function with a 512-bit output size.
87
        // It provides a 512-bit (64-byte) hash value.
88
        //
89
        // Note: Some 2FA mobile apps might not support (poor ecosystems) this hash function, so it is recommended to build your own 2FA mobile apps.
90
        BLAKE2b512 = "BLAKE2b512"
91

92
        // BLAKE3256 represents the secure BLAKE3 hash function with a 256-bit output size.
93
        // It provides a 256-bit (32-byte) hash value.
94
        // BLAKE3 is a modern, high-performance cryptographic hash function that is faster and more secure than SHA-3 and BLAKE2.
95
        //
96
        // Note: Some 2FA mobile apps might not support this hash function due to its relatively new adoption, so it is recommended to build your own 2FA mobile apps when using BLAKE3.
97
        BLAKE3256 = "BLAKE3256"
98

99
        // BLAKE3384 represents the secure BLAKE3 hash function with a 384-bit output size.
100
        // It provides a 384-bit (48-byte) hash value.
101
        // BLAKE3 is a modern, high-performance cryptographic hash function that is faster and more secure than SHA-3 and BLAKE2.
102
        //
103
        // Note: Some 2FA mobile apps might not support this hash function due to its relatively new adoption, so it is recommended to build your own 2FA mobile apps when using BLAKE3.
104
        BLAKE3384 = "BLAKE3384"
105

106
        // BLAKE3512 represents the secure BLAKE3 hash function with a 512-bit output size.
107
        // It provides a 512-bit (64-byte) hash value.
108
        // BLAKE3 is a modern, high-performance cryptographic hash function that is faster and more secure than SHA-3 and BLAKE2.
109
        //
110
        // Note: Some 2FA mobile apps might not support this hash function due to its relatively new adoption, so it is recommended to build your own 2FA mobile apps when using BLAKE3.
111
        BLAKE3512 = "BLAKE3512"
112
)
113

114
const (
115
        // NoneStrict represents no strictness for the synchronization window.
116
        // It has a value of 0, meaning the synchronization window size is not enforced.
117
        NoneStrict = iota
118

119
        // HighStrict represents the highest level of strictness for the synchronization window.
120
        // It has a value of 1, meaning the synchronization window size is fixed at 1.
121
        HighStrict
122

123
        // MediumStrict represents a medium level of strictness for the synchronization window.
124
        // It has a value of 2, and the actual synchronization window size is determined by the corresponding range in SyncWindowRanges.
125
        MediumStrict
126

127
        // LowStrict represents a low level of strictness for the synchronization window.
128
        // It has a value of 3, and the actual synchronization window size is determined by the corresponding range in SyncWindowRanges.
129
        LowStrict
130
)
131

132
const (
133
        // CounterMismatchThreshold1x represents a counter mismatch threshold of 1.
134
        // If the number of counter mismatches exceeds this threshold,
135
        // the sync window size will be adjusted to the value defined in the verifier's configuration.
136
        CounterMismatchThreshold1x = iota + 1
137

138
        // CounterMismatchThreshold3x represents a counter mismatch threshold of 3.
139
        // If the number of counter mismatches exceeds this threshold,
140
        // the sync window size will be adjusted to the value defined in the verifier's configuration.
141
        CounterMismatchThreshold3x = iota + 2
142

143
        // CounterMismatchThreshold5x represents a counter mismatch threshold of 5.
144
        // If the number of counter mismatches exceeds this threshold,
145
        // the sync window size will be adjusted to the value defined in the verifier's configuration.
146
        CounterMismatchThreshold5x = iota + 3
147
)
148

149
// SyncWindowRanges is a map that associates strictness levels with their corresponding ranges of synchronization window sizes.
150
// The ranges are used to dynamically calculate the actual synchronization window size based on the counter value:
151
//
152
//   - For [MediumStrict], the synchronization window size can be between 2 and 5.
153
//   - For [LowStrict], the synchronization window size can be between 5 and 10.
154
//   - The [HighStrict] level does not have a range defined in [SyncWindowRanges] because it has a fixed synchronization window size of 1.
155
//
156
// Also note that there are some considerations to keep in the mind:
157
//
158
//   - Security vs. Convenience Trade-off: Increasing the sync window size makes the system more user-friendly since it's less likely to reject valid tokens due to minor synchronization issues.
159
//     However, a larger window also increases the period during which an attacker can use a stolen OTP to gain unauthorized access.
160
var SyncWindowRanges = map[int][]int{
161
        MediumStrict: {2, 5},
162
        LowStrict:    {5, 10},
163
}
164

165
const (
166
        // FastCleanup represents the fastest cleanup interval for removing expired tokens in the TOTP verifier.
167
        // It is assigned a value of iota + 1, which evaluates to 1.
168
        // When FastCleanup is selected, the cleanup process runs every 25% of the TOTP period, providing the most frequent cleanup.
169
        FastCleanup = iota + 1
170

171
        // MediumCleanup represents a medium cleanup interval for removing expired tokens in the TOTP verifier.
172
        // It is assigned the next sequential value of iota, which evaluates to 2.
173
        // When MediumCleanup is selected, the cleanup process runs every 50% of the TOTP period, providing a balanced cleanup frequency.
174
        MediumCleanup
175

176
        // SlowCleanup represents the slowest cleanup interval for removing expired tokens in the TOTP verifier.
177
        // It is assigned the next sequential value of iota, which evaluates to 3.
178
        // When SlowCleanup is selected, the cleanup process runs every 75% of the TOTP period, providing the least frequent cleanup.
179
        SlowCleanup
180
)
181

182
// CleanupIntervals is a map that associates cleanup interval constants with their corresponding percentage of the TOTP period.
183
// The cleanup interval determines how frequently the cleanup process runs to remove expired tokens.
184
//
185
// The available cleanup intervals are:
186
//   - [FastCleanup]: The cleanup process runs every 25% of the TOTP period.
187
//   - [MediumCleanup]: The cleanup process runs every 50% of the TOTP period.
188
//   - [SlowCleanup]: The cleanup process runs every 75% of the TOTP period.
189
//
190
// Note: Choosing an appropriate cleanup interval is important to balance the need for timely removal of expired tokens
191
// and the overhead of running the cleanup process too frequently. The default cleanup interval is [MediumCleanup].
192
var CleanupIntervals = map[int]float64{
193
        FastCleanup:   0.25, // 25% of the TOTP period
194
        MediumCleanup: 0.50, // 50% of the TOTP period
195
        SlowCleanup:   0.75, // 75% of the TOTP period
196
}
197

198
// TimeSource is a function type that returns the current time.
199
type TimeSource func() time.Time
200

201
// OTPVerifier is an interface that defines the behavior of an OTP verifier.
202
type OTPVerifier interface {
203
        Verify(token string, signature ...string) bool
204
        GenerateToken() string
205
        GenerateTokenWithSignature() (string, string)
206
        SetCounter(counter uint64)
207
        GetCounter() uint64
208
        GenerateOTPURL(issuer, accountName string) string
209
}
210

211
// Config is a struct that holds the configuration options for the OTP verifier.
212
type Config struct {
213
        Secret                  string
214
        Digits                  int
215
        Period                  int
216
        UseSignature            bool
217
        TimeSource              TimeSource
218
        Counter                 uint64
219
        CounterMismatch         int
220
        Hasher                  *gotp.Hasher
221
        SyncWindow              int
222
        ResyncWindowDelay       time.Duration
223
        URITemplate             string
224
        CustomURITemplateParams map[string]string
225
        Hash                    string
226
        Crypto                  Crypto
227
        CleanupInterval         int
228
}
229

230
// QRCodeConfig represents the configuration for generating QR codes.
231
type QRCodeConfig struct {
232
        Level              qrcode.RecoveryLevel
233
        Size               int
234
        ForegroundColor    color.Color
235
        BackgroundColor    color.Color
236
        DisableBorder      bool
237
        TopText            string
238
        BottomText         string
239
        Font               font.Face
240
        TopTextPosition    image.Point
241
        BottomTextPosition image.Point
242
        FilePath           string
243
}
244

245
// Crypto is a struct that holds cryptographic configuration options.
246
//
247
// Note: This design allows for flexibility at the top level.
248
// For example, it can be used anywhere in this codebase without calling [crypto/rand] again (DRY).
249
// The Rand, Prime, and Int functions are provided as part of the Crypto struct to enable secure random number generation
250
// and prime number generation. These functions are used in various cryptographic operations throughout the codebase.
251
//
252
// By including them in the Crypto struct, they can be easily accessed and reused without the need to import and call [crypto/rand] multiple times.
253
// This promotes code reusability, maintainability, and adherence to the DRY (Don't Repeat Yourself) principle.
254
type Crypto struct {
255
        Rand  func([]byte) (int, error)
256
        Prime func(rand io.Reader, bits int) (*big.Int, error)
257
        Int   func(rand io.Reader, max *big.Int) (n *big.Int, err error)
258
}
259

260
// DefaultConfig represents the default configuration values.
261
var DefaultConfig = Config{
262
        Digits:                  6,
263
        Period:                  30,
264
        UseSignature:            false,
265
        SyncWindow:              HighStrict,
266
        ResyncWindowDelay:       30 * time.Minute,
267
        CounterMismatch:         CounterMismatchThreshold3x,
268
        URITemplate:             "otpauth://%s/%s:%s?secret=%s&issuer=%s&digits=%d&algorithm=%s",
269
        CustomURITemplateParams: nil,
270
        Crypto: Crypto{
271
                Rand:  rand.Read,
272
                Prime: rand.Prime,
273
                Int:   rand.Int,
274
        },
275
}
276

277
// Hashers is a map of supported hash functions.
278
//
279
// Note: This design allows for flexibility at the top level. For example,
280
// it can be used for experimental purposes, such as creating custom hashing functions (advanced use cases) related to cryptography.
281
var Hashers = map[string]*gotp.Hasher{
282
        SHA1:       {HashName: SHA1, Digest: sha1.New},
283
        SHA224:     {HashName: SHA224, Digest: sha256.New224},
284
        SHA256:     {HashName: SHA256, Digest: sha256.New},
285
        SHA384:     {HashName: SHA384, Digest: sha512.New384},
286
        SHA512:     {HashName: SHA512, Digest: sha512.New},
287
        SHA512S224: {HashName: SHA512S224, Digest: sha512.New512_224},
288
        SHA512S256: {HashName: SHA512S256, Digest: sha512.New512_256},
289
        BLAKE2b256: {HashName: BLAKE2b256, Digest: blake2botp.New256},
290
        BLAKE2b384: {HashName: BLAKE2b384, Digest: blake2botp.New384},
291
        BLAKE2b512: {HashName: BLAKE2b512, Digest: blake2botp.New512},
292
        BLAKE3256:  {HashName: BLAKE3256, Digest: blake3otp.New256},
293
        BLAKE3384:  {HashName: BLAKE3384, Digest: blake3otp.New384},
294
        BLAKE3512:  {HashName: BLAKE3512, Digest: blake3otp.New512},
295
}
296

297
// DefaultQRCodeConfig represents the default configuration for generating QR codes.
298
var DefaultQRCodeConfig = InitializeDefaultQRCodeConfig()
299

300
// InitializeDefaultQRCodeConfig sets up the default configuration for generating QR codes with dynamic text positions.
301
func InitializeDefaultQRCodeConfig() QRCodeConfig {
2✔
302
        size := 256      // This is the QR code size used in the default config
2✔
303
        textHeight := 20 // This should be set to the height of the text
2✔
304

2✔
305
        return QRCodeConfig{
2✔
306
                Level:              qrcode.Medium,
2✔
307
                Size:               size,
2✔
308
                ForegroundColor:    color.Black,
2✔
309
                BackgroundColor:    color.White,
2✔
310
                DisableBorder:      false,
2✔
311
                TopText:            "",
2✔
312
                BottomText:         "",
2✔
313
                Font:               basicfont.Face7x13,
2✔
314
                TopTextPosition:    image.Point{X: size / 2, Y: textHeight / 1},      // Dynamically calculated
2✔
315
                BottomTextPosition: image.Point{X: size / 2, Y: size + textHeight/1}, // Dynamically calculated
2✔
316
        }
2✔
317
}
2✔
318

319
// ensureDefaultConfig checks the provided config and fills in any zero values with defaults.
320
func ensureDefaultConfig(config QRCodeConfig) QRCodeConfig {
8✔
321
        if config.Font == nil {
12✔
322
                config.Font = DefaultQRCodeConfig.Font
4✔
323
        }
4✔
324
        if config.ForegroundColor == nil {
10✔
325
                config.ForegroundColor = DefaultQRCodeConfig.ForegroundColor
2✔
326
        }
2✔
327
        if config.BackgroundColor == nil {
12✔
328
                config.BackgroundColor = DefaultQRCodeConfig.BackgroundColor
4✔
329
        }
4✔
330
        if config.TopTextPosition == (image.Point{}) {
12✔
331
                config.TopTextPosition = DefaultQRCodeConfig.TopTextPosition
4✔
332
        }
4✔
333
        if config.BottomTextPosition == (image.Point{}) {
12✔
334
                config.BottomTextPosition = DefaultQRCodeConfig.BottomTextPosition
4✔
335
        }
4✔
336
        return config
8✔
337
}
338

339
// generateOTPURL creates the URL for the QR code based on the provided URI template.
340
func (v *Config) generateOTPURL(issuer, accountName string) string {
8✔
341
        // Determine the OTP type based on whether a counter is used.
8✔
342
        otpType := gotp.OtpTypeTotp
8✔
343
        if v.Counter != 0 {
12✔
344
                otpType = gotp.OtpTypeHotp
4✔
345
        }
4✔
346

347
        // Parse the URI template to get a base URL object.
348
        // Note: It is important to use url.PathEscape for the issuer and account name
349
        // because the URL won't work correctly without it when scanning the QR code.
350
        baseURL, err := url.Parse(fmt.Sprintf(v.URITemplate, otpType, url.PathEscape(issuer), url.PathEscape(accountName)))
8✔
351
        if err != nil {
8✔
352
                // Panic is better than handling the error using fmt, log, or any other method since this is an internal error.
×
UNCOV
353
                panic(err)
×
354
        }
355

356
        // Prepare query parameters.
357
        // Note: This should be fixing a weird bug related to the "issuer" field when spaces are included,
358
        // ensuring that "Gopher Company" is displayed correctly instead of "Gopher+Company".
359
        query := baseURL.Query()
8✔
360
        query.Set("secret", v.Secret)
8✔
361
        query.Set("digits", fmt.Sprint(v.Digits))
8✔
362
        query.Set("algorithm", v.Hasher.HashName)
8✔
363
        if otpType != gotp.OtpTypeTotp {
12✔
364
                query.Set("counter", fmt.Sprint(v.Counter))
4✔
365
        }
4✔
366
        if otpType != gotp.OtpTypeHotp {
12✔
367
                query.Set("period", fmt.Sprint(v.Period))
4✔
368
        }
4✔
369

370
        // Add custom URI Template parameters to the query if CustomURITemplateParams is not nil
371
        if v.CustomURITemplateParams != nil {
10✔
372
                for key, value := range v.CustomURITemplateParams {
4✔
373
                        escapedKey := url.PathEscape(key)
2✔
374
                        escapedValue := url.PathEscape(value)
2✔
375
                        query.Set(escapedKey, escapedValue)
2✔
376
                }
2✔
377
        }
378

379
        // Re-encode the query parameters.
380
        baseURL.RawQuery = query.Encode()
8✔
381

8✔
382
        // Return the fully constructed URL string.
8✔
383
        return baseURL.String()
8✔
384
}
385

386
// OTPFactory is a simple factory function to create an OTPVerifier.
387
// It takes a Config and creates the appropriate verifier based on the configuration.
388
func OTPFactory(config Config) OTPVerifier {
26✔
389
        if config.Counter != 0 {
39✔
390
                return NewHOTPVerifier(config)
13✔
391
        }
13✔
392
        return NewTOTPVerifier(config)
13✔
393
}
394

395
// GetHasherByName returns a pointer to a [gotp.Hasher] based on the given hash function name.
396
// It panics if the hash function name is empty or not supported.
397
//
398
// The supported hash function names are:
399
//   - [SHA1]
400
//   - [SHA224]
401
//   - [SHA256]
402
//   - [SHA384]
403
//   - [SHA512]
404
//   - [SHA512S224]
405
//   - [SHA512S256]
406
//   - [BLAKE2b256]
407
//   - [BLAKE2b384]
408
//   - [BLAKE2b512]
409
//   - [BLAKE3256]
410
//   - [BLAKE3384]
411
//   - [BLAKE3512]
412
//
413
// Note: The hash function name is case-sensitive.
414
func (v *Config) GetHasherByName(Hash string) *gotp.Hasher {
255✔
415
        if Hash == "" {
256✔
416
                panic("GetHasherByName: hash function name cannot be empty")
1✔
417
        }
418

419
        hasher, exists := Hashers[Hash]
254✔
420
        if !exists {
255✔
421
                panic(fmt.Sprintf("GetHasherByName: hash function %s is not supported", Hash))
1✔
422
        }
423
        return hasher
253✔
424
}
425

426
// GenerateSecureRandomCounter generates a random counter number for HOTP with a specified maximum number of digits.
427
//
428
// Note: The maximum value for maxDigits is 30. Setting maxDigits to a value greater than 30 may result in integer overflow and panic.
429
// Also note that, there is no guarantee about digits for example when set 6 it the result can be possible 5 digits but it secure,
430
// the reason why there is no guarantee it requires skilled mathematical reasoning to understand about cryptography
431
func (v *Config) GenerateSecureRandomCounter(maxDigits int) uint64 {
33✔
432
        if maxDigits <= 0 {
35✔
433
                panic("GenerateSecureRandomCounter: maxDigits must be greater than 0")
2✔
434
        }
435

436
        // Check if maxDigits is within the safe range
437
        const maxSafeDigits = 30 // Maximum number of digits that can be safely represented in uint64
31✔
438
        if maxDigits > maxSafeDigits {
32✔
439
                panic(fmt.Sprintf("GenerateSecureRandomCounter: maxDigits must be less than or equal to %d to avoid integer overflow", maxSafeDigits))
1✔
440
        }
441

442
        // Calculate the maximum possible value based on the number of digits
443
        var max uint64 = 9*v.cryptopowpow10(maxDigits-1) + v.cryptopowpow10(maxDigits-1) - 1
30✔
444

30✔
445
        // Create a fixed-size byte array to store the random bytes
30✔
446
        var randomBytes [8]byte
30✔
447

30✔
448
        // Generate random bytes using the Crypto.Rand function from the Config struct
30✔
449
        // Note: This will continue generating random bytes and is safe. If it fails to generate random bytes, it will panic and crash.
30✔
450
        _, err := v.Crypto.Rand(randomBytes[:])
30✔
451
        if err != nil {
30✔
UNCOV
452
                panic(err)
×
453
        }
454

455
        // Convert the random bytes to a uint64 value
456
        var n uint64
30✔
457
        for i := 0; i < 8; i++ {
270✔
458
                n = (n << 8) | uint64(randomBytes[i])
240✔
459
        }
240✔
460

461
        // Ensure the generated number is within the desired range
462
        n = n % (max + 1)
30✔
463

30✔
464
        return n
30✔
465
}
466

467
// cryptopowpow10 is a helper function that calculates the power of 10 for a given exponent.
468
//
469
// Reference: https://en.wikipedia.org/wiki/Exponentiation
470
//
471
// Note: This Helper Function for SRC-Generator is secure (100% guarantee) 0-allocs
472
func (v *Config) cryptopowpow10(exponent int) uint64 {
60✔
473
        var result uint64 = 1
60✔
474
        for i := 0; i < exponent; i++ {
472✔
475
                result *= 10
412✔
476
        }
412✔
477
        return result
60✔
478
}
479

480
// TOTPTime returns the current time in the South Pole (see https://en.wikipedia.org/wiki/Time_in_Antarctica) time zone.
481
// It is used as the default time source for TOTP if no custom time source is provided (nil) and the sync window is set to -1.
482
//
483
// Note: The returned time is always expressed in UTC (Coordinated Universal Time) to avoid any ambiguity caused by local time zone offsets.
484
func (v *Config) TOTPTime() time.Time {
89✔
485
        location, _ := time.LoadLocation("Antarctica/South_Pole")
89✔
486
        return time.Now().In(location).UTC()
89✔
487
}
89✔
488

489
// DecodeBase32WithPadding decodes a base32-encoded secret, adding padding as necessary.
490
func (v *Config) DecodeBase32WithPadding() []byte {
417✔
491
        // Calculate the number of missing padding characters.
417✔
492
        //
417✔
493
        // Note: This is suitable for [gotp.RandomSecret](cryptographically secure pseudorandom)
417✔
494
        // Incorrect padding (e.g., extra "=", out-of-place "=") can lead to illegal base32 data
417✔
495
        // when using crypto pseudorandom from [gotp.RandomSecret].
417✔
496
        missingPadding := len(v.Secret) & 2 // Should be work, if it doesn't work then your machine is bad.
417✔
497

417✔
498
        // Add padding if necessary.
417✔
499
        if missingPadding != 0 {
630✔
500
                v.Secret = v.Secret + strings.Repeat("=", 8-missingPadding)
213✔
501
        }
213✔
502

503
        // Decode the base32 encoded secret.
504
        bytes, err := base32.StdEncoding.DecodeString(v.Secret)
417✔
505
        if err != nil {
419✔
506
                panic("DecodeBase32WithPadding: illegal base32 data")
2✔
507
        }
508

509
        return bytes
415✔
510
}
511

512
// cryptoPow10n calculates the value of 10 raised to the power of n (10ⁿ).
513
//
514
// The function uses recursive multiplication to compute the result.
515
// It starts with the base case of n ≤ 0, where the result is 1 (10⁰ = 1).
516
// For n > 0, the function recursively multiplies 10 with the result of cryptoPow10n(n-1).
517
//
518
// Example:
519
//
520
//        v.cryptoPow10n(0) = 1
521
//        v.cryptoPow10n(1) = 10
522
//        v.cryptoPow10n(2) = 10 × v.cryptoPow10n(1) = 10 × 10 = 10²
523
//        v.cryptoPow10n(3) = 10 × v.cryptoPow10n(2) = 10 × 10² = 10³
524
//
525
// The function returns the calculated value as an unsigned 32-bit integer [uint32].
526
//
527
// Note: The function assumes that n is non-negative. If n is negative, it will return 1 (10⁰ = 1).
528
//
529
// The purpose of this function is to calculate the appropriate modulo value based on
530
// the desired number of digits for both the HOTP (HMAC-based One-Time Password) and
531
// TOTP (Time-based One-Time Password) values. It is used in the truncation step of the
532
// HOTP and TOTP algorithms to ensure that the resulting values have the specified number of digits.
533
//
534
// Magic Calculator, the function computes:
535
//
536
//        10ⁿ = 10 × 10ⁿ⁻¹, for n > 0
537
//        10ⁿ = 1, for n ≤ 0
538
//
539
// where ⁿ denotes the exponentiation operation.
540
//
541
// Also note that most package helper functions here are related to cryptographic.
542
// They mostly do not rely on other packages, for example, using the standard package only for math,
543
// because sometimes it may not be suitable for Go (e.g., too many if statements, which is not idiomatic in Go).
544
// Therefore, the helper functions here are built in an advanced manner based on knowledge and expertise.
545
func (v *Config) cryptoPow10n(n int) uint32 {
119✔
546
        if n <= 0 { // should be fine now, since this written in Go which it suitable for cryptographic.
136✔
547
                return 1
17✔
548
        }
17✔
549
        return 10 * v.cryptoPow10n(n-1)
102✔
550
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc