Skip to content
Merged
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
6 changes: 6 additions & 0 deletions lib/auth/webauthnwin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/go-webauthn/webauthn/protocol/webauthncose"
"github.com/gravitational/trace"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client/proto"
wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes"
)
Expand Down Expand Up @@ -305,3 +306,8 @@ func Diag(ctx context.Context) (*DiagResult, error) {

return res, nil
}

// getPackageLogger returns a logger with component="WebAuthnWin".
func getPackageLogger() *slog.Logger {
return slog.With(teleport.ComponentKey, "WebAuthnWin")
}
17 changes: 6 additions & 11 deletions lib/auth/webauthnwin/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ func TestLogin(t *testing.T) {
},
}

const wantOptsVersion uint32 = 9

tests := []struct {
name string
origin string
Expand All @@ -191,10 +193,8 @@ func TestLogin(t *testing.T) {
origin: origin,
assertionIn: func() *wantypes.CredentialAssertion { return okAssertion },
assertFn: func(t *testing.T, car *wanpb.CredentialAssertionResponse, req *getAssertionRequest) {
assert.Equal(t, uint32(5), req.opts.dwVersion)

assert.Equal(t, wantOptsVersion, req.opts.dwVersion)
assert.Equal(t, webauthnUserVerificationDiscouraged, req.opts.dwUserVerificationRequirement)

assert.Equal(t, webauthnAttachmentAny, req.opts.dwAuthenticatorAttachment)
},
},
Expand All @@ -208,10 +208,8 @@ func TestLogin(t *testing.T) {
},
opts: LoginOpts{AuthenticatorAttachment: AttachmentPlatform},
assertFn: func(t *testing.T, car *wanpb.CredentialAssertionResponse, req *getAssertionRequest) {
assert.Equal(t, uint32(5), req.opts.dwVersion)

assert.Equal(t, wantOptsVersion, req.opts.dwVersion)
assert.Equal(t, webauthnUserVerificationRequired, req.opts.dwUserVerificationRequirement)

assert.Equal(t, webauthnAttachmentPlatform, req.opts.dwAuthenticatorAttachment)
},
},
Expand All @@ -225,10 +223,8 @@ func TestLogin(t *testing.T) {
},
opts: LoginOpts{AuthenticatorAttachment: AttachmentCrossPlatform},
assertFn: func(t *testing.T, car *wanpb.CredentialAssertionResponse, req *getAssertionRequest) {
assert.Equal(t, uint32(5), req.opts.dwVersion)

assert.Equal(t, wantOptsVersion, req.opts.dwVersion)
assert.Equal(t, webauthnUserVerificationPreferred, req.opts.dwUserVerificationRequirement)

assert.Equal(t, webauthnAttachmentCrossPlatform, req.opts.dwAuthenticatorAttachment)
},
},
Expand All @@ -242,8 +238,7 @@ func TestLogin(t *testing.T) {
},
opts: LoginOpts{AuthenticatorAttachment: AttachmentCrossPlatform},
assertFn: func(t *testing.T, car *wanpb.CredentialAssertionResponse, req *getAssertionRequest) {
assert.Equal(t, uint32(5), req.opts.dwVersion)

assert.Equal(t, wantOptsVersion, req.opts.dwVersion)
assert.Equal(t, webauthnUserVerificationDiscouraged, req.opts.dwUserVerificationRequirement)
},
},
Expand Down
34 changes: 31 additions & 3 deletions lib/auth/webauthnwin/conv.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package webauthnwin

import (
"context"
"encoding/json"
"strings"
"syscall"
Expand All @@ -37,25 +38,52 @@ func assertOptionsToCType(in wantypes.PublicKeyCredentialRequestOptions, loginOp
}

var dwAuthenticatorAttachment uint32
var credentialHint string
if loginOpts != nil {
switch loginOpts.AuthenticatorAttachment {
case AttachmentPlatform:
dwAuthenticatorAttachment = 1
dwAuthenticatorAttachment = webauthnAttachmentPlatform
getPackageLogger().DebugContext(context.Background(),
"Using platform attachment",
"attachment", dwAuthenticatorAttachment,
)
case AttachmentCrossPlatform:
dwAuthenticatorAttachment = 2
dwAuthenticatorAttachment = webauthnAttachmentCrossPlatform
// If we are setting cross-platform we imply a physical security-key (not
// a phone).
// See https://github.com/gravitational/teleport/issues/62060.
credentialHint = webauthnCredentialHintSecurityKey
getPackageLogger().DebugContext(context.Background(),
"Using cross-platform attachment with security-key hint",
"attachment", dwAuthenticatorAttachment,
"credential_hint", credentialHint,
)
}
}

var cCredentialHints uint32 // number of hints
var pCredentialHints *uint16 // pointer to array of NULL-terminated UTF-16 strings
if credentialHint != "" {
var err error
pCredentialHints, err = utf16PtrFromString(credentialHint)
if err != nil {
return nil, trace.Wrap(err)
}
cCredentialHints = 1
}

return &webauthnAuthenticatorGetAssertionOptions{
// https://github.com/microsoft/webauthn/blob/7ab979cc833bfab9a682ed51761309db57f56c8c/webauthn.h#L36-L97
// contains information about different versions.
// We can set newest version and it still works on older APIs.
dwVersion: 5,
dwVersion: 9,
dwTimeoutMilliseconds: uint32(in.Timeout),
dwAuthenticatorAttachment: dwAuthenticatorAttachment,
dwUserVerificationRequirement: userVerificationToCType(in.UserVerification),
// TODO(tobiaszheller): support U2fAppId.
pAllowCredentialList: allowCredList,
cCredentialHints: cCredentialHints,
ppwszCredentialHints: pCredentialHints,
}, nil
}

