Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to import user password credentials #1963

Closed
wants to merge 2 commits into from
Closed
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
7 changes: 6 additions & 1 deletion cmd/hashers/argon2/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package argon2
import (
"context"
"fmt"
"github.com/ory/kratos/hash"
"reflect"
"strings"

Expand Down Expand Up @@ -76,7 +77,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 +163,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
30 changes: 13 additions & 17 deletions driver/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"github.com/ory/kratos/hash"
"io"
"net"
"net/http"
Expand Down Expand Up @@ -169,19 +170,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 @@ -210,6 +201,7 @@ type (

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

Expand Down Expand Up @@ -389,10 +381,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 @@ -404,15 +396,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 @@ -1069,6 +1061,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
10 changes: 5 additions & 5 deletions driver/registry_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ func (m *RegistryDefault) Config(ctx context.Context) *config.Config {
return corp.ContextualizeConfig(ctx, m.c)
}

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 @@ -392,11 +396,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
43 changes: 41 additions & 2 deletions hash/hasher.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package hash

import "context"
import (
"context"
"github.com/inhies/go-bytesize"
"time"
)

// Hasher provides methods for generating and comparing password hashes.
type Hasher interface {
Expand All @@ -11,6 +15,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
12 changes: 4 additions & 8 deletions hash/hasher_bcrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,16 @@ package hash

import (
"context"

"github.com/ory/kratos/schema"

"github.com/ory/kratos/schema/errors"
"golang.org/x/crypto/bcrypt"

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

type Bcrypt struct {
c BcryptConfiguration
}

type BcryptConfiguration interface {
config.Provider
ConfigProvider
}

func NewHasherBcrypt(c BcryptConfiguration) *Bcrypt {
Expand All @@ -27,7 +23,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 +36,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