diff --git a/docs/examples/auth/apikey/main.go b/docs/examples/auth/apikey/main.go index 30a79bcc..6e86a28b 100644 --- a/docs/examples/auth/apikey/main.go +++ b/docs/examples/auth/apikey/main.go @@ -11,6 +11,7 @@ package main import ( + "crypto/subtle" "log" "net/http" "os" @@ -40,7 +41,9 @@ func wireAPIKeyAuth() { api.RegisterAuth("key", security.APIKeyAuth( "X-Token", "header", func(token string) (any, error) { - if token == "abcdefuvwxyz" { + // Use subtle.ConstantTimeCompare to avoid leaking the + // expected token byte-by-byte via response timing. + if subtle.ConstantTimeCompare([]byte(token), []byte("abcdefuvwxyz")) == 1 { return "alice", nil } return nil, errors.New(http.StatusUnauthorized, "invalid api key") diff --git a/docs/examples/auth/basic/main.go b/docs/examples/auth/basic/main.go index a1ded2c5..fc0c238d 100644 --- a/docs/examples/auth/basic/main.go +++ b/docs/examples/auth/basic/main.go @@ -10,6 +10,7 @@ package main import ( "context" + "crypto/subtle" "net/http" "github.com/go-openapi/errors" @@ -33,7 +34,10 @@ type fakePrincipal struct{ Name string } type fakeStore struct{} func (fakeStore) AuthenticateBasic(_ context.Context, user, pass string) (*fakePrincipal, error) { - if user == "alice" && pass == "s3cret" { + // subtle.ConstantTimeCompare avoids leaking the expected password + // byte-by-byte via response timing. The username is non-secret and + // compared with `==` purely to short-circuit unknown accounts. + if user == "alice" && subtle.ConstantTimeCompare([]byte(pass), []byte("s3cret")) == 1 { return &fakePrincipal{Name: user}, nil } return nil, errors.Unauthenticated("basic") diff --git a/security/authenticator.go b/security/authenticator.go index 4c091018..2430997b 100644 --- a/security/authenticator.go +++ b/security/authenticator.go @@ -42,22 +42,42 @@ func ScopedAuthenticator(handler func(*ScopedAuthRequest) (bool, any, error)) ru }) } -// UserPassAuthentication authentication function. +// UserPassAuthentication validates a basic-auth credential. +// +// Implementations comparing the password (or any derived secret) against a +// known value MUST use [crypto/subtle.ConstantTimeCompare]: the runtime +// extracts the credential from the request and delegates the comparison +// here, and does not enforce a constant-time posture on the caller's behalf. type UserPassAuthentication func(string, string) (any, error) -// UserPassAuthenticationCtx authentication function with [context.Context]. +// UserPassAuthenticationCtx is the [context.Context]-aware variant of +// [UserPassAuthentication]. The same constant-time-comparison guidance +// applies. type UserPassAuthenticationCtx func(context.Context, string, string) (context.Context, any, error) -// TokenAuthentication authentication function. +// TokenAuthentication validates an API-key token. +// +// Implementations comparing the token against a known value MUST use +// [crypto/subtle.ConstantTimeCompare]; the runtime delegates the comparison +// here and does not enforce a constant-time posture on the caller's behalf. type TokenAuthentication func(string) (any, error) -// TokenAuthenticationCtx authentication function with [context.Context]. +// TokenAuthenticationCtx is the [context.Context]-aware variant of +// [TokenAuthentication]. The same constant-time-comparison guidance +// applies. type TokenAuthenticationCtx func(context.Context, string) (context.Context, any, error) -// ScopedTokenAuthentication authentication function. +// ScopedTokenAuthentication validates a bearer/OAuth2 token along with the +// scopes required for the operation. +// +// Implementations comparing the token against a known value MUST use +// [crypto/subtle.ConstantTimeCompare]; the runtime delegates the comparison +// here and does not enforce a constant-time posture on the caller's behalf. type ScopedTokenAuthentication func(string, []string) (any, error) -// ScopedTokenAuthenticationCtx authentication function with [context.Context]. +// ScopedTokenAuthenticationCtx is the [context.Context]-aware variant of +// [ScopedTokenAuthentication]. The same constant-time-comparison guidance +// applies. type ScopedTokenAuthenticationCtx func(context.Context, string, []string) (context.Context, any, error) var DefaultRealmName = "API"