diff --git a/go.mod b/go.mod index 8f83270d10e8e72a22c652d5cf334ac52a97d21d..0717e9c076b9c0937874aa8e903aca16731ed4b0 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21.0 toolchain go1.22.1 require ( - git.autistici.org/ai3/go-common v0.0.0-20241017171051-880a2c5ae7f4 + git.autistici.org/ai3/go-common v0.0.0-20250125130542-62b40adde91d git.autistici.org/id/usermetadb v0.0.0-20241017171915-b5c24a0ff9b7 github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 github.com/cenkalti/backoff/v4 v4.3.0 diff --git a/go.sum b/go.sum index 55e715abac33f0613fa8e607bce07bb5c1643e61..58c8561f6ba59137d4522981e885b45371b4f545 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= git.autistici.org/ai3/go-common v0.0.0-20241017171051-880a2c5ae7f4 h1:xB5K4GL4VlguEOknhgz+AN3k8nmx19y91RRUvByaLnQ= git.autistici.org/ai3/go-common v0.0.0-20241017171051-880a2c5ae7f4/go.mod h1:JpPfOTGgbvAElF0wdmv81y6l3CRsdS4z3IkNOETUDVo= +git.autistici.org/ai3/go-common v0.0.0-20250125130542-62b40adde91d h1:20u44cuDBH0xxPojMsDt0j9x+8FGZhifOuNe9zwoqos= +git.autistici.org/ai3/go-common v0.0.0-20250125130542-62b40adde91d/go.mod h1:JpPfOTGgbvAElF0wdmv81y6l3CRsdS4z3IkNOETUDVo= git.autistici.org/id/usermetadb v0.0.0-20240906122220-42ab91c67c5c h1:se5XxGQN0CBmbfcBCa+oP9SXXMXQy7ovI/BApx0Mz/M= git.autistici.org/id/usermetadb v0.0.0-20240906122220-42ab91c67c5c/go.mod h1:gu8lZEoo9XWy6wzPRaXP5Gy/tIRKqYW6zbKdyqT9I8M= git.autistici.org/id/usermetadb v0.0.0-20241017171915-b5c24a0ff9b7 h1:9rOxoXT+dARc56YrRV1g4CQsM1K1f+sT8ofoZ5YoO2M= diff --git a/vendor/git.autistici.org/ai3/go-common/pwhash/argon2.go b/vendor/git.autistici.org/ai3/go-common/pwhash/argon2.go index fef03caa0c001faace0d16123b23e53e5f0626ec..5c53eec3226176203ef843fec265efa5191c7bfa 100644 --- a/vendor/git.autistici.org/ai3/go-common/pwhash/argon2.go +++ b/vendor/git.autistici.org/ai3/go-common/pwhash/argon2.go @@ -6,15 +6,15 @@ import ( "encoding/hex" "errors" "fmt" - "log" "strconv" "strings" "golang.org/x/crypto/argon2" ) -var ( - argonKeyLen uint32 = 32 +const ( + argonLegacyKeySize = 32 + argonDefaultKeySize = 16 argonSaltLen = 16 ) @@ -29,9 +29,11 @@ type argon2PasswordHash struct { // newArgon2PasswordHash returns an Argon2i-based PasswordHash using the // specified parameters for time, memory, and number of threads. -func newArgon2PasswordHash(time, mem uint32, threads uint8, codec argon2Codec) PasswordHash { +func newArgon2PasswordHash(kind string, keySize int, time, mem uint32, threads uint8, codec argon2Codec) PasswordHash { return &argon2PasswordHash{ params: argon2Params{ + KeySize: keySize, + Kind: kind, Time: time, Memory: mem, Threads: threads, @@ -41,8 +43,8 @@ func newArgon2PasswordHash(time, mem uint32, threads uint8, codec argon2Codec) P } // NewArgon2 returns an Argon2i-based PasswordHash using the default parameters. -func NewArgon2() PasswordHash { - return NewArgon2WithParams( +func NewArgon2Legacy() PasswordHash { + return NewArgon2LegacyWithParams( defaultArgon2Params.Time, defaultArgon2Params.Memory, defaultArgon2Params.Threads, @@ -51,8 +53,8 @@ func NewArgon2() PasswordHash { // NewArgon2WithParams returns an Argon2i-based PasswordHash using the // specified parameters for time, memory, and number of threads. -func NewArgon2WithParams(time, mem uint32, threads uint8) PasswordHash { - return newArgon2PasswordHash(time, mem, threads, &a2Codec{}) +func NewArgon2LegacyWithParams(time, mem uint32, threads uint8) PasswordHash { + return newArgon2PasswordHash(kindArgon2I, argonLegacyKeySize, time, mem, threads, &a2LegacyCodec{}) } // NewArgon2Std returns an Argon2i-based PasswordHash that conforms @@ -65,12 +67,12 @@ func NewArgon2Std() PasswordHash { ) } -// NewArgon2StdWithParams returns an Argon2i-based PasswordHash using +// NewArgon2StdWithParams returns an Argon2id-based PasswordHash using // the specified parameters for time, memory, and number of -// threads. This will use the string encoding ("$argon2$") documented +// threads. This will use the string encoding ("$argon2id$") documented // in the argon2 reference implementation. func NewArgon2StdWithParams(time, mem uint32, threads uint8) PasswordHash { - return newArgon2PasswordHash(time, mem, threads, &argon2StdCodec{}) + return newArgon2PasswordHash(kindArgon2ID, argonDefaultKeySize, time, mem, threads, &argon2StdCodec{}) } // ComparePassword returns true if the given password matches the @@ -80,28 +82,53 @@ func (s *argon2PasswordHash) ComparePassword(encrypted, password string) bool { if err != nil { return false } - dk2 := argon2.Key([]byte(password), salt, params.Time, params.Memory, params.Threads, argonKeyLen) + + dk2 := params.hash(password, salt) return subtle.ConstantTimeCompare(dk, dk2) == 1 } // Encrypt the given password with the Argon2 algorithm. func (s *argon2PasswordHash) Encrypt(password string) string { salt := getRandomBytes(argonSaltLen) - dk := argon2.Key([]byte(password), salt, s.params.Time, s.params.Memory, s.params.Threads, argonKeyLen) + dk := s.params.hash(password, salt) return s.codec.encodeArgon2Hash(s.params, salt, dk) } +const ( + kindArgon2I = "argon2i" + kindArgon2ID = "argon2id" +) + type argon2Params struct { + Kind string + KeySize int Time uint32 Memory uint32 Threads uint8 } +func (p argon2Params) hash(password string, salt []byte) []byte { + if p.KeySize == 0 { + panic("key size is 0") + } + + switch p.Kind { + case kindArgon2I: + return argon2.Key([]byte(password), salt, p.Time, p.Memory, p.Threads, uint32(p.KeySize)) + case kindArgon2ID: + return argon2.IDKey([]byte(password), salt, p.Time, p.Memory, p.Threads, uint32(p.KeySize)) + default: + panic("unknown argon2 hash kind") + } +} + // Default Argon2 parameters are tuned for a high-traffic // authentication service (<1ms per operation). var defaultArgon2Params = argon2Params{ + Kind: kindArgon2ID, + KeySize: 16, Time: 1, - Memory: 4 * 1024, + Memory: 64 * 1024, Threads: 4, } @@ -110,13 +137,14 @@ type argon2Codec interface { decodeArgon2Hash(string) (argon2Params, []byte, []byte, error) } -type a2Codec struct{} +// Argon2i legacy encoding, do not use. +type a2LegacyCodec struct{} -func (*a2Codec) encodeArgon2Hash(params argon2Params, salt, dk []byte) string { +func (*a2LegacyCodec) encodeArgon2Hash(params argon2Params, salt, dk []byte) string { return fmt.Sprintf("$a2$%d$%d$%d$%x$%x", params.Time, params.Memory, params.Threads, salt, dk) } -func (*a2Codec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) { +func (*a2LegacyCodec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) { if !strings.HasPrefix(s, "$a2$") { err = errors.New("not an Argon2 password hash") return @@ -128,6 +156,8 @@ func (*a2Codec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk return } + params.Kind = kindArgon2I + var i uint64 if i, err = strconv.ParseUint(parts[0], 10, 32); err != nil { @@ -149,16 +179,36 @@ func (*a2Codec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk if err != nil { return } + dk, err = hex.DecodeString(parts[4]) + if err != nil { + return + } + + params.KeySize = len(dk) + switch len(dk) { + case 16, 24, 32: + default: + err = errors.New("bad key size") + } + return } +// Standard Argon2 encoding as per the reference implementation in +// https://github.com/P-H-C/phc-winner-argon2/blob/4ac8640c2adc1257677d27d3f833c8d1ee68c7d2/src/encoding.c#L242-L252 type argon2StdCodec struct{} +const argon2HashVersionStr = "v=19" + func (*argon2StdCodec) encodeArgon2Hash(params argon2Params, salt, dk []byte) string { encSalt := base64.RawStdEncoding.EncodeToString(salt) encDK := base64.RawStdEncoding.EncodeToString(dk) - return fmt.Sprintf("$argon2i$v=19$m=%d,t=%d,p=%d$%s$%s", params.Memory, params.Time, params.Threads, encSalt, encDK) + return fmt.Sprintf( + "$%s$%s$m=%d,t=%d,p=%d$%s$%s", + params.Kind, argon2HashVersionStr, + params.Memory, params.Time, params.Threads, + encSalt, encDK) } func parseArgon2HashParams(s string) (params argon2Params, err error) { @@ -182,7 +232,7 @@ func parseArgon2HashParams(s string) (params argon2Params, err error) { i, err = strconv.ParseUint(kv[1], 10, 8) params.Threads = uint8(i) default: - err = errors.New("unknown parameter in hash") + err = fmt.Errorf("unknown parameter '%s' in hash", kv[0]) } if err != nil { return @@ -192,30 +242,46 @@ func parseArgon2HashParams(s string) (params argon2Params, err error) { } func (*argon2StdCodec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) { - if !strings.HasPrefix(s, "$argon2i$") { + var kind string + switch { + case strings.HasPrefix(s, "$argon2i$"): + kind = kindArgon2I + case strings.HasPrefix(s, "$argon2id$"): + kind = kindArgon2ID + default: err = errors.New("not an Argon2 password hash") return } - parts := strings.SplitN(s[9:], "$", 4) - if len(parts) != 4 { + parts := strings.SplitN(s, "$", 6) + if len(parts) != 6 { err = errors.New("bad encoding") return } - if parts[0] != "v=19" { + if parts[2] != argon2HashVersionStr { err = errors.New("bad argon2 hash version") return } - params, err = parseArgon2HashParams(parts[1]) + params, err = parseArgon2HashParams(parts[3]) if err != nil { return } - if salt, err = base64.RawStdEncoding.DecodeString(parts[2]); err != nil { + params.Kind = kind + + if salt, err = base64.RawStdEncoding.DecodeString(parts[4]); err != nil { + return + } + if dk, err = base64.RawStdEncoding.DecodeString(parts[5]); err != nil { return } - dk, err = base64.RawStdEncoding.DecodeString(parts[3]) - log.Printf("params: %+v", params) + params.KeySize = len(dk) + switch len(dk) { + case 16, 24, 32: + default: + err = errors.New("bad key size") + } + return } diff --git a/vendor/git.autistici.org/ai3/go-common/pwhash/password.go b/vendor/git.autistici.org/ai3/go-common/pwhash/password.go index 07b093d412991e5bb07f78e9e400b94aad6ae60a..dee3839d76f930773ee0d787077bc3f96a7cffd5 100644 --- a/vendor/git.autistici.org/ai3/go-common/pwhash/password.go +++ b/vendor/git.autistici.org/ai3/go-common/pwhash/password.go @@ -4,7 +4,7 @@ // The format is the well-known dollar-separated field string, // extended with optional algorithm-specific parameters: // -// $id[$params...]$salt$encrypted +// $id[$params...]$salt$encrypted // // We extend 'id' beyond the values supported by the libc crypt(3) // function with the following hashing algorithms: @@ -16,9 +16,8 @@ // the parameterized benchmarks are named with // time/memory(MB)/threads. For nicer results: // -// go test -bench=Argon2 -run=none . 2>&1 | \ -// awk '/^Bench/ {ops=1000000000 / $3; print $1 " " ops " ops/sec"}' -// +// go test -bench=Argon2 -run=none . 2>&1 | \ +// awk '/^Bench/ {ops=1000000000 / $3; print $1 " " ops " ops/sec"}' package pwhash import ( @@ -49,12 +48,13 @@ func getRandomBytes(n int) []byte { // A registry of default handlers for decoding passwords. var prefixRegistry = map[string]PasswordHash{ - "$1$": NewSystemCrypt(), - "$5$": NewSystemCrypt(), - "$6$": NewSystemCrypt(), - "$s$": NewScrypt(), - "$a2$": NewArgon2(), - "$argon2i$": NewArgon2Std(), + "$1$": NewSystemCrypt(), + "$5$": NewSystemCrypt(), + "$6$": NewSystemCrypt(), + "$s$": NewScrypt(), + "$a2$": NewArgon2Legacy(), + "$argon2i$": NewArgon2Std(), + "$argon2id$": NewArgon2Std(), } // ComparePassword returns true if the given password matches the @@ -65,6 +65,7 @@ func ComparePassword(encrypted, password string) bool { return h.ComparePassword(encrypted, password) } } + return false } @@ -73,7 +74,7 @@ func ComparePassword(encrypted, password string) bool { var DefaultEncryptAlgorithm PasswordHash func init() { - DefaultEncryptAlgorithm = NewArgon2() + DefaultEncryptAlgorithm = NewArgon2Std() } // Encrypt will encrypt a password with the default algorithm. diff --git a/vendor/modules.txt b/vendor/modules.txt index 573f2b2f99afa17fccb8fed11abcbcb368ce95bc..a18d74a11d1bc3ea496f17ad25717317b2c625c7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2,7 +2,7 @@ ## explicit; go 1.20 filippo.io/edwards25519 filippo.io/edwards25519/field -# git.autistici.org/ai3/go-common v0.0.0-20241017171051-880a2c5ae7f4 +# git.autistici.org/ai3/go-common v0.0.0-20250125130542-62b40adde91d ## explicit; go 1.21.0 git.autistici.org/ai3/go-common git.autistici.org/ai3/go-common/clientutil