Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 126 additions & 6 deletions argon2/argon2.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import (
"io"
"strings"

"golang.org/x/crypto/argon2"

"github.com/zitadel/passwap/internal/salt"
"github.com/zitadel/passwap/verifier"
"golang.org/x/crypto/argon2"
)

// Argon2 identifiers
Expand All @@ -24,6 +25,16 @@ const (
Prefix = "$argon2"
)

// Validation defaults
const (
DefaultMinTime = 1
DefaultMaxTime = 10
DefaultMinMemory = 8 * 1024
DefaultMaxMemory = 512 * 1024
DefaultMinThreads = 1
DefaultMaxThreads = 16
)

// Params are used for all argon2 modes.
type Params struct {
Time uint32
Expand Down Expand Up @@ -126,6 +137,37 @@ func parse(encoded string) (*checker, error) {
return &c, nil
}

func (c *checker) validate(opts *ValidationOpts) error {
if c.Time < opts.MinTime || c.Time > opts.MaxTime {
return &verifier.BoundsError{
Algorithm: "argon2",
Param: "time",
Min: int(opts.MinTime),
Max: int(opts.MaxTime),
Actual: int(c.Time),
}
}
if c.Memory < opts.MinMemory || c.Memory > opts.MaxMemory {
return &verifier.BoundsError{
Algorithm: "argon2",
Param: "memory",
Min: int(opts.MinMemory),
Max: int(opts.MaxMemory),
Actual: int(c.Memory),
}
}
if c.Threads < opts.MinThreads || c.Threads > opts.MaxThreads {
return &verifier.BoundsError{
Algorithm: "argon2",
Param: "threads",
Min: int(opts.MinThreads),
Max: int(opts.MaxThreads),
Actual: int(c.Threads),
}
}
return nil
}

func (c *checker) verify(pw string) verifier.Result {
hash := c.hf([]byte(pw), c.salt, c.Time, c.Memory, c.Threads, c.KeyLen)
res := subtle.ConstantTimeCompare(hash, c.hash)
Expand All @@ -134,6 +176,7 @@ func (c *checker) verify(pw string) verifier.Result {
}

type Hasher struct {
opts *ValidationOpts
p Params
rand io.Reader
hf hashFunc
Expand All @@ -155,6 +198,18 @@ func (h *Hasher) Hash(password string) (string, error) {
), nil
}

func (h *Hasher) Validate(encoded string) (verifier.Result, error) {
c, err := parse(encoded)
if err != nil || c == nil {
return verifier.Skip, err
}
err = c.validate(h.opts)
if err != nil {
return verifier.Fail, err
}
return verifier.OK, nil
}

// Verify implements passwap.Verifier
func (h *Hasher) Verify(encoded, password string) (verifier.Result, error) {
c, err := parse(encoded)
Expand All @@ -174,26 +229,93 @@ func (h *Hasher) Verify(encoded, password string) (verifier.Result, error) {
return verifier.OK, nil
}

func NewArgon2i(p Params) *Hasher {
func NewArgon2i(p Params, opts *ValidationOpts) *Hasher {
p.id = Identifier_i

return &Hasher{
opts: checkValidationOpts(opts),
p: p,
rand: rand.Reader,
hf: argon2.Key,
}
}

func NewArgon2id(p Params) *Hasher {
func NewArgon2id(p Params, opts *ValidationOpts) *Hasher {
p.id = Identifier_id

return &Hasher{
opts: checkValidationOpts(opts),
p: p,
rand: rand.Reader,
hf: argon2.IDKey,
}
}

type ValidationOpts struct {
MinTime uint32
MaxTime uint32
MinMemory uint32
MaxMemory uint32
MinThreads uint8
MaxThreads uint8
}

var DefaultValidationOpts = &ValidationOpts{
MinTime: DefaultMinTime,
MaxTime: DefaultMaxTime,
MinMemory: DefaultMinMemory,
MaxMemory: DefaultMaxMemory,
MinThreads: DefaultMinThreads,
MaxThreads: DefaultMaxThreads,
}

func checkValidationOpts(opts *ValidationOpts) *ValidationOpts {
if opts == nil {
return DefaultValidationOpts
}
if opts.MinTime == 0 {
opts.MinTime = DefaultMinTime
}
if opts.MaxTime == 0 {
opts.MaxTime = DefaultMaxTime
}
if opts.MinMemory == 0 {
opts.MinMemory = DefaultMinMemory
}
if opts.MaxMemory == 0 {
opts.MaxMemory = DefaultMaxMemory
}
if opts.MinThreads == 0 {
opts.MinThreads = DefaultMinThreads
}
if opts.MaxThreads == 0 {
opts.MaxThreads = DefaultMaxThreads
}
return opts
}

type Verifier struct {
opts *ValidationOpts
}

func NewVerifier(opts *ValidationOpts) *Verifier {
return &Verifier{
opts: checkValidationOpts(opts),
}
}

func (v *Verifier) Validate(encoded string) (verifier.Result, error) {
c, err := parse(encoded)
if err != nil || c == nil {
return verifier.Skip, err
}
err = c.validate(v.opts)
if err != nil {
return verifier.Fail, err
}
return verifier.OK, nil
}

// Verify parses encoded and uses its argon2 parameters
// to verify password against its hash.
// Either the result of Fail or OK is returned,
Expand All @@ -203,13 +325,11 @@ func NewArgon2id(p Params) *Hasher {
// and therefore not by this package.
// ErrArgon2d is returned when an argon2d identifier is in
// the encoded string.
func Verify(encoded, password string) (verifier.Result, error) {
func (v *Verifier) Verify(encoded, password string) (verifier.Result, error) {
c, err := parse(encoded)
if err != nil || c == nil {
return verifier.Skip, err
}

return c.verify(password), nil
}

var Verifier = verifier.VerifyFunc(Verify)
Loading