Files
upstream-ory-kratos/x/webauthnx/signcount.go
upstream-mirror c60cc7ca75
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
snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:19:12 +05:00

67 lines
2.7 KiB
Go

// Copyright © 2025 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package webauthnx
import (
"context"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/ory/kratos/identity"
"github.com/ory/x/logrusx"
)
// SignCountDependencies is the minimal set of registry dependencies required to
// persist an updated WebAuthn signature counter after a login.
type SignCountDependencies interface {
logrusx.Provider
identity.PrivilegedPoolProvider
}
// UpdateSignCountAfterLogin writes the mutable authenticator state reported
// during a successful login back onto the stored credential: the signature
// counter, the clone warning, and the backup state.
//
// It implements the relying-party side of W3C WebAuthn signature-counter
// handling (https://www.w3.org/TR/webauthn-2/#sctn-sign-counter). When the
// go-webauthn library detects that the counter did not advance it sets
// CloneWarning, which we persist on the credential but deliberately do NOT act
// on to block the login:
//
// - Synced passkeys (Apple iCloud, Google) legitimately always report a
// counter of 0, so a hard "reject if not increasing" rule would lock out
// real users.
// - The user already proved possession of the authenticator, so a failure to
// persist the counter must not turn a successful login into an error.
//
// The clone warning is durably stored on the credential and is inspectable via
// the admin identity API, so there is no separate security log event.
//
// The write goes through UpdateCredentialsConfig, which re-reads the stored
// config under an exclusive row lock and applies UpdateFromLogin to the latest
// committed value, so concurrent logins of the same credential serialize
// instead of clobbering each other's counter. That lock lives in its own
// transaction and is deliberately NOT enrolled in the login request: the
// assertion has already succeeded, so a failed or slow counter write must never
// roll back or block the login. Persistence is therefore best-effort — errors
// are logged, not returned.
func UpdateSignCountAfterLogin(
ctx context.Context,
d SignCountDependencies,
i *identity.Identity,
credentialsType identity.CredentialsType,
validated *webauthn.Credential,
) {
err := d.PrivilegedIdentityPool().UpdateCredentialsConfig(ctx, i.ID, credentialsType,
identity.UpdateConfig(func(conf *identity.CredentialsWebAuthnConfig) error {
conf.Credentials.UpdateFromLogin(validated)
return nil
}),
)
if err != nil {
d.Logger().WithError(err).WithField("identity_id", i.ID).WithField("network_id", i.NID).
Warn("Unable to persist updated WebAuthn signature counter. The login succeeded but clone detection state was not persisted.")
}
}