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
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v5

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.24'
check-latest: true
Expand Down
75 changes: 51 additions & 24 deletions consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ const (
WinHelloCTAPTransportTest
WinHelloCTAPTransportInternal
WinHelloCTAPTransportHybrid
WinHelloCTAPTransportFlagsMask WinHelloCOSEAlgorithm = 0x0000003F
WinHelloCTAPTransportSmartCard
WinHelloCTAPTransportFlagsMask WinHelloCOSEAlgorithm = 0x0000007F
)

type WinHelloUserVerification uint32
Expand Down Expand Up @@ -135,25 +136,30 @@ const (
)

type AuthenticatorGetAssertionOptions struct {
Timeout time.Duration
AuthenticatorAttachment WinHelloAuthenticatorAttachment
UserVerificationRequirement WinHelloUserVerificationRequirement
U2FAppID string
CancellationID *windows.GUID
CredentialLargeBlobOperation WinHelloCredentialLargeBlobOperation
CredentialLargeBlob []byte
BrowserInPrivateMode bool
AutoFill bool
JsonExt []byte
CredentialHints []webauthntypes.PublicKeyCredentialHint
Timeout time.Duration
AuthenticatorAttachment WinHelloAuthenticatorAttachment
UserVerificationRequirement WinHelloUserVerificationRequirement
U2FAppID string
CancellationID *windows.GUID
CredentialLargeBlobOperation WinHelloCredentialLargeBlobOperation
CredentialLargeBlob []byte
BrowserInPrivateMode bool
AutoFill bool
JsonExt []byte
CredentialHints []webauthntypes.PublicKeyCredentialHint
RemoteWebOrigin string
PublicKeyCredentialRequestOptionsJSON []byte
AuthenticatorID []byte
}

type WinHelloGetAssertionResponse struct {
*ctaptypes.AuthenticatorGetAssertionResponse
CredLargeBlob []byte
CredLargeBlobStatus WinHelloCredentialLargeBlobStatus
UsedTransport []webauthntypes.AuthenticatorTransport
hmacSecret *webauthntypes.AuthenticationExtensionsPRFValues
CredLargeBlob []byte
CredLargeBlobStatus WinHelloCredentialLargeBlobStatus
UsedTransport []webauthntypes.AuthenticatorTransport
ClientDataJSON []byte
AuthenticationResponseJSON []byte
hmacSecret *webauthntypes.AuthenticationExtensionsPRFValues
}

