diff --git a/CHANGELOG.md b/CHANGELOG.md index e968e250d9b7..d258cdd832e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ **Table of Contents** -- [ (2024-12-03)](#2024-12-03) +- [ (2024-12-04)](#2024-12-04) - [Breaking Changes](#breaking-changes) - [Bug Fixes](#bug-fixes) - [Code Refactoring](#code-refactoring) @@ -339,7 +339,7 @@ -# [](https://github.com/ory/kratos/compare/v1.3.0...v) (2024-12-03) +# [](https://github.com/ory/kratos/compare/v1.3.0...v) (2024-12-04) ## Breaking Changes @@ -523,6 +523,15 @@ https://github.com/ory-corp/cloud/issues/7176 - Fast add credential type lookups ([#4177](https://github.com/ory/kratos/issues/4177)) ([eeb1355](https://github.com/ory/kratos/commit/eeb13552118504f17b48f2c7e002e777f5ee73f4)) +- Gracefully handle failing password rehashing during login + ([#4235](https://github.com/ory/kratos/issues/4235)) + ([3905787](https://github.com/ory/kratos/commit/39057879821b387b49f5d4f7cb19b9e02ec924a7)): + + This fixes an issue where we would successfully import long passwords (>72 + chars), but fail when the user attempts to login with the correct password + because we can't rehash it. In this case, we simply issue a warning to the + logs, keep the old hash intact, and continue logging in the user. + - Improve QueryForCredentials ([#4181](https://github.com/ory/kratos/issues/4181)) ([ca0d6a7](https://github.com/ory/kratos/commit/ca0d6a7ea717495429b8bac7fd843ac69c1ebf16)) diff --git a/SECURITY.md b/SECURITY.md index 026e3afb70f8..6104514805c4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,51 +3,54 @@ # Ory Security Policy -## Overview +This policy outlines Ory's security commitments and practices for users across +different licensing and deployment models. -This security policy outlines the security support commitments for different -types of Ory users. +To learn more about Ory's security service level agreements (SLAs) and +processes, please [contact us](https://www.ory.sh/contact/). -[Get in touch](https://www.ory.sh/contact/) to learn more about Ory's security -SLAs and process. - -## Apache 2.0 License Users +## Ory Network Users -- **Security SLA:** No security Service Level Agreement (SLA) is provided. -- **Release Schedule:** Releases are planned every 3 to 6 months. These releases - will contain all security fixes implemented up to that point. -- **Version Support:** Security patches are only provided for the current - release version. +- **Security SLA:** Ory addresses vulnerabilities in the Ory Network according + to the following guidelines: + - Critical: Typically addressed within 14 days. + - High: Typically addressed within 30 days. + - Medium: Typically addressed within 90 days. + - Low: Typically addressed within 180 days. + - Informational: Addressed as necessary. + These timelines are targets and may vary based on specific circumstances. +- **Release Schedule:** Updates are deployed to the Ory Network as + vulnerabilities are resolved. +- **Version Support:** The Ory Network always runs the latest version, ensuring + up-to-date security fixes. ## Ory Enterprise License Customers -- **Security SLA:** The following timelines apply for security vulnerabilities - based on their severity: - - Critical: Resolved within 14 days. - - High: Resolved within 30 days. - - Medium: Resolved within 90 days. - - Low: Resolved within 180 days. - - Informational: Addressed as needed. -- **Release Schedule:** Updates are provided as soon as vulnerabilities are - resolved, adhering to the above SLA. -- **Version Support:** Depending on the Ory Enterprise License agreement - multiple versions can be supported. +- **Security SLA:** Ory addresses vulnerabilities based on their severity: + - Critical: Typically addressed within 14 days. + - High: Typically addressed within 30 days. + - Medium: Typically addressed within 90 days. + - Low: Typically addressed within 180 days. + - Informational: Addressed as necessary. + These timelines are targets and may vary based on specific circumstances. +- **Release Schedule:** Updates are made available as vulnerabilities are + resolved. Ory works closely with enterprise customers to ensure timely updates + that align with their operational needs. +- **Version Support:** Ory may provide security support for multiple versions, + depending on the terms of the enterprise agreement. -## Ory Network Users +## Apache 2.0 License Users -- **Security SLA:** The following timelines apply for security vulnerabilities - based on their severity: - - Critical: Resolved within 14 days. - - High: Resolved within 30 days. - - Medium: Resolved within 90 days. - - Low: Resolved within 180 days. - - Informational: Addressed as needed. -- **Release Schedule:** Updates are automatically deployed to Ory Network as - soon as vulnerabilities are resolved, adhering to the above SLA. -- **Version Support:** Ory Network always runs the most current version. +- **Security SLA:** Ory does not provide a formal SLA for security issues under + the Apache 2.0 License. +- **Release Schedule:** Releases prioritize new functionality and include fixes + for known security vulnerabilities at the time of release. While major + releases typically occur one to two times per year, Ory does not guarantee a + fixed release schedule. +- **Version Support:** Security patches are only provided for the latest release + version. ## Reporting a Vulnerability -Please head over to our -[security policy](https://www.ory.sh/docs/ecosystem/security) to learn more -about reporting security vulnerabilities. +For details on how to report security vulnerabilities, visit our +[security policy documentation](https://www.ory.sh/docs/ecosystem/security). diff --git a/hash/hash_comparator.go b/hash/hash_comparator.go index ca23fc4abfd4..4c6007ec94ff 100644 --- a/hash/hash_comparator.go +++ b/hash/hash_comparator.go @@ -551,10 +551,11 @@ func compareCryptHelper(password []byte, hash string) error { return errors.WithStack(ErrMismatchedHashAndPassword) } +var regexSSHA = regexp.MustCompile(`\{([^}]*)\}`) + // decodeSSHAHash decodes SSHA[1|256|512] encoded password hash in usual {SSHA...} format. func decodeSSHAHash(encodedHash string) (hasher string, salt, hash []byte, err error) { - re := regexp.MustCompile(`\{([^}]*)\}`) - match := re.FindStringSubmatch(string(encodedHash)) + match := regexSSHA.FindStringSubmatch(string(encodedHash)) var index_of_salt_begin int var index_of_hash_begin int diff --git a/selfservice/strategy/password/login.go b/selfservice/strategy/password/login.go index cc4e658f863d..92eda3390076 100644 --- a/selfservice/strategy/password/login.go +++ b/selfservice/strategy/password/login.go @@ -112,7 +112,7 @@ func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow, if !s.d.Hasher(ctx).Understands([]byte(o.HashedPassword)) { if err := s.migratePasswordHash(ctx, i.ID, []byte(p.Password)); err != nil { - return nil, s.handleLoginError(r, f, p, err) + s.d.Logger().Warnf("Unable to migrate password hash for identity %s: %s Keeping existing password hash and continuing.", i.ID, err) } } } diff --git a/selfservice/strategy/password/login_test.go b/selfservice/strategy/password/login_test.go index c955bf7d8a20..79f82b9c45b2 100644 --- a/selfservice/strategy/password/login_test.go +++ b/selfservice/strategy/password/login_test.go @@ -6,13 +6,16 @@ package password_test import ( "bytes" "context" + "crypto/sha256" _ "embed" + "encoding/base64" "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "net/url" + "slices" "strings" "testing" "time" @@ -21,6 +24,7 @@ import ( configtesthelpers "github.com/ory/kratos/driver/config/testhelpers" + "github.com/ory/x/randx" "github.com/ory/x/snapshotx" "github.com/ory/kratos/driver" @@ -903,6 +907,63 @@ func TestCompleteLogin(t *testing.T) { assert.Equal(t, identifier, gjson.Get(body, "identity.traits.email").String(), "%s", body) }) + t.Run("suite=password rehashing degrades gracefully during login", func(t *testing.T) { + identifier := x.NewUUID().String() + "@google.com" + // pwd := "Kd9hUV4Xkcq87VSca6A4fq1iBijrMScBFhkpIPEwBtvTDsBwfqJCqXPPr4TkhOhsd9wFGeB3MzS4bJuesLCAjJc5s1GKJ51zW7F" + pwd := randx.MustString(100, randx.AlphaNum) // longer than bcrypt max length + require.Greater(t, len(pwd), 72) // bcrypt max length + salt := randx.MustString(32, randx.AlphaNum) + sha := sha256.Sum256([]byte(pwd + salt)) + hashed := "{SSHA256}" + base64.StdEncoding.EncodeToString(slices.Concat(sha[:], []byte(salt))) + iId := x.NewUUID() + require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(context.Background(), &identity.Identity{ + ID: iId, + SchemaID: "migration", + Traits: identity.Traits(fmt.Sprintf(`{"email":%q}`, identifier)), + Credentials: map[identity.CredentialsType]identity.Credentials{ + identity.CredentialsTypePassword: { + Type: identity.CredentialsTypePassword, + Identifiers: []string{identifier}, + Config: sqlxx.JSONRawMessage(`{"hashed_password":"` + hashed + `"}`), + }, + }, + VerifiableAddresses: []identity.VerifiableAddress{ + { + ID: x.NewUUID(), + Value: identifier, + Verified: true, + CreatedAt: time.Now(), + IdentityID: iId, + }, + }, + })) + + values := func(v url.Values) { + v.Set("identifier", identifier) + v.Set("method", identity.CredentialsTypePassword.String()) + v.Set("password", pwd) + } + + browserClient := testhelpers.NewClientWithCookies(t) + + body := testhelpers.SubmitLoginForm(t, false, browserClient, publicTS, values, + false, false, http.StatusOK, redirTS.URL) + + assert.Equal(t, identifier, gjson.Get(body, "identity.traits.email").String(), "%s", body) + + // check that the password hash algorithm is unchanged + _, c, err := reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(context.Background(), identity.CredentialsTypePassword, identifier) + require.NoError(t, err) + var o identity.CredentialsPassword + require.NoError(t, json.NewDecoder(bytes.NewBuffer(c.Config)).Decode(&o)) + assert.Equal(t, hashed, o.HashedPassword) + + // login still works + body = testhelpers.SubmitLoginForm(t, false, browserClient, publicTS, values, + false, true, http.StatusOK, redirTS.URL) + assert.Equal(t, identifier, gjson.Get(body, "identity.traits.email").String(), "%s", body) + }) + t.Run("suite=password migration hook", func(t *testing.T) { ctx := context.Background() @@ -948,7 +1009,8 @@ func TestCompleteLogin(t *testing.T) { require.NoError(t, reg.Config().Set(ctx, config.ViperKeyPasswordMigrationHook, map[string]any{ "config": map[string]any{"url": ts.URL}, - "enabled": true})) + "enabled": true, + })) for _, tc := range []struct { name string