snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 21:19:12 +05:00
commit c60cc7ca75
8092 changed files with 394160 additions and 0 deletions

162
hash/hash_limits.go Normal file
View File

@@ -0,0 +1,162 @@
// Copyright © 2025 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package hash
import (
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
)
// ErrHashParametersOutOfBounds is returned when an imported or stored hash
// declares cost parameters that exceed the bounds enforced by this package.
// The bounds exist to prevent attacker-controlled hash parameters from causing
// memory exhaustion (process OOM) or unbounded CPU consumption when the
// password comparator is invoked.
var ErrHashParametersOutOfBounds = errors.New("hash parameters exceed allowed bounds")
// Bounds for cost parameters embedded in imported password hashes. Each value
// is chosen to comfortably accommodate strong real-world configurations while
// bounding worst-case memory and CPU consumption when the comparator runs at
// login time.
const (
// maxScryptLogN bounds the firebase-scrypt ln parameter.
// firebase-scrypt computes N = 1 << ln. Memory is 128 * N * r bytes.
// Firebase's strongest documented profile is ln=14 (~16 MiB at r=8); 17
// leaves headroom for legacy configs while bounding allocation at
// ~128 MiB at r=8.
maxScryptLogN uint32 = 17
// maxScryptN bounds plain scrypt's N directly. OWASP's Password Storage
// Cheat Sheet recommends N=2^17 (131072) as the minimum for password
// hashing; no widely-deployed password-hash system documents a larger
// value. Higher N appears only in file-encryption tooling (tarsnap, age)
// where multi-second cost is acceptable. The Kratos default and RFC 7914's
// example both use N=16384 (2^14). Combined with the r=8 ceiling, this
// caps memory at 128 MiB per compare.
maxScryptN uint32 = 1 << 17
// maxScryptR bounds scrypt's r (block size). RFC 7914 specifies r=8
// verbatim; all five OWASP recommended scrypt configurations use r=8.
// No documented production password hash uses r > 8. With N=2^17 this
// caps memory at 128 MiB per compare.
maxScryptR uint32 = 8
// maxScryptP bounds scrypt's p (parallelization). p=1 is the standard
// default; OWASP's highest documented config uses p=10. Go's scrypt
// implementation runs p iterations serially, so p multiplies CPU
// linearly without any parallelism gain.
maxScryptP uint32 = 10
// maxArgon2MemoryKiB bounds argon2 memory at 1 GiB. Kratos default is
// 128 MiB; OWASP recommends 64 MiB. 1 GiB matches the existing
// Argon2DefaultDedicatedMemory ceiling in driver/config.
maxArgon2MemoryKiB uint32 = 1 << 20
// maxArgon2Iterations bounds argon2 t. RFC 9106 recommends t∈{1,3};
// OWASP configurations use up to t=5; libsodium SENSITIVE preset uses
// t=4. Bitwarden's user-configurable hard cap is 10, the highest
// documented production value.
maxArgon2Iterations uint32 = 10
// maxArgon2Parallelism bounds argon2 p. Default is 2*NumCPU; 16 covers
// up to 8-core hosts.
maxArgon2Parallelism uint8 = 16
// maxPbkdf2Iterations bounds PBKDF2 i. OWASP 2023 recommends 600k
// (SHA-256) / 210k (SHA-512). 10M ≈ 10× the strongest published
// recommendation and bounds CPU to a few seconds on modern hardware.
maxPbkdf2Iterations uint32 = 10_000_000
// maxBcryptCost bounds bcrypt cost. The format spec allows 4–31, but
// cost grows exponentially: cost 12 (Kratos, PHP, Django default) is
// ~250 ms; cost 14 (high-security guidance) is ~1 s; cost 15 (practical
// max for interactive use) is ~2 s; cost 17 is ~8 s. No mainstream
// platform defaults above cost 12.
maxBcryptCost = 15
)
func validateFirebaseScryptParams(logN, r, p uint32) error {
if logN > maxScryptLogN {
return errors.Wrapf(ErrHashParametersOutOfBounds, "firescrypt ln=%d exceeds max %d", logN, maxScryptLogN)
}
if r == 0 || r > maxScryptR {
return errors.Wrapf(ErrHashParametersOutOfBounds, "firescrypt r=%d not in [1, %d]", r, maxScryptR)
}
if p == 0 || p > maxScryptP {
return errors.Wrapf(ErrHashParametersOutOfBounds, "firescrypt p=%d not in [1, %d]", p, maxScryptP)
}
return nil
}
func validateScryptParams(n, r, p uint32) error {
if n == 0 || n > maxScryptN {
return errors.Wrapf(ErrHashParametersOutOfBounds, "scrypt N=%d not in [1, %d]", n, maxScryptN)
}
if r == 0 || r > maxScryptR {
return errors.Wrapf(ErrHashParametersOutOfBounds, "scrypt r=%d not in [1, %d]", r, maxScryptR)
}
if p == 0 || p > maxScryptP {
return errors.Wrapf(ErrHashParametersOutOfBounds, "scrypt p=%d not in [1, %d]", p, maxScryptP)
}
return nil
}
func validateArgon2Params(memoryKiB uint64, iterations uint32, parallelism uint8) error {
if memoryKiB == 0 || memoryKiB > uint64(maxArgon2MemoryKiB) {
return errors.Wrapf(ErrHashParametersOutOfBounds, "argon2 m=%d KiB not in [1, %d]", memoryKiB, maxArgon2MemoryKiB)
}
if iterations == 0 || iterations > maxArgon2Iterations {
return errors.Wrapf(ErrHashParametersOutOfBounds, "argon2 t=%d not in [1, %d]", iterations, maxArgon2Iterations)
}
if parallelism == 0 || parallelism > maxArgon2Parallelism {
return errors.Wrapf(ErrHashParametersOutOfBounds, "argon2 p=%d not in [1, %d]", parallelism, maxArgon2Parallelism)
}
return nil
}
func validatePbkdf2Params(iterations uint32) error {
if iterations == 0 || iterations > maxPbkdf2Iterations {
return errors.Wrapf(ErrHashParametersOutOfBounds, "pbkdf2 i=%d not in [1, %d]", iterations, maxPbkdf2Iterations)
}
return nil
}
func validateBcryptHashCost(hashed []byte) error {
cost, err := bcrypt.Cost(hashed)
if err != nil {
return err
}
if cost > maxBcryptCost {
return errors.Wrapf(ErrHashParametersOutOfBounds, "bcrypt cost=%d exceeds max %d", cost, maxBcryptCost)
}
return nil
}
// ValidateImportedHash performs cost-parameter bounds checking on a hash
// before it is persisted via the admin identity import API. It is the
// import-time counterpart to the bounds checks in the compare* paths and
// exists so we fail fast on malicious imports rather than persisting a hash
// that would crash the process at every login attempt.
//
// Hashers without attacker-controlled cost parameters (md5-crypt, sha-crypt,
// the static SHA/MD5/HMAC families, SSHA) pass through unchanged.
func ValidateImportedHash(hashed []byte) error {
switch {
case IsBcryptHash(hashed):
return validateBcryptHashCost(hashed)
case IsArgon2idHash(hashed), IsArgon2iHash(hashed):
_, _, _, err := decodeArgon2idHash(string(hashed))
return err
case IsPbkdf2Hash(hashed):
_, _, _, err := decodePbkdf2Hash(string(hashed))
return err
case IsScryptHash(hashed):
_, _, _, err := decodeScryptHash(string(hashed))
return err
case IsFirebaseScryptHash(hashed):
_, _, _, _, _, err := decodeFirebaseScryptHash(string(hashed))
return err
}
return nil
}