Expand Down
66 changes: 59 additions & 7 deletions lib/auth/webauthnwin/ctypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const (
webauthnUserVerificationRequired = uint32(1)
webauthnUserVerificationPreferred = uint32(2)
webauthnUserVerificationDiscouraged = uint32(3)

webauthnCredentialHintSecurityKey = "security-key"
webauthnCredentialHintClientDevice = "client-device"
webauthnCredentialHintHybrid = "hybrid"
)

type webauthnRPEntityInformation struct {
Expand Down Expand Up @@ -240,6 +244,7 @@ type webauthnClientData struct {
pwszHashAlgID *uint16
}

//nolint:unused // This type maps a C struct. We need to keep the layout consistent.
type webauthnAuthenticatorGetAssertionOptions struct {
dwVersion uint32
// Time that the operation is expected to complete within.
Expand Down Expand Up @@ -289,13 +294,60 @@ type webauthnAuthenticatorGetAssertionOptions struct {
//
// The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5
//
// This field is kept just to keep size of struct valid.
_ uint32
// Size of pbCredLargeBlob
// This field is kept just to keep size of struct valid.
_ uint32
// This field is kept just to keep size of struct valid.
_ *byte

_ uint32 // dwCredLargeBlobOperation
cbCredLargeBlob uint32
pbCredLargeBlob *byte

//
// The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6
//

// PRF values which will be converted into HMAC-SECRET values according to WebAuthn Spec.
_ uint32 // pHmacSecretSaltValues

// Optional. BrowserInPrivate Mode. Defaulting to FALSE.
bBrowserInPrivateMode bool

//
// The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7
//

// Deprecated
// Optional. Linked Device Connection Info.
_ uint32 // pLinkedDevice

// Optional. Allowlist MUST contain 1 credential applicable for Hybrid transport.
bAutoFill bool

// Size of pbJsonExt
cbJsonExt uint32
pbJsonExt *byte

//
// The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_8
//

// PublicKeyCredentialHints (https://w3c.github.io/webauthn/#enum-hints)
cCredentialHints uint32
ppwszCredentialHints *uint16

//
// The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_9
//
// https://github.com/microsoft/webauthn/blob/c3ed95fd7603441a0253c55c14e79239cb556a9f/webauthn.h#L958.
//

// Web Origin. For Remote Web App scenario.
pwszRemoteWebOrigin *uint16

// UTF-8 encoded JSON serialization of the PublicKeyCredentialRequestOptions.
cbPublicKeyCredentialRequestOptionsJSON uint32
pbPublicKeyCredentialRequestOptionsJSON *byte

// Authenticator ID got from WebAuthNGetAuthenticatorList API.
cbAuthenticatorID uint32
pbAuthenticatorID *byte
}

//nolint:unused // TODO: remove when linter runs on windows build tag
Expand Down
17 changes: 17 additions & 0 deletions lib/auth/webauthnwin/webauthn_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ func newNativeImpl() *nativeImpl {
hasCompileSupport: true,
}

// Do not hold onto this logger. It is created before tsh has a chance to
// initialize it properly.
logger := slog.With(teleport.ComponentKey, "WebAuthnWin")
ctx := context.Background()

Expand Down Expand Up @@ -115,6 +117,21 @@ func (n *nativeImpl) GetAssertion(origin string, in *getAssertionRequest) (*want
return nil, trace.Wrap(err)
}

logger := getPackageLogger()
logger.DebugContext(context.Background(), "WebAuthn.dll API version",
"version", n.webauthnAPIVersion,
)

if n.webauthnAPIVersion < int(in.opts.dwVersion) {
const legacyVersion = 5
in.opts.dwVersion = legacyVersion
logger.DebugContext(context.Background(),
"WebAuthn.dll too old, falling back to legacy version",
"api_version", n.webauthnAPIVersion,
"legacy_version", in.opts.dwVersion,
)
}

var out *webauthnAssertion
ret, err := webAuthNAuthenticatorGetAssertion(hwnd, in.rpID, in.clientData, in.opts, &out)
if ret != 0 {
Expand Down
Loading