authelia/internal/middlewares/timing_attack_delay.go

98 lines
2.1 KiB
Go
Raw Normal View History

package middlewares
import (
"math"
"math/big"
"sync"
"time"
2023-03-01 23:38:56 +00:00
"github.com/sirupsen/logrus"
"github.com/authelia/authelia/v4/internal/logging"
"github.com/authelia/authelia/v4/internal/random"
)
2023-03-01 23:38:56 +00:00
func NewTimingAttackDelayer(name string, delay, min, max time.Duration, history int) *TimingAttackDelay {
return &TimingAttackDelay{
log: logging.Logger().WithFields(map[string]any{"service": "timing attack delay", "name": name}),
random: random.NewCryptographical(),
2023-03-01 23:38:56 +00:00
mu: &sync.Mutex{},
2023-03-01 23:38:56 +00:00
authns: delayedAuthns(delay, history),
n: history,
i: 0,
2023-03-01 23:38:56 +00:00
msDelayMin: float64(min.Milliseconds()),
msDelayMax: big.NewInt(max.Milliseconds()),
}
2023-03-01 23:38:56 +00:00
}
2023-03-01 23:38:56 +00:00
type TimingAttackDelay struct {
log *logrus.Entry
random random.Provider
2023-03-01 23:38:56 +00:00
mu sync.Locker
2023-03-01 23:38:56 +00:00
authns []time.Duration
n int
i int
2023-03-01 23:38:56 +00:00
msDelayMin float64
msDelayMax *big.Int
}
2023-03-01 23:38:56 +00:00
func (m *TimingAttackDelay) Delay(successful bool, elapsed time.Duration) {
time.Sleep(m.actual(elapsed, m.avg(elapsed, successful), successful))
}
func (m *TimingAttackDelay) actual(elapsed time.Duration, avg float64, successful bool) time.Duration {
additional, err := m.random.IntErr(m.msDelayMax)
if err != nil {
return time.Millisecond * time.Duration(avg+float64(m.msDelayMax.Int64()))
}
total := math.Max(avg, m.msDelayMin) + float64(additional.Int64())
actual := math.Max(total-float64(elapsed.Milliseconds()), 1.0)
m.log.WithFields(map[string]any{
"successful": successful,
"elapsed": elapsed.Milliseconds(),
"average": avg,
"random": additional.Int64(),
"total": total,
"actual": actual,
}).Trace("Delaying to Prevent Timing Attacks")
return time.Millisecond * time.Duration(actual)
}
2023-03-01 23:38:56 +00:00
func (m *TimingAttackDelay) avg(elapsed time.Duration, successful bool) float64 {
var sum int64
2023-03-01 23:38:56 +00:00
m.mu.Lock()
for _, authn := range m.authns {
sum += authn.Milliseconds()
}
if successful {
2023-03-01 23:38:56 +00:00
m.authns[m.i] = elapsed
m.i = (m.i + 1) % m.n
}
2023-03-01 23:38:56 +00:00
m.mu.Unlock()
2023-03-01 23:38:56 +00:00
return float64(sum / int64(m.n))
}
2023-03-01 23:38:56 +00:00
func delayedAuthns(delay time.Duration, history int) []time.Duration {
s := make([]time.Duration, history)
2023-03-01 23:38:56 +00:00
for i := range s {
s[i] = delay
}
2023-03-01 23:38:56 +00:00
return s
}