func (a *_WEBAUTHN_ASSERTION) ToGetAssertionResponse() (
Expand Down Expand Up @@ -185,22 +191,41 @@ func (a *_WEBAUTHN_ASSERTION) ToGetAssertionResponse() (

winHelloResp := &WinHelloGetAssertionResponse{
AuthenticatorGetAssertionResponse: resp,
CredLargeBlob: bytes.Clone(unsafe.Slice(a.PbCredLargeBlob, a.CbCredLargeBlob)),
CredLargeBlobStatus: WinHelloCredentialLargeBlobStatus(a.DwCredLargeBlobStatus),
UsedTransport: flagsToTransports(a.DwUsedTransport),
}

if a.PHmacSecret != nil {
if a.DwVersion >= 2 {
winHelloResp.CredLargeBlob = bytes.Clone(unsafe.Slice(a.PbCredLargeBlob, a.CbCredLargeBlob))
winHelloResp.CredLargeBlobStatus = WinHelloCredentialLargeBlobStatus(a.DwCredLargeBlobStatus)
}

if a.DwVersion >= 3 && a.PHmacSecret != nil {
winHelloResp.hmacSecret = &webauthntypes.AuthenticationExtensionsPRFValues{
First: bytes.Clone(unsafe.Slice(a.PHmacSecret.PbFirst, a.PHmacSecret.CbFirst)),
Second: bytes.Clone(unsafe.Slice(a.PHmacSecret.PbSecond, a.PHmacSecret.CbSecond)),
}
}

unsignedExtensionOutputsRaw := bytes.Clone(unsafe.Slice(a.PbUnsignedExtensionOutputs, a.CbUnsignedExtensionOutputs))
if unsignedExtensionOutputsRaw != nil && len(unsignedExtensionOutputsRaw) > 0 {
if err := cbor.Unmarshal(unsignedExtensionOutputsRaw, &resp.UnsignedExtensionOutputs); err != nil {
return nil, err
if a.DwVersion >= 4 {
winHelloResp.UsedTransport = flagsToTransports(a.DwUsedTransport)
}

if a.DwVersion >= 5 {
unsignedExtensionOutputsRaw := bytes.Clone(unsafe.Slice(a.PbUnsignedExtensionOutputs, a.CbUnsignedExtensionOutputs))
if unsignedExtensionOutputsRaw != nil && len(unsignedExtensionOutputsRaw) > 0 {
if err := cbor.Unmarshal(unsignedExtensionOutputsRaw, &resp.UnsignedExtensionOutputs); err != nil {
return nil, err
}
}
}

if a.DwVersion >= 6 {
clientDataJSONRaw := bytes.Clone(unsafe.Slice(a.PbClientDataJSON, a.CbClientDataJSON))
if clientDataJSONRaw != nil && len(clientDataJSONRaw) > 0 {
winHelloResp.ClientDataJSON = clientDataJSONRaw
}
authenticationResponseJSONRaw := bytes.Clone(unsafe.Slice(a.PbAuthenticationResponseJSON, a.CbAuthenticationResponseJSON))
if authenticationResponseJSONRaw != nil && len(authenticationResponseJSONRaw) > 0 {
winHelloResp.AuthenticationResponseJSON = authenticationResponseJSONRaw
}
}

Expand All @@ -220,6 +245,8 @@ func flagsToTransports(flags uint32) []webauthntypes.AuthenticatorTransport {
case flags&uint32(WinHelloCTAPTransportInternal) != 0:
tr = append(tr, webauthntypes.AuthenticatorTransportInternal)
case flags&uint32(WinHelloCTAPTransportHybrid) != 0:
case flags&uint32(WinHelloCTAPTransportSmartCard) != 0:
tr = append(tr, webauthntypes.AuthenticatorTransportSmartCard)
tr = append(tr, webauthntypes.AuthenticatorTransportHybrid)
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/goforj/godump v1.6.0
github.com/ldclabs/cose v1.3.2
github.com/stretchr/testify v1.11.1
golang.org/x/sys v0.35.0
golang.org/x/sys v0.38.0
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
3 changes: 3 additions & 0 deletions types_webauthn.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ type (
_WEBAUTHN_USER_ENTITY_INFORMATION C.WEBAUTHN_USER_ENTITY_INFORMATION
_WEBAUTHN_X5C C.WEBAUTHN_X5C
_CTAPCBOR_HYBRID_STORAGE_LINKED_DATA C.CTAPCBOR_HYBRID_STORAGE_LINKED_DATA
_WEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS C.WEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS
_WEBAUTHN_AUTHENTICATOR_DETAILS C.WEBAUTHN_AUTHENTICATOR_DETAILS
_WEBAUTHN_AUTHENTICATOR_DETAILS_LIST C.WEBAUTHN_AUTHENTICATOR_DETAILS_LIST
)

func int32ToBool(i int32) bool {
Expand Down
1 change: 1 addition & 0 deletions versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type currentVersion struct {
commonAttestation uint32
credentialAttestation uint32
assertion uint32
authenticatorDetails uint32
}

func availableVersions(ver uint32) *currentVersion {
Expand Down
112 changes: 89 additions & 23 deletions winhello.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,73 @@ import (
)

var (
modWebAuthn = windows.NewLazyDLL("webauthn.dll")
procWebAuthNAuthenticatorGetAssertion = modWebAuthn.NewProc("WebAuthNAuthenticatorGetAssertion")
procWebAuthNFreeAssertion = modWebAuthn.NewProc("WebAuthNFreeAssertion")
procWebAuthNGetApiVersionNumber = modWebAuthn.NewProc("WebAuthNGetApiVersionNumber")
currVer = availableVersions(APIVersionNumber())
ErrWindowsVersionNotSupported = errors.New("windows version not supported, requires Windows 10 1903 or later")
)

var (
modWebAuthn *windows.LazyDLL
procWebAuthNGetApiVersionNumber *windows.LazyProc
procWebAuthNIsUserVerifyingPlatformAuthenticatorAvailable *windows.LazyProc
procWebAuthNAuthenticatorMakeCredential *windows.LazyProc
procWebAuthNAuthenticatorGetAssertion *windows.LazyProc
procWebAuthNFreeCredentialAttestation *windows.LazyProc
procWebAuthNFreeAssertion *windows.LazyProc
procWebAuthNGetCancellationId *windows.LazyProc
procWebAuthNCancelCurrentOperation *windows.LazyProc
procWebAuthNGetPlatformCredentialList *windows.LazyProc
procWebAuthNFreePlatformCredentialList *windows.LazyProc
procWebAuthNDeletePlatformCredential *windows.LazyProc
procWebAuthNGetAuthenticatorList *windows.LazyProc
procWebAuthNFreeAuthenticatorList *windows.LazyProc
procWebAuthNGetErrorName *windows.LazyProc
procWebAuthNGetW3CExceptionDOMError *windows.LazyProc
currVer *currentVersion
InitError error
)

func init() {
modWebAuthn = windows.NewLazyDLL("webauthn.dll")

if err := modWebAuthn.Load(); err != nil {
InitError = ErrWindowsVersionNotSupported
return
}

procWebAuthNGetApiVersionNumber = modWebAuthn.NewProc("WebAuthNGetApiVersionNumber")
procWebAuthNIsUserVerifyingPlatformAuthenticatorAvailable = modWebAuthn.NewProc("WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable")
procWebAuthNAuthenticatorMakeCredential = modWebAuthn.NewProc("WebAuthNAuthenticatorMakeCredential")
procWebAuthNAuthenticatorGetAssertion = modWebAuthn.NewProc("WebAuthNAuthenticatorGetAssertion")
procWebAuthNFreeCredentialAttestation = modWebAuthn.NewProc("WebAuthNFreeCredentialAttestation")
procWebAuthNFreeAssertion = modWebAuthn.NewProc("WebAuthNFreeAssertion")
procWebAuthNGetCancellationId = modWebAuthn.NewProc("WebAuthNGetCancellationId")
procWebAuthNCancelCurrentOperation = modWebAuthn.NewProc("WebAuthNCancelCurrentOperation")
procWebAuthNGetPlatformCredentialList = modWebAuthn.NewProc("WebAuthNGetPlatformCredentialList")
procWebAuthNFreePlatformCredentialList = modWebAuthn.NewProc("WebAuthNFreePlatformCredentialList")
procWebAuthNDeletePlatformCredential = modWebAuthn.NewProc("WebAuthNDeletePlatformCredential")
procWebAuthNGetAuthenticatorList = modWebAuthn.NewProc("WebAuthNGetAuthenticatorList")
procWebAuthNFreeAuthenticatorList = modWebAuthn.NewProc("WebAuthNFreeAuthenticatorList")
procWebAuthNGetErrorName = modWebAuthn.NewProc("WebAuthNGetErrorName")
procWebAuthNGetW3CExceptionDOMError = modWebAuthn.NewProc("WebAuthNGetW3CExceptionDOMError")

apiVersion := APIVersionNumber()
if apiVersion == 0 {
InitError = ErrWindowsVersionNotSupported
return
}

currVer = availableVersions(apiVersion)
}

type WebAuthnCredentialDetails struct {
CredentialID []byte
RP webauthntypes.PublicKeyCredentialRpEntity
User webauthntypes.PublicKeyCredentialUserEntity
Removable bool
BackedUp bool
CredentialID []byte
RP webauthntypes.PublicKeyCredentialRpEntity
User webauthntypes.PublicKeyCredentialUserEntity
Removable bool
BackedUp bool
AuthenticatorName string
AuthenticatorLogo []byte
ThirdPartyPayment bool
Transports []webauthntypes.AuthenticatorTransport
}

func GetAssertion(
Expand All @@ -43,19 +97,23 @@ func GetAssertion(
}

opts := &_WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS{
DwVersion: currVer.authenticatorGetAssertionOptions,
DwTimeoutMilliseconds: uint32(winHelloOpts.Timeout.Milliseconds()),
CredentialList: _WEBAUTHN_CREDENTIALS{}, // basically deprecated, baseline supports pAllowCredentialList
DwAuthenticatorAttachment: uint32(winHelloOpts.AuthenticatorAttachment),
DwUserVerificationRequirement: uint32(winHelloOpts.UserVerificationRequirement),
DwFlags: 0, // user only in version 8 for PRF Global Eval
DwCredLargeBlobOperation: uint32(winHelloOpts.CredentialLargeBlobOperation),
CbCredLargeBlob: uint32(len(winHelloOpts.CredentialLargeBlob)),
PbCredLargeBlob: unsafe.SliceData(winHelloOpts.CredentialLargeBlob),
BBrowserInPrivateMode: boolToInt32(winHelloOpts.BrowserInPrivateMode),
BAutoFill: boolToInt32(winHelloOpts.AutoFill),
CbJsonExt: uint32(len(winHelloOpts.JsonExt)),
PbJsonExt: unsafe.SliceData(winHelloOpts.JsonExt),
DwVersion: currVer.authenticatorGetAssertionOptions,
DwTimeoutMilliseconds: uint32(winHelloOpts.Timeout.Milliseconds()),
CredentialList: _WEBAUTHN_CREDENTIALS{}, // basically deprecated, baseline supports pAllowCredentialList
DwAuthenticatorAttachment: uint32(winHelloOpts.AuthenticatorAttachment),
DwUserVerificationRequirement: uint32(winHelloOpts.UserVerificationRequirement),
DwFlags: 0, // user only in version 8 for PRF Global Eval
DwCredLargeBlobOperation: uint32(winHelloOpts.CredentialLargeBlobOperation),
CbCredLargeBlob: uint32(len(winHelloOpts.CredentialLargeBlob)),
PbCredLargeBlob: unsafe.SliceData(winHelloOpts.CredentialLargeBlob),
BBrowserInPrivateMode: boolToInt32(winHelloOpts.BrowserInPrivateMode),
BAutoFill: boolToInt32(winHelloOpts.AutoFill),
CbJsonExt: uint32(len(winHelloOpts.JsonExt)),
PbJsonExt: unsafe.SliceData(winHelloOpts.JsonExt),
CbPublicKeyCredentialRequestOptionsJSON: uint32(len(winHelloOpts.PublicKeyCredentialRequestOptionsJSON)),
PbPublicKeyCredentialRequestOptionsJSON: unsafe.SliceData(winHelloOpts.PublicKeyCredentialRequestOptionsJSON),
CbAuthenticatorId: uint32(len(winHelloOpts.AuthenticatorID)),
PbAuthenticatorId: unsafe.SliceData(winHelloOpts.AuthenticatorID),
}

credExList := make([]*_WEBAUTHN_CREDENTIAL_EX, len(allowList))
Expand Down Expand Up @@ -117,6 +175,10 @@ func GetAssertion(
opts.PpwszCredentialHints = unsafe.SliceData(credHints)
}

if winHelloOpts.RemoteWebOrigin != "" {
opts.PwszRemoteWebOrigin = windows.StringToUTF16Ptr(winHelloOpts.RemoteWebOrigin)
}

if extInputs != nil {
exts := make([]_WEBAUTHN_EXTENSION, 0)

Expand Down Expand Up @@ -258,6 +320,10 @@ func GetAssertion(
}

func APIVersionNumber() uint32 {
if procWebAuthNGetApiVersionNumber.Find() != nil {
return 0
}

r1, _, _ := procWebAuthNGetApiVersionNumber.Call()
return uint32(r1)
}
17 changes: 17 additions & 0 deletions ztypes_webauthn.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,23 @@ type (
WEncodedTunnelServerDomain uint16
Comment thread
ElectroNafta marked this conversation as resolved.
Pad_cgo_0 [6]byte
}
_WEBAUTHN_AUTHENTICATOR_DETAILS_OPTIONS struct {
DwVersion uint32
}
_WEBAUTHN_AUTHENTICATOR_DETAILS struct {
DwVersion uint32
CbAuthenticatorId uint32
PbAuthenticatorId *uint8
PwszAuthenticatorName *uint16
CbAuthenticatorLogo uint32
PbAuthenticatorLogo *uint8
BLocked int32
Pad_cgo_0 [4]byte
}
_WEBAUTHN_AUTHENTICATOR_DETAILS_LIST struct {
CAuthenticatorDetails uint32
PpAuthenticatorDetails **_WEBAUTHN_AUTHENTICATOR_DETAILS
}
)

func int32ToBool(i int32) bool {
Expand Down