diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cbf10cb..cb3fbf1 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -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 diff --git a/consts.go b/consts.go index 986917a..23cda91 100644 --- a/consts.go +++ b/consts.go @@ -46,7 +46,8 @@ const ( WinHelloCTAPTransportTest WinHelloCTAPTransportInternal WinHelloCTAPTransportHybrid - WinHelloCTAPTransportFlagsMask WinHelloCOSEAlgorithm = 0x0000003F + WinHelloCTAPTransportSmartCard + WinHelloCTAPTransportFlagsMask WinHelloCOSEAlgorithm = 0x0000007F ) type WinHelloUserVerification uint32 @@ -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() ( @@ -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 } } @@ -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) } diff --git a/go.mod b/go.mod index 84fe92d..a3ef595 100644 --- a/go.mod +++ b/go.mod @@ -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 ( diff --git a/go.sum b/go.sum index b05428a..3c00305 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/types_webauthn.go b/types_webauthn.go index f014862..2050631 100644 --- a/types_webauthn.go +++ b/types_webauthn.go @@ -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 { diff --git a/versions.go b/versions.go index b89b6c7..775af1c 100644 --- a/versions.go +++ b/versions.go @@ -14,6 +14,7 @@ type currentVersion struct { commonAttestation uint32 credentialAttestation uint32 assertion uint32 + authenticatorDetails uint32 } func availableVersions(ver uint32) *currentVersion { diff --git a/winhello.go b/winhello.go index 7f1ae0f..3c7bd04 100644 --- a/winhello.go +++ b/winhello.go @@ -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( @@ -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)) @@ -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) @@ -258,6 +320,10 @@ func GetAssertion( } func APIVersionNumber() uint32 { + if procWebAuthNGetApiVersionNumber.Find() != nil { + return 0 + } + r1, _, _ := procWebAuthNGetApiVersionNumber.Call() return uint32(r1) } diff --git a/ztypes_webauthn.go b/ztypes_webauthn.go index 4d7203c..77cd436 100644 --- a/ztypes_webauthn.go +++ b/ztypes_webauthn.go @@ -261,6 +261,23 @@ type ( WEncodedTunnelServerDomain uint16 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 {