Skip to content

Commit

Permalink
Add ability to import user password credentials and removed dependenc…
Browse files Browse the repository at this point in the history
…y to driver/config from hash
  • Loading branch information
Sam Gardner authored and samcgardner committed Jan 31, 2022
1 parent 9421fbc commit 60e65f4
Show file tree
Hide file tree
Showing 49 changed files with 705 additions and 141 deletions.
8 changes: 7 additions & 1 deletion cmd/hashers/argon2/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"reflect"
"strings"

"github.com/ory/kratos/hash"

"github.com/spf13/cobra"
"github.com/spf13/pflag"

Expand Down Expand Up @@ -76,7 +78,7 @@ func configProvider(cmd *cobra.Command, flagConf *argon2Config) (*argon2Config,
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unable to initialize the config provider: %s\n", err)
return nil, cmdx.FailSilently(cmd)
}
conf.localConfig = *conf.config.HasherArgon2()
conf.localConfig = (config.Argon2)(*conf.config.HasherArgon2())

if cmd.Flags().Changed(FlagIterations) {
conf.localConfig.Iterations = flagConf.localConfig.Iterations
Expand Down Expand Up @@ -162,6 +164,10 @@ func (c *argon2Config) Config(_ context.Context) *config.Config {
return c.config
}

func (c *argon2Config) HashConfig(ctx context.Context) hash.HashConfigProvider {
return c.Config(ctx)
}

func (c *argon2Config) HasherArgon2() (*config.Argon2, error) {
if c.localConfig.Memory == 0 {
c.localConfig.Memory = config.Argon2DefaultMemory
Expand Down
31 changes: 14 additions & 17 deletions driver/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"testing"
"time"

"github.com/ory/kratos/hash"

"golang.org/x/net/publicsuffix"

"github.com/duo-labs/webauthn/protocol"
Expand Down Expand Up @@ -172,19 +174,9 @@ const (
const DefaultSessionCookieName = "ory_kratos_session"

type (
Argon2 struct {
Memory bytesize.ByteSize `json:"memory"`
Iterations uint32 `json:"iterations"`
Parallelism uint8 `json:"parallelism"`
SaltLength uint32 `json:"salt_length"`
KeyLength uint32 `json:"key_length"`
ExpectedDuration time.Duration `json:"expected_duration"`
ExpectedDeviation time.Duration `json:"expected_deviation"`
DedicatedMemory bytesize.ByteSize `json:"dedicated_memory"`
}
Bcrypt struct {
Cost uint32 `json:"cost"`
}
Argon2 hash.Argon2Config
Bcrypt hash.BcryptConfig

SelfServiceHook struct {
Name string `json:"hook"`
Config json.RawMessage `json:"config"`
Expand Down Expand Up @@ -215,6 +207,7 @@ type (

Provider interface {
Config(ctx context.Context) *Config
hash.ConfigProvider
}
)

Expand Down Expand Up @@ -394,10 +387,10 @@ func (p *Config) SessionName() string {
return stringsx.Coalesce(p.p.String(ViperKeySessionName), DefaultSessionCookieName)
}

func (p *Config) HasherArgon2() *Argon2 {
func (p *Config) HasherArgon2() *hash.Argon2Config {
// warn about usage of default values and point to the docs
// warning will require https://github.com/ory/viper/issues/19
return &Argon2{
return &hash.Argon2Config{
Memory: p.p.ByteSizeF(ViperKeyHasherArgon2ConfigMemory, Argon2DefaultMemory),
Iterations: uint32(p.p.IntF(ViperKeyHasherArgon2ConfigIterations, int(Argon2DefaultIterations))),
Parallelism: uint8(p.p.IntF(ViperKeyHasherArgon2ConfigParallelism, int(Argon2DefaultParallelism))),
Expand All @@ -409,15 +402,15 @@ func (p *Config) HasherArgon2() *Argon2 {
}
}

func (p *Config) HasherBcrypt() *Bcrypt {
func (p *Config) HasherBcrypt() *hash.BcryptConfig {
// warn about usage of default values and point to the docs
// warning will require https://github.com/ory/viper/issues/19
cost := uint32(p.p.IntF(ViperKeyHasherBcryptCost, int(BcryptDefaultCost)))
if !p.IsInsecureDevMode() && cost < BcryptDefaultCost {
cost = BcryptDefaultCost
}

return &Bcrypt{Cost: cost}
return &hash.BcryptConfig{Cost: cost}
}

func (p *Config) listenOn(key string) string {
Expand Down Expand Up @@ -1040,6 +1033,10 @@ func (p *Config) HasherPasswordHashingAlgorithm() string {
}
}

func (p *Config) Hasher(provider hash.ConfigProvider) hash.Hasher {
return hash.NewHasher(p.HasherPasswordHashingAlgorithm(), provider)
}

func (p *Config) CipherAlgorithm() string {
configValue := p.p.StringF(ViperKeyCipherAlgorithm, DefaultCipherAlgorithm)
switch configValue {
Expand Down
2 changes: 1 addition & 1 deletion driver/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ type Registry interface {
errorx.HandlerProvider
errorx.PersistenceProvider

hash.HashProvider
hash.Generator

identity.HandlerProvider
identity.ValidationProvider
Expand Down
14 changes: 6 additions & 8 deletions driver/registry_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import (
"sync"
"time"

"github.com/gobuffalo/pop/v6"
"github.com/hashicorp/go-retryablehttp"

"github.com/ory/x/httpx"

"github.com/gobuffalo/pop/v6"

"github.com/ory/nosurf"

"github.com/ory/kratos/selfservice/strategy/webauthn"
Expand Down Expand Up @@ -271,6 +269,10 @@ func (m *RegistryDefault) SMTPConfig(ctx context.Context) courier.SMTPConfig {
return m.Config(ctx)
}

func (m *RegistryDefault) HashConfig(ctx context.Context) hash.HashConfigProvider {
return m.Config(ctx)
}

func (m *RegistryDefault) selfServiceStrategies() []interface{} {
if len(m.selfserviceStrategies) == 0 {
m.selfserviceStrategies = []interface{}{
Expand Down Expand Up @@ -404,11 +406,7 @@ func (m *RegistryDefault) Cipher() cipher.Cipher {

func (m *RegistryDefault) Hasher() hash.Hasher {
if m.passwordHasher == nil {
if m.c.HasherPasswordHashingAlgorithm() == "bcrypt" {
m.passwordHasher = hash.NewHasherBcrypt(m)
} else {
m.passwordHasher = hash.NewHasherArgon2(m)
}
m.passwordHasher = m.c.Hasher(m)
}
return m.passwordHasher
}
Expand Down
6 changes: 2 additions & 4 deletions hash/hash_comparator.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import (
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"

"github.com/ory/kratos/driver/config"
)

var ErrUnknownHashAlgorithm = errors.New("unknown hash algorithm")
Expand Down Expand Up @@ -102,7 +100,7 @@ func IsPbkdf2Hash(hash []byte) bool {
return isPbkdf2Hash.Match(hash)
}

func decodeArgon2idHash(encodedHash string) (p *config.Argon2, salt, hash []byte, err error) {
func decodeArgon2idHash(encodedHash string) (p *Argon2Config, salt, hash []byte, err error) {
parts := strings.Split(encodedHash, "$")
if len(parts) != 6 {
return nil, nil, nil, ErrInvalidHash
Expand All @@ -117,7 +115,7 @@ func decodeArgon2idHash(encodedHash string) (p *config.Argon2, salt, hash []byte
return nil, nil, nil, ErrIncompatibleVersion
}

p = new(config.Argon2)
p = new(Argon2Config)
_, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &p.Memory, &p.Iterations, &p.Parallelism)
if err != nil {
return nil, nil, nil, err
Expand Down
44 changes: 42 additions & 2 deletions hash/hasher.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package hash

import "context"
import (
"context"
"time"

"github.com/inhies/go-bytesize"
)

// Hasher provides methods for generating and comparing password hashes.
type Hasher interface {
Expand All @@ -11,6 +16,41 @@ type Hasher interface {
Understands(hash []byte) bool
}

type HashProvider interface {
// Generator is the interface that objects that can construct hashers must implement
type Generator interface {
Hasher() Hasher
}

// HashConfigProvider is the interface that objects that can generate configuration for the implemented hashers must
// implement
type HashConfigProvider interface {
HasherBcrypt() *BcryptConfig
HasherArgon2() *Argon2Config
}

type BcryptConfig struct {
Cost uint32 `json:"cost"`
}

type Argon2Config struct {
Memory bytesize.ByteSize `json:"memory"`
Iterations uint32 `json:"iterations"`
Parallelism uint8 `json:"parallelism"`
SaltLength uint32 `json:"salt_length"`
KeyLength uint32 `json:"key_length"`
ExpectedDuration time.Duration `json:"expected_duration"`
ExpectedDeviation time.Duration `json:"expected_deviation"`
DedicatedMemory bytesize.ByteSize `json:"dedicated_memory"`
}

type ConfigProvider interface {
HashConfig(ctx context.Context) HashConfigProvider
}

func NewHasher(algorithm string, provider ConfigProvider) Hasher {
if algorithm == "bcrypt" {
return NewHasherBcrypt(provider)
} else {
return NewHasherArgon2(provider)
}
}
6 changes: 2 additions & 4 deletions hash/hasher_argon2.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import (

"github.com/pkg/errors"
"golang.org/x/crypto/argon2"

"github.com/ory/kratos/driver/config"
)

var (
Expand All @@ -26,7 +24,7 @@ type Argon2 struct {
}

type Argon2Configuration interface {
config.Provider
ConfigProvider
}

func NewHasherArgon2(c Argon2Configuration) *Argon2 {
Expand All @@ -38,7 +36,7 @@ func toKB(mem bytesize.ByteSize) uint32 {
}

func (h *Argon2) Generate(ctx context.Context, password []byte) ([]byte, error) {
p := h.c.Config(ctx).HasherArgon2()
p := h.c.HashConfig(ctx).HasherArgon2()

salt := make([]byte, p.SaltLength)
if _, err := rand.Read(salt); err != nil {
Expand Down
10 changes: 4 additions & 6 deletions hash/hasher_bcrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@ package hash
import (
"context"

"github.com/ory/kratos/schema"

"golang.org/x/crypto/bcrypt"

"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/schema/errors"
)

type Bcrypt struct {
c BcryptConfiguration
}

type BcryptConfiguration interface {
config.Provider
ConfigProvider
}

func NewHasherBcrypt(c BcryptConfiguration) *Bcrypt {
Expand All @@ -27,7 +25,7 @@ func (h *Bcrypt) Generate(ctx context.Context, password []byte) ([]byte, error)
return nil, err
}

hash, err := bcrypt.GenerateFromPassword(password, int(h.c.Config(ctx).HasherBcrypt().Cost))
hash, err := bcrypt.GenerateFromPassword(password, int(h.c.HashConfig(ctx).HasherBcrypt().Cost))
if err != nil {
return nil, err
}
Expand All @@ -40,7 +38,7 @@ func validateBcryptPasswordLength(password []byte) error {
// so if password is longer than 72 bytes, function returns an error
// See https://en.wikipedia.org/wiki/Bcrypt#User_input
if len(password) > 72 {
return schema.NewPasswordPolicyViolationError(
return errors.NewPasswordPolicyViolationError(
"#/password",
"passwords are limited to a maximum length of 72 characters",
)
Expand Down
Loading

0 comments on commit 60e65f4

Please sign in to comment.