├── interact ├── app.go ├── doc.go ├── scan.go └── choose.go ├── go.mod ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── cmd ├── getlist.go ├── base.go ├── getassert.go ├── makecerd.go ├── deletecred.go └── showcert.go ├── api ├── share │ ├── GetCredentialsOpt.go │ ├── x5c.go │ ├── COSECredential.go │ ├── ClientData.go │ ├── CredentialEx.go │ ├── Extensions.go │ ├── Credential.go │ ├── RpInfo.go │ ├── Hmac.go │ ├── UserInfo.go │ ├── AuthenticatorData.go │ ├── CredentialDetail.go │ ├── MakeCredentialOpt.go │ ├── GetAssertionOpt.go │ ├── CommonAttestation.go │ ├── Assertion.go │ └── Attestation.go ├── hresult │ └── hresult.go ├── cose │ └── cose.go ├── utils │ └── utils.go ├── warp.go ├── define │ └── define.go ├── api.go └── raw │ └── api.go ├── go.sum ├── README.md ├── LICENSE └── main.go /interact/app.go: -------------------------------------------------------------------------------- 1 | package interact 2 | 3 | type Element interface { 4 | Do() error 5 | } 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Ink-33/authn 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/fxamacker/cbor/v2 v2.4.0 7 | github.com/google/uuid v1.3.0 8 | golang.org/x/sys v0.11.0 9 | ) 10 | 11 | require github.com/x448/float16 v0.8.4 // indirect 12 | -------------------------------------------------------------------------------- /interact/doc.go: -------------------------------------------------------------------------------- 1 | // Package interact provides implementations for some basic interaction 2 | // that aims to make cli program a bit more pretty and let it easy to use. 3 | // 4 | // This package will be splitted to a standalone repository in the future. 5 | package interact 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ -------------------------------------------------------------------------------- /cmd/getlist.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/Ink-33/authn/api" 5 | ) 6 | 7 | func GetPlatformCredList(c *api.WebAuthNClient) (func(), error) { 8 | printCallAPI() 9 | res, err := c.GetPlatformCredentialList("-") 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | return func() { printCredList(res) }, nil 15 | } 16 | -------------------------------------------------------------------------------- /api/share/GetCredentialsOpt.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | // RawGetCredentialsOptions for WebAuthNGetPlatformCredentialList API 4 | type RawGetCredentialsOptions struct { 5 | // Version of this structure, to allow for modifications in the future. 6 | Version uint32 // DWORD dwVersion 7 | 8 | // Optional. 9 | RPID *uint16 // LPCWSTR pwszRpId 10 | 11 | // Optional. BrowserInPrivate Mode. Defaulting to FALSE. 12 | BrowserInPrivateMode bool // BOOL bBrowserInPrivateMode 13 | } 14 | -------------------------------------------------------------------------------- /api/share/x5c.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import "github.com/Ink-33/authn/api/utils" 4 | 5 | // RawX5C is the X.509 certificate chain. 6 | type RawX5C struct { 7 | // Length of X.509 encoded certificate 8 | DataLen uint32 // DWORD cbData; 9 | // X.509 encoded certificate bytes 10 | // _Field_size_bytes_(cbData) 11 | DataPtr *byte // PBYTE pbData; 12 | } 13 | 14 | // X5C is the X.509 certificate chain. 15 | type X5C []byte 16 | 17 | func (c *RawX5C) DeRaw() X5C { 18 | if c == nil { 19 | return nil 20 | } 21 | return utils.BytesBuilder(c.DataPtr, c.DataLen) 22 | } 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= 2 | github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= 3 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 5 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 6 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 7 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= 8 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 9 | -------------------------------------------------------------------------------- /interact/scan.go: -------------------------------------------------------------------------------- 1 | package interact 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | // ScanInputAndCheck scans user input with check. 9 | // 10 | // It returns -1 if input is not a number or is less than 0. 11 | // 12 | // If nothing is input, return -2 13 | func ScanInputAndCheck() int { 14 | in := "" 15 | fmt.Print("> ") 16 | fmt.Scanln(&in) 17 | println() 18 | if in == "" { 19 | return -2 20 | } 21 | op, err := strconv.Atoi(in) 22 | if err != nil { 23 | return -1 24 | } 25 | if op < 0 { 26 | return -1 27 | } 28 | return op 29 | } 30 | 31 | // ScantoSting scans user input to string. 32 | func ScantoSting() string { 33 | input := "" 34 | fmt.Scanln(&input) 35 | return input 36 | } 37 | -------------------------------------------------------------------------------- /api/share/COSECredential.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | // RawCOSECredentialParameters is the information about credential parameters. 4 | type RawCOSECredentialParameters struct { 5 | CredentialParametersLen uint32 // DWORD cCredentialParameters; 6 | // _Field_size_(cCredentialParameters) 7 | CredentialParameters *RawCOSECredentialParameter // PWEBAUTHN_COSE_CREDENTIAL_PARAMETER pCredentialParameters; 8 | } 9 | 10 | // RawCOSECredentialParameter is the information about credential parameter. 11 | type RawCOSECredentialParameter struct { 12 | // Version of this structure, to allow for modifications in the future. 13 | Version uint32 // DWORD dwVersion; 14 | 15 | // Well-known credential type specifying a credential to create. 16 | CredentialType *uint16 // LPCWSTR pwszCredentialType 17 | 18 | // Well-known COSE algorithm specifying the algorithm to use for the credential. 19 | Alg int32 // LONG lAlg 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # authn 2 | 3 | > WIP 4 | 5 | A Windows WebAuthN API wrapper with both human-friendly and low-level API provided. 6 | 7 | Target `WebAuthN API` version: 4 8 | 9 | ## Usage 10 | 11 | Here is a simple cli demo showing some basic functions of `WebAuthN`. 12 | 13 | [Relying Party ID](https://w3c.github.io/webauthn/#rp-id) `go.webauthn.demo.app` will be used in this demo. 14 | 15 | **Note**: No information will be uploaded. All operations are processed locally. 16 | 17 | ``` 18 | PS C:\Users\ink33\authn> .\authn.exe 19 | Cli tool version: dev 20 | WebAuthN API Version: 4 21 | Is User Verifying Platform Authenticator Available: true 22 | 23 | Select operation: 24 | 25 | (1): Make Credential 26 | (2): Show Authenticator Attestation Certificate 27 | (3): Get Assertion 28 | (4): Get Platform Credential List 29 | (5): Delete Platform Credential 30 | 31 | (0): Exit 32 | 33 | > 34 | ``` 35 | 36 | New functions will coming soon. 37 | -------------------------------------------------------------------------------- /api/share/ClientData.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | // RawCollectedClientData is the information about client data. 4 | type RawCollectedClientData struct { 5 | // Version of this structure, to allow for modifications in the future. 6 | // This field is required and should be set to CURRENT_VERSION above. 7 | Version uint32 // DWORD dwVersion 8 | 9 | // Size of the pbClientDataJSON field. 10 | ClientDataJSONLen uint32 // DWORD cbClientDataJSON 11 | // UTF-8 encoded JSON serialization of the client data. 12 | // _Field_size_bytes_ (cbClientDataJSON) 13 | ClientDataJSONPtr *byte // PBYTE pbClientDataJSON 14 | 15 | // Hash algorithm ID used to hash the pbClientDataJSON field. 16 | HashAlgID *uint16 // LPCWSTR pwszHashAlgId 17 | } 18 | 19 | // RawCollectedClient is the information about client data json. 20 | type RawCollectedClient struct { 21 | Type string `json:"type"` 22 | Challenge string `json:"challenge"` 23 | Origin string `json:"origin"` 24 | } 25 | -------------------------------------------------------------------------------- /cmd/base.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | 7 | "github.com/Ink-33/authn/api/share" 8 | ) 9 | 10 | // Version ... 11 | var Version = "dev" 12 | 13 | type testUser struct { 14 | id []byte 15 | } 16 | 17 | func (user *testUser) GetID() []byte { 18 | return user.id 19 | } 20 | 21 | func (user *testUser) GetName() string { 22 | return "test@example.com" 23 | } 24 | 25 | func (user *testUser) GetDisplayName() string { 26 | return "Test User" 27 | } 28 | 29 | func (user *testUser) GetIcon() string { 30 | return "" 31 | } 32 | 33 | func printCallAPI() { 34 | fmt.Println("Calling win32 api ...") 35 | } 36 | 37 | func printCredList(list []*share.CredentialDetails) { 38 | for i, v := range list { 39 | fmt.Printf("[%v]\tCredID:\t%v\n\tUser:\t%v[%v]\n\tRP:\t%v[%v]\n\tRemovable:\t%v\n\n", 40 | i, 41 | base64.RawURLEncoding.EncodeToString(v.CredentialID), 42 | v.UserInformation.Name, 43 | v.UserInformation.DisplayName, 44 | v.RPInformation.Name, 45 | v.RPInformation.ID, 46 | v.Removable, 47 | ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /api/share/CredentialEx.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | // RawCredentialEX is the information about credential with extra information, such as, dwTransports 4 | // 5 | // _WEBAUTHN_CREDENTIAL_EX 6 | type RawCredentialEX struct { 7 | // Version of this structure, to allow for modifications in the future. 8 | Version uint32 // DWORD dwVersion; 9 | 10 | // Size of pbID. 11 | IDLen uint32 // DWORD cbId; 12 | // Unique ID for this particular credential. 13 | // _Field_size_bytes_(cbId) 14 | InPtr *byte // PBYTE pbId; 15 | 16 | // Well-known credential type specifying what this particular credential is. 17 | CredentialType *uint16 // LPCWSTR pwszCredentialType; 18 | 19 | // Transports. 0 implies no transport restrictions. 20 | Transports uint32 // DWORD dwTransports; 21 | } 22 | 23 | // RawCredentialList is the information about credential list with extra information 24 | // 25 | // _WEBAUTHN_CREDENTIAL_LIST 26 | type RawCredentialList struct { 27 | Credentials uint32 // DWORD cCredentials; 28 | // _Field_size_ (cCredentials) 29 | CredentialsPtr *RawCredentialEX // PWEBAUTHN_CREDENTIAL_EX *ppCredentials 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ink33 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | env: 6 | COMMIT_ID: "${{ github.sha }}" 7 | PR_PROMPT: "::warning:: Build artifact will not be uploaded due to the workflow is trigged by pull request." 8 | 9 | jobs: 10 | build: 11 | name: Build binary CI 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Setup Go environment 16 | uses: actions/setup-go@v4.1.0 17 | with: 18 | go-version: 1.19 19 | - name: Cache downloaded module 20 | uses: actions/cache@v3 21 | with: 22 | path: | 23 | ~/.cache/go-build 24 | ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-windows-amd64-${{ hashFiles('**/go.sum') }} 26 | - name: Build binary file 27 | env: 28 | GOOS: windows 29 | GOARCH: amd64 30 | IS_PR: ${{ !!github.head_ref }} 31 | run: | 32 | export CGO_ENABLED=0 33 | export LD_FLAGS="-w -s -X github.com/Ink-33/authn/cmd.Version=${COMMIT_ID::7}" 34 | go build -o "output/authn.exe" -trimpath -ldflags "$LD_FLAGS" . 35 | - name: Upload artifact 36 | uses: actions/upload-artifact@v3 37 | if: ${{ !github.head_ref }} 38 | with: 39 | name: authn 40 | path: output/ 41 | -------------------------------------------------------------------------------- /api/hresult/hresult.go: -------------------------------------------------------------------------------- 1 | package hresult 2 | 3 | // HResult defines error message 4 | type HResult uintptr 5 | 6 | func (e HResult) Error() string { 7 | switch e { 8 | case 0: 9 | return "Ok" 10 | case 0x80070032: 11 | return "NotSupported" // Win32: The request is not supported 12 | case 0x800704C7: 13 | return "Canceled" // Win32: The operation was canceled by the user 14 | case 0x800705B4: 15 | return "Timeout" // Win32: This operation returned because the timeout period expired 16 | case 0x8009000F: 17 | return "NteExists" // Object already exists 18 | case 0x80090011: 19 | return "NteNotFound" // Object was not found 20 | case 0x80090016: 21 | return "NteBadKeyset" // Keyset does not exist (Windows Hello not active) 22 | case 0x80090023: 23 | return "NteTokenKeysetStorageFull" // The security token does not have storage space available for an additional container 24 | case 0x80090027: 25 | return "NteInvalidParameter" // The parameter is incorrect 26 | case 0x80090029: 27 | return "NteNotSupported" // The requested operation is not supported 28 | case 0x80090035: 29 | return "NteDeviceNotFound" // The device that is required by this cryptographic provider is not found on this platform 30 | case 0x80090036: 31 | return "NteUserCanceled" // The action was cancelled by the user 32 | default: 33 | return "UnknownError" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Ink-33/authn/api" 7 | "github.com/Ink-33/authn/api/raw" 8 | "github.com/Ink-33/authn/cmd" 9 | "github.com/Ink-33/authn/interact" 10 | ) 11 | 12 | func main() { 13 | fmt.Printf("Cli tool version: %v\n", cmd.Version) 14 | fmt.Printf("WebAuthN API Version: %v\n", raw.GetAPIVersionNumber()) 15 | fmt.Printf("Is User Verifying Platform Authenticator Available: %v\n\n", raw.IsUserVerifyingPlatformAuthenticatorAvailable()) 16 | c := api.NewClient("go.webauthn.demo.app", "WebAuthN From Golang", "") 17 | 18 | choices := interact.Choose{ 19 | Title: "Select operation:", 20 | Choices: []interact.Choice{ 21 | interact.NewChoice( 22 | "Make Credential", 23 | func() (func(), error) { return cmd.MakeCred(c) }, 24 | ), 25 | interact.NewChoice( 26 | "Show Authenticator Attestation Certificate", 27 | func() (func(), error) { return cmd.ShowCertInfo(c) }, 28 | ), 29 | interact.NewChoice( 30 | "Get Assertion", 31 | func() (func(), error) { return cmd.GetAssertion(c) }, 32 | ), 33 | interact.NewChoice( 34 | "Get Platform Credential List", 35 | func() (func(), error) { return cmd.GetPlatformCredList(c) }, 36 | ), 37 | interact.NewChoice( 38 | "Delete Platform Credential", 39 | func() (func(), error) { return cmd.DeletePlatformCred(c) }, 40 | ), 41 | }, 42 | Loop: true, 43 | } 44 | _, err := choices.Do() 45 | if err != nil { 46 | panic(err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/share/Extensions.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "github.com/Ink-33/authn/api/utils" 7 | ) 8 | 9 | // RawExtensions contains a list of extensions 10 | type RawExtensions struct { 11 | ExtensionsLen uint32 // DWORD cExtensions; 12 | // _Field_size_(cExtensions) 13 | ExtensionsPrt *RawExtension // PWEBAUTHN_EXTENSION pExtensions; 14 | } 15 | 16 | // RawExtension contains information about an extension. 17 | type RawExtension struct { 18 | Identifier *uint16 // LPCWSTR pwszExtensionIdentifier 19 | ID uint32 // DWORD cbExtension 20 | Ptr uintptr // PVOID pvExtension 21 | } 22 | 23 | // Extensions contains a list of extensions 24 | type Extensions []*Extension 25 | 26 | // Extension contains information about an extension. 27 | type Extension struct { 28 | Identifier string 29 | ID uint32 30 | // TODO 31 | Ptr unsafe.Pointer 32 | } 33 | 34 | func (c *RawExtensions) DeRaw() Extensions { 35 | if c == nil || c.ExtensionsLen == 0 { 36 | return Extensions{} 37 | } 38 | raw := unsafe.Slice(c.ExtensionsPrt, c.ExtensionsLen) 39 | exts := make(Extensions, c.ExtensionsLen) 40 | for i := 0; i < int(c.ExtensionsLen); i++ { 41 | exts[i] = raw[i].DeRaw() 42 | } 43 | return exts 44 | } 45 | 46 | func (c *RawExtension) DeRaw() *Extension { 47 | if c == nil { 48 | return nil 49 | } 50 | return &Extension{ 51 | Identifier: utils.UTF16PtrtoString(c.Identifier), 52 | ID: c.ID, 53 | Ptr: nil, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /cmd/getassert.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "fmt" 7 | 8 | "github.com/Ink-33/authn/api" 9 | ) 10 | 11 | func GetAssertion(c *api.WebAuthNClient) (func(), error) { 12 | id := make([]byte, 32) 13 | _, _ = rand.Read(id) 14 | 15 | printCallAPI() 16 | b, err := c.GetAssertion("local://demo.app", nil) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | return func() { 22 | fmt.Printf("RPID Hash: %v\n", 23 | base64.RawURLEncoding.EncodeToString(b.AuthenticatorData.RPIDHash[:])) 24 | 25 | fmt.Printf("User Present: %v\n", b.AuthenticatorData.Flags.UserPresent) 26 | fmt.Printf("RFU1: %v\n", b.AuthenticatorData.Flags.RFU1) 27 | fmt.Printf("User Verified: %v\n", b.AuthenticatorData.Flags.UserVerified) 28 | fmt.Printf("Backup Eligibility: %v\n", b.AuthenticatorData.Flags.BackupEligibility) 29 | fmt.Printf("Backup State: %v\n", b.AuthenticatorData.Flags.BackupState) 30 | fmt.Printf("RFU2: %v\n", b.AuthenticatorData.Flags.RFU2) 31 | fmt.Printf("Attested credential data included: %v\n", b.AuthenticatorData.Flags.AttestedCredentialData) 32 | fmt.Printf("Extension data included: %v\n", b.AuthenticatorData.Flags.ExtensionData) 33 | 34 | fmt.Printf("UserID: %v\n", 35 | base64.RawURLEncoding.EncodeToString(b.UserID)) 36 | fmt.Printf("CredentialID: %v\n", 37 | base64.RawURLEncoding.EncodeToString(b.Credential.ID)) 38 | fmt.Printf("Signature: %v\n", 39 | base64.RawURLEncoding.EncodeToString(b.Signature)) 40 | 41 | fmt.Printf("Sign Counter: %v\n", b.AuthenticatorData.SignCounter) 42 | 43 | }, nil 44 | } 45 | -------------------------------------------------------------------------------- /api/share/Credential.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "github.com/Ink-33/authn/api/utils" 5 | ) 6 | 7 | // RawCredential is the information about credential. 8 | // 9 | // _WEBAUTHN_CREDENTIAL 10 | type RawCredential struct { 11 | // Version of this structure, to allow for modifications in the future. 12 | Version uint32 // DWORD dwVersion; 13 | 14 | // Size of pbID. 15 | IDLen uint32 // DWORD cbId; 16 | // Unique ID for this particular credential. 17 | // _Field_size_bytes_(cbId) 18 | IDPtr *byte // PBYTE pbId; 19 | 20 | // Well-known credential type specifying what this particular credential is. 21 | CredentialType *uint16 // LPCWSTR pwszCredentialType; 22 | } 23 | 24 | // RawCredentials is the information about credentials. 25 | // 26 | // _WEBAUTHN_CREDENTIALS 27 | type RawCredentials struct { 28 | CredentialsLen uint32 // DWORD cCredentials; 29 | // _Field_size_(cCredentials) 30 | CredentialsPtr *RawCredential // PWEBAUTHN_CREDENTIAL pCredentials; 31 | } 32 | 33 | // Credential is the information about credential. 34 | type Credential struct { 35 | // Version of this structure, to allow for modifications in the future. 36 | Version uint32 // DWORD dwVersion; 37 | 38 | // Unique ID for this particular credential. 39 | ID []byte 40 | 41 | // Well-known credential type specifying what this particular credential is. 42 | CredentialType string 43 | } 44 | 45 | // DeRaw ... 46 | func (c *RawCredential) DeRaw() *Credential { 47 | if c == nil { 48 | return nil 49 | } 50 | return &Credential{ 51 | Version: c.Version, 52 | ID: utils.BytesBuilder(c.IDPtr, c.IDLen), 53 | CredentialType: utils.UTF16PtrtoString(c.CredentialType), 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /api/share/RpInfo.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import "github.com/Ink-33/authn/api/utils" 4 | 5 | // RawRPInfo is the information about an RP Entity 6 | // 7 | // _WEBAUTHN_RP_ENTITY_INFORMATION 8 | type RawRPInfo struct { 9 | // Version of this structure, to allow for modifications in the future. 10 | // This field is required and should be set to CURRENT_VERSION above. 11 | Version uint32 //DWORD dwVersion; 12 | 13 | // Identifier for the RP. This field is required. 14 | ID *uint16 // PCWSTR pwszId; 15 | 16 | // Contains the friendly name of the Relying Party, such as "Acme Corporation", "Widgets Inc" or "Awesome Site". 17 | // This field is required. 18 | Name *uint16 // PCWSTR pwszName 19 | 20 | // Optional URL pointing to RP's logo. 21 | Icon *uint16 // PCWSTR pwszIcon 22 | } 23 | 24 | // RPInfo is the information about an RP Entity 25 | type RPInfo struct { 26 | // Version of this structure, to allow for modifications in the future. 27 | // This field is required and should be set to CURRENT_VERSION above. 28 | Version uint32 //DWORD dwVersion; 29 | 30 | // Identifier for the RP. This field is required. 31 | ID string 32 | 33 | // Contains the friendly name of the Relying Party, such as "Acme Corporation", "Widgets Inc" or "Awesome Site". 34 | // This field is required. 35 | Name string 36 | 37 | // Optional URL pointing to RP's logo. 38 | Icon string 39 | } 40 | 41 | // DeRaw ... 42 | func (c *RawRPInfo) DeRaw() *RPInfo { 43 | if c == nil { 44 | return nil 45 | } 46 | return &RPInfo{ 47 | Version: c.Version, 48 | ID: utils.UTF16PtrtoString(c.ID), 49 | Name: utils.UTF16PtrtoString(c.Name), 50 | Icon: utils.UTF16PtrtoString(c.Icon), 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /api/cose/cose.go: -------------------------------------------------------------------------------- 1 | package cose 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fxamacker/cbor/v2" 7 | ) 8 | 9 | // WebAuthN use COSE format to encode public key. 10 | // See https://w3c.github.io/webauthn/#sctn-encoded-credPubKey-examples for example key format. 11 | 12 | type COSEPublicKey interface{} 13 | 14 | type PKMetadata struct { 15 | KeyType int64 `cbor:"1,keyasint,omitempty"` 16 | Algorithm int64 `cbor:"3,keyasint,omitempty"` 17 | } 18 | 19 | type EC2 struct { 20 | PKMetadata 21 | Curve int64 `cbor:"-1,keyasint,omitempty"` 22 | X []byte `cbor:"-2,keyasint,omitempty"` 23 | Y []byte `cbor:"-3,keyasint,omitempty"` 24 | } 25 | 26 | type OKP struct { 27 | PKMetadata 28 | Curve int64 `cbor:"-1,keyasint,omitempty"` 29 | X []byte `cbor:"-2,keyasint,omitempty"` 30 | } 31 | 32 | type RSA struct { 33 | PKMetadata 34 | Modulus []byte `cbor:"-1,keyasint,omitempty"` 35 | Exponent []byte `cbor:"-3,keyasint,omitempty"` 36 | } 37 | 38 | func ParseCOSEKey(cose []byte) (pkdata COSEPublicKey, err error) { 39 | pkd := &PKMetadata{} 40 | err = cbor.Unmarshal(cose, pkd) 41 | if err != nil { 42 | return nil, err 43 | } 44 | switch pkd.KeyType { 45 | case 1: // Octet Key 46 | pk := &OKP{} 47 | err = cbor.Unmarshal(cose, pk) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return pk, nil 52 | case 2: // EC2 key 53 | pk := &EC2{} 54 | err = cbor.Unmarshal(cose, pk) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return pk, nil 59 | case 3: // RSA key 60 | pk := &RSA{} 61 | err = cbor.Unmarshal(cose, pk) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return pk, nil 66 | default: 67 | return nil, fmt.Errorf("unknown key type") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /api/share/Hmac.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import "github.com/Ink-33/authn/api/utils" 4 | 5 | // RawHMACSecretSalt ... 6 | // 7 | // _WEBAUTHN_HMAC_SECRET_SALT 8 | type RawHMACSecretSalt struct { 9 | // Size of pbFirst. 10 | FirstLen uint32 // DWORD cbFirst 11 | // _Field_size_bytes_ (cbFirst) 12 | FirstPtr *byte // PBYTE pbFirst // Required 13 | 14 | // Size of pbSecond. 15 | SecondLen uint32 // DWORD cbSecond 16 | // _Field_size_bytes_ (cbSecond) 17 | SecondPtr *byte // PBYTE pbSecond 18 | } 19 | 20 | // RawCredWithHMACSecretSalt ... 21 | // 22 | // _WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT 23 | type RawCredWithHMACSecretSalt struct { 24 | // Size of pbCredID. 25 | CredIDLen uint32 // DWORD cbCredID 26 | // _Field_size_bytes_ (cbCredID) 27 | CredIDPtr *byte // PBYTE pbCredID // Required 28 | 29 | // PRF Values for above credential 30 | HMACSecretSalt *RawHMACSecretSalt // PWEBAUTHN_HMAC_SECRET_SALT pHmacSecretSalt // Required 31 | } 32 | 33 | // RawHMACSecretSaltValues ... 34 | // 35 | // _WEBAUTHN_HMAC_SECRET_SALT_VALUES 36 | type RawHMACSecretSaltValues struct { 37 | GlobalHmacSalt *RawHMACSecretSalt // PWEBAUTHN_HMAC_SECRET_SALT pGlobalHmacSalt 38 | 39 | CredWithHMACSecretSaltListLen uint32 // DWORD cCredWithHmacSecretSaltList 40 | // _Field_size_ (cCredWithHmacSecretSaltList) 41 | CredWithHMACSecretSaltListPtr *RawCredWithHMACSecretSalt // PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT pCredWithHmacSecretSaltList 42 | } 43 | 44 | // HMACSecretSalt ... 45 | type HMACSecretSalt struct { 46 | First []byte 47 | Second []byte 48 | } 49 | 50 | // DeRaw ... 51 | func (c *RawHMACSecretSalt) DeRaw() *HMACSecretSalt { 52 | if c == nil { 53 | return nil 54 | } 55 | return &HMACSecretSalt{ 56 | First: utils.BytesBuilder(c.FirstPtr, c.FirstLen), 57 | Second: utils.BytesBuilder(c.SecondPtr, c.SecondLen), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /api/share/UserInfo.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "github.com/Ink-33/authn/api/utils" 5 | ) 6 | 7 | // RawUserInfo is the information about an User Entity 8 | // 9 | // _WEBAUTHN_USER_ENTITY_INFORMATION 10 | type RawUserInfo struct { 11 | // Version of this structure, to allow for modifications in the future. 12 | // This field is required and should be set to CURRENT_VERSION above. 13 | Version uint32 // DWORD dwVersion; 14 | 15 | // Identifier for the User. This field is required. 16 | IDLen uint32 // DWORD cbId 17 | // _Field_size_bytes_(cbId) 18 | IDPtr *byte // PBYTE pbId 19 | 20 | // Contains a detailed name for this account, such as "john.p.smith@example.com". 21 | Name *uint16 // PCWSTR pwszName 22 | 23 | // Optional URL that can be used to retrieve an image containing the user's current avatar, 24 | // or a data URI that contains the image data. 25 | Icon *uint16 // PCWSTR pwszIcon 26 | 27 | // For User: Contains the friendly name associated with the user account by the Relying Party, such as "John P. Smith". 28 | DisplayName *uint16 // PCWSTR pwszDisplayName 29 | } 30 | 31 | // UserInfo is the information about an User Entity 32 | type UserInfo struct { 33 | // Version of this structure, to allow for modifications in the future. 34 | // This field is required and should be set to CURRENT_VERSION above. 35 | Version uint32 // DWORD dwVersion; 36 | 37 | ID []byte // PBYTE pbId 38 | 39 | // Contains a detailed name for this account, such as "john.p.smith@example.com". 40 | Name string 41 | 42 | // Optional URL that can be used to retrieve an image containing the user's current avatar, 43 | // or a data URI that contains the image data. 44 | Icon string 45 | 46 | // For User: Contains the friendly name associated with the user account by the Relying Party, such as "John P. Smith". 47 | DisplayName string 48 | } 49 | 50 | // DeRaw ... 51 | func (c *RawUserInfo) DeRaw() *UserInfo { 52 | if c == nil { 53 | return nil 54 | } 55 | return &UserInfo{ 56 | Version: c.Version, 57 | ID: utils.BytesBuilder(c.IDPtr, c.IDLen), 58 | Name: utils.UTF16PtrtoString(c.Name), 59 | Icon: utils.UTF16PtrtoString(c.Icon), 60 | DisplayName: utils.UTF16PtrtoString(c.DisplayName), 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /api/share/AuthenticatorData.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/Ink-33/authn/api/cose" 7 | "github.com/fxamacker/cbor/v2" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | type AuthenticatorData struct { 12 | RPIDHash [32]byte 13 | Flags Flags 14 | SignCounter uint32 15 | AttestedCredentialData *CredentialData 16 | Extensions any 17 | } 18 | 19 | type CredentialData struct { 20 | AAGUID uuid.UUID 21 | CredentialID []byte 22 | CredentialPublicKey cose.COSEPublicKey 23 | } 24 | 25 | type Flags struct { 26 | UserPresent bool 27 | RFU1 bool 28 | UserVerified bool 29 | BackupEligibility bool 30 | BackupState bool 31 | RFU2 bool 32 | AttestedCredentialData bool 33 | ExtensionData bool 34 | } 35 | 36 | func ParseAuthenticatorData(data []byte) (*AuthenticatorData, error) { 37 | d := &AuthenticatorData{ 38 | Flags: Flags{ 39 | UserPresent: data[32]&(1<<0) == 1<<0, 40 | RFU1: data[32]&(1<<1) == 1<<1, 41 | UserVerified: data[32]&(1<<2) == 1<<2, 42 | BackupEligibility: data[32]&(1<<3) == 1<<3, 43 | BackupState: data[32]&(1<<4) == 1<<4, 44 | RFU2: data[32]&(1<<5) == 1<<5, 45 | AttestedCredentialData: data[32]&(1<<6) == 1<<6, 46 | ExtensionData: data[32]&(1<<7) == 1<<7, 47 | }, 48 | SignCounter: binary.BigEndian.Uint32(data[33:37]), 49 | Extensions: nil, 50 | } 51 | copy(d.RPIDHash[:], data[:32]) 52 | if d.Flags.AttestedCredentialData { 53 | cidlen := binary.BigEndian.Uint16(data[53:55]) 54 | cd := &CredentialData{ 55 | CredentialID: data[55 : 55+cidlen], 56 | CredentialPublicKey: nil, 57 | } 58 | copy(cd.AAGUID[:], data[37:53]) 59 | i := 55 + int(cidlen) + 1 60 | for ; i < len(data)+1; i++ { 61 | pk, err := cose.ParseCOSEKey(data[55+cidlen : i]) 62 | if err == nil { 63 | cd.CredentialPublicKey = pk 64 | break 65 | } 66 | } 67 | d.AttestedCredentialData = cd 68 | if i != len(data) { 69 | err := cbor.Unmarshal(data[i:], d.Extensions) 70 | if err != nil { 71 | return nil, err 72 | } 73 | } 74 | } 75 | 76 | return d, nil 77 | } 78 | -------------------------------------------------------------------------------- /cmd/makecerd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "fmt" 7 | 8 | "github.com/Ink-33/authn/api" 9 | ) 10 | 11 | func MakeCred(c *api.WebAuthNClient) (func(), error) { 12 | id := make([]byte, 32) 13 | _, _ = rand.Read(id) 14 | u := &testUser{id} 15 | 16 | printCallAPI() 17 | a, err := c.MakeCredential(u, "local://demo.app", nil) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | p := func() { 23 | // fmt.Printf("Version: %v\n", a.Version) 24 | // fmt.Printf("FormatType: %v\n", a.FormatType) 25 | // fmt.Printf("AttestationDecodeType: %v\n", a.AttestationDecode) 26 | // fmt.Printf("UsedTransport: %v\n", a.UsedTransport) 27 | // fmt.Printf("EpAtt: %v\n", a.EpAtt) 28 | // fmt.Printf("LargeBlobSupported: %v\n", a.LargeBlobSupported) 29 | // fmt.Printf("ResidentKey: %v\n", a.ResidentKey) 30 | 31 | // fmt.Printf("Extensions: %v\n", a.Extensions) 32 | 33 | fmt.Printf("RPID Hash: %v\n", 34 | base64.RawURLEncoding.EncodeToString(a.AuthenticatorData.RPIDHash[:])) 35 | 36 | fmt.Printf("User Present: %v\n", a.AuthenticatorData.Flags.UserPresent) 37 | fmt.Printf("User Verified: %v\n", a.AuthenticatorData.Flags.UserVerified) 38 | fmt.Printf("Backup Eligibility: %v\n", a.AuthenticatorData.Flags.BackupEligibility) 39 | fmt.Printf("Backup State: %v\n", a.AuthenticatorData.Flags.BackupState) 40 | fmt.Printf("Attested credential data included: %v\n", a.AuthenticatorData.Flags.AttestedCredentialData) 41 | fmt.Printf("Extension data included: %v\n", a.AuthenticatorData.Flags.ExtensionData) 42 | 43 | fmt.Printf("Sign Counter: %v\n", a.AuthenticatorData.SignCounter) 44 | 45 | fmt.Printf("AAGUID: %v\n", a.AuthenticatorData.AttestedCredentialData.AAGUID.String()) 46 | 47 | fmt.Printf("CredentialID:%v\n", 48 | base64.RawURLEncoding.EncodeToString(a.AuthenticatorData.AttestedCredentialData.CredentialID)) 49 | 50 | fmt.Printf("COSE: %v\n", a.AuthenticatorData.AttestedCredentialData.CredentialPublicKey) 51 | // atM := map[string]any{} 52 | // err = cbor.Unmarshal(a.Attestation, &atM) 53 | // if err != nil { 54 | // fmt.Printf("Err: %v\n", err) 55 | // return 56 | // } 57 | // fmt.Printf("Attestation: %v\n", atM) 58 | 59 | // atoM := map[string]any{} 60 | // err = cbor.Unmarshal(a.AttestationObject, &atoM) 61 | // if err != nil { 62 | // fmt.Printf("Err: %v\n", err) 63 | // return 64 | // } 65 | // fmt.Printf("AttestationObject: %v\n", atoM) 66 | 67 | } 68 | return p, nil 69 | } 70 | -------------------------------------------------------------------------------- /api/share/CredentialDetail.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "github.com/Ink-33/authn/api/utils" 7 | ) 8 | 9 | // RawCredentialDetails is the Credential Information for WebAuthNGetPlatformCredentialList API 10 | // 11 | // _WEBAUTHN_CREDENTIAL_DETAILS 12 | type RawCredentialDetails struct { 13 | // Version of this structure, to allow for modifications in the future. 14 | Version uint32 // DWORD dwVersion 15 | 16 | // Size of pbCredentialID. 17 | CredentialIDLen uint32 // DWORD cbCredentialID 18 | // _Field_size_bytes_ (cbCredentialID) 19 | CredentialIDPtr *byte // PBYTE pbCredentialID 20 | 21 | // RP Info 22 | RPInformation *RawRPInfo // PWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation 23 | 24 | // User Info 25 | UserInformation *RawUserInfo // PWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation 26 | 27 | // Removable or not. 28 | Removable bool // BOOL bRemovable 29 | } 30 | 31 | // RawCredentialDetailsList ... 32 | // 33 | // _WEBAUTHN_CREDENTIAL_DETAILS_LIST 34 | type RawCredentialDetailsList struct { 35 | CredentialDetailsLen uint32 // DWORD cCredentialDetails 36 | // _Field_size_ (cCredentialDetails) 37 | CredentialDetailsPtr **RawCredentialDetails // PWEBAUTHN_CREDENTIAL_DETAILS *ppCredentialDetails 38 | } 39 | 40 | // CredentialDetails ... 41 | type CredentialDetails struct { 42 | // Version of this structure, to allow for modifications in the future. 43 | Version uint32 44 | 45 | CredentialID []byte 46 | 47 | // RP Info 48 | RPInformation *RPInfo 49 | 50 | // User Info 51 | UserInformation *UserInfo 52 | 53 | // Removable or not. 54 | Removable bool // BOOL bRemovable 55 | } 56 | 57 | // DeRaw ... 58 | func (c *RawCredentialDetails) DeRaw() *CredentialDetails { 59 | if c == nil { 60 | return nil 61 | } 62 | return &CredentialDetails{ 63 | Version: c.Version, 64 | CredentialID: utils.BytesBuilder(c.CredentialIDPtr, c.CredentialIDLen), 65 | RPInformation: c.RPInformation.DeRaw(), 66 | UserInformation: c.UserInformation.DeRaw(), 67 | Removable: c.Removable, 68 | } 69 | } 70 | 71 | // DeRaw ... 72 | func (c *RawCredentialDetailsList) DeRaw() []*CredentialDetails { 73 | if c == nil { 74 | return nil 75 | } 76 | rl := unsafe.Slice(c.CredentialDetailsPtr, c.CredentialDetailsLen) 77 | l := make([]*CredentialDetails, c.CredentialDetailsLen) 78 | for i := uint32(0); i < c.CredentialDetailsLen; i++ { 79 | l[i] = rl[i].DeRaw() 80 | } 81 | return l 82 | } 83 | -------------------------------------------------------------------------------- /cmd/deletecred.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/Ink-33/authn/api" 10 | "github.com/Ink-33/authn/interact" 11 | ) 12 | 13 | type cred struct { 14 | cbCred uint32 15 | pbCred *byte 16 | b64Cred string 17 | } 18 | 19 | func DeletePlatformCred(c *api.WebAuthNClient) (func(), error) { 20 | choices := interact.Choose{ 21 | Title: "Select sub operation:", 22 | Choices: []interact.Choice{ 23 | interact.NewChoice( 24 | "Purge all credentials related with this cli tool", 25 | func() (func(), error) { msg, err := purge(c); return msg, interact.NewToPreviouswithErr(err) }, 26 | ), 27 | interact.NewChoice( 28 | "Choose credentials to delete", 29 | func() (func(), error) { msg, err := choose(c); return msg, interact.NewToPreviouswithErr(err) }, 30 | ), 31 | }, 32 | Loop: true, 33 | ToPreviousChooseDesc: "Cancel", 34 | } 35 | return choices.Do() 36 | } 37 | 38 | func purge(c *api.WebAuthNClient) (func(), error) { 39 | printCallAPI() 40 | list, err := c.GetPlatformCredentialList("") 41 | if err != nil { 42 | if err.Error() == "NteNotFound" { 43 | return func() { fmt.Println("Nothing to do ...") }, nil 44 | } 45 | return nil, err 46 | } 47 | 48 | if len(list) == 0 { 49 | return func() { fmt.Println("Nothing to do ...") }, nil 50 | } 51 | 52 | printCredList(list) 53 | fmt.Printf("These credentials will be removed. Y/n?") 54 | q := "" 55 | fmt.Scanln(&q) 56 | 57 | if strings.ToLower(q) != "y" { 58 | return nil, fmt.Errorf("Cancelled") 59 | } 60 | 61 | deletelist := make([]cred, len(list)) 62 | for i := 0; i < len(list); i++ { 63 | deletelist[i] = cred{ 64 | cbCred: uint32(len(list[i].CredentialID)), 65 | pbCred: &list[i].CredentialID[0], 66 | b64Cred: base64.URLEncoding.EncodeToString(list[i].CredentialID), 67 | } 68 | } 69 | delete(c, deletelist) 70 | return func() { fmt.Println("All done") }, nil 71 | } 72 | 73 | func choose(c *api.WebAuthNClient) (func(), error) { 74 | return nil, fmt.Errorf("TODO") 75 | } 76 | 77 | func delete(c *api.WebAuthNClient, list []cred) { 78 | wg := sync.WaitGroup{} 79 | wg.Add(len(list)) 80 | 81 | bucket := make(chan struct{}, 3) 82 | for i := 0; i < 3; i++ { 83 | bucket <- struct{}{} 84 | } 85 | 86 | for i := 0; i < len(list); i++ { 87 | <-bucket 88 | go func(i int) { 89 | fmt.Printf("Deleting %v ...\n", list[i].b64Cred) 90 | err := c.DeletePlatformCred(list[i].cbCred, list[i].pbCred) 91 | if err != nil { 92 | fmt.Println("Err:", err) 93 | } 94 | wg.Done() 95 | bucket <- struct{}{} 96 | }(i) 97 | } 98 | wg.Wait() 99 | } 100 | -------------------------------------------------------------------------------- /api/share/MakeCredentialOpt.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import "golang.org/x/sys/windows" 4 | 5 | // RawAuthenticatorMakeCredentialOptions ... 6 | type RawAuthenticatorMakeCredentialOptions struct { 7 | // Version of this structure, to allow for modifications in the future. 8 | Version uint32 // DWORD dwVersion; 9 | 10 | // Time that the operation is expected to complete within. 11 | // This is used as guidance, and can be overridden by the platform. 12 | TimeoutMilliseconds uint32 // DWORD dwTimeoutMilliseconds 13 | 14 | // Credentials used for exclusion. 15 | CredentialList RawCredentials // WEBAUTHN_CREDENTIALS CredentialList 16 | 17 | // Optional extensions to parse when performing the operation. 18 | Extensions RawExtensions // WEBAUTHN_EXTENSIONS Extensions 19 | 20 | // Optional. Platform vs Cross-Platform Authenticators. 21 | AuthenticatorAttachment uint32 // DWORD dwAuthenticatorAttachment 22 | 23 | // Optional. Require key to be resident or not. Defaulting to FALSE. 24 | RequireResidentKey bool // BOOL bRequireResidentKey 25 | 26 | // User Verification Requirement. 27 | UserVerificationRequirement uint32 // DWORD dwUserVerificationRequirement 28 | 29 | // Attestation Conveyance Preference. 30 | AttestationConveyancePreference uint32 // DWORD dwAttestationConveyancePreference 31 | 32 | // Reserved for future Use 33 | Flags uint32 // DWORD dwFlags 34 | 35 | // 36 | // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2 37 | // 38 | 39 | // Cancellation Id - Optional - See WebAuthNGetCancellationId 40 | CancellationID *windows.GUID // GUID *pCancellationId 41 | 42 | // 43 | // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3 44 | // 45 | 46 | // Exclude Credential List. If present, "CredentialList" will be ignored. 47 | ExcludeCredentialList *RawCredentialList // PWEBAUTHN_CREDENTIAL_LIST pExcludeCredentialList 48 | 49 | // 50 | // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4 51 | // 52 | 53 | // Enterprise Attestation 54 | EnterpriseAttestation uint32 // DWORD dwEnterpriseAttestation 55 | 56 | // Large Blob Support: none, required or preferred 57 | // 58 | // NTE_INVALID_PARAMETER when large blob required or preferred and 59 | // bRequireResidentKey isn't set to TRUE 60 | LargeBlobSupport uint32 // DWORD dwLargeBlobSupport 61 | 62 | // Optional. Prefer key to be resident. Defaulting to FALSE. When TRUE, 63 | // overrides the above bRequireResidentKey. 64 | PreferResidentKey bool // BOOL bPreferResidentKey 65 | 66 | // 67 | // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5 68 | // 69 | 70 | // Optional. BrowserInPrivate Mode. Defaulting to FALSE. 71 | BrowserInPrivateMode bool // BOOL bBrowserInPrivateMode 72 | } 73 | -------------------------------------------------------------------------------- /api/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "reflect" 7 | "unsafe" 8 | 9 | "github.com/google/uuid" 10 | "golang.org/x/sys/windows" 11 | ) 12 | 13 | var kernel32 = windows.NewLazySystemDLL("Kernel32.dll") 14 | var user32 = windows.NewLazySystemDLL("User32.dll") 15 | 16 | // GetConsoleWindow retrieves the window handle used by the console associated with the calling process. 17 | func GetConsoleWindow() (hWnd uintptr) { 18 | hWnd, _, _ = kernel32.NewProc("GetConsoleWindow").Call() 19 | return 20 | } 21 | 22 | // GetHostWindow retrieves the window handle used by the foreground window who starts the caliing process. 23 | func GetHostWindow() (hWnd uintptr) { 24 | return GetForegroundWindow() 25 | } 26 | 27 | // UTF16PtrtoString converts a pointer to a UTF16 string into a Go string. 28 | func UTF16PtrtoString(p *uint16) string { 29 | if p == nil { 30 | return "" 31 | } 32 | return windows.UTF16ToString((*[4096]uint16)(unsafe.Pointer(p))[:]) 33 | } 34 | 35 | // B2S convert byte slice to string. 36 | func B2S(b []byte) string { 37 | return *(*string)(unsafe.Pointer(&b)) 38 | } 39 | 40 | // S2B convert string to []byte slice. 41 | func S2B(s string) (b []byte) { 42 | (*reflect.SliceHeader)(unsafe.Pointer(&b)).Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data 43 | (*reflect.SliceHeader)(unsafe.Pointer(&b)).Cap = len(s) 44 | (*reflect.SliceHeader)(unsafe.Pointer(&b)).Len = len(s) 45 | return 46 | } 47 | 48 | // CreateChallenge generates a new chanllenge that will be sent to the authenticator. 49 | // 50 | // In order to prevent replay attacks, the challenges MUST contain enough entropy to 51 | // make guessing them infeasible. Challenges SHOULD therefore be at least 16 bytes long. 52 | // See https://w3c.github.io/webauthn/#sctn-cryptographic-challenges 53 | // 54 | // # Default 32 bytes length will be used if the given length is less than 16 or more than 256 55 | // 56 | // Challenge is encoded in base64. 57 | func CreateChallenge(len int) (string, error) { 58 | if len < 16 || len > 256 { 59 | len = 16 60 | } 61 | challenge := make([]byte, len) 62 | _, err := rand.Read(challenge) 63 | if err != nil { 64 | return "", err 65 | } 66 | return base64.RawURLEncoding.EncodeToString(challenge), nil 67 | } 68 | 69 | // BytesBuilder converts PBYTE to go []byte. 70 | func BytesBuilder(ptr *byte, len uint32) (buf []byte) { 71 | if len == 0 { 72 | return 73 | } 74 | buf = make([]byte, len) 75 | raw := unsafe.Slice(ptr, len) 76 | copy(buf, raw) // make sure it is safe after call free api. 77 | return buf 78 | } 79 | 80 | // CreateCancelID returns a new windows guid 81 | func CreateCancelID() (windows.GUID, error) { 82 | return windows.GUIDFromString("{" + uuid.New().String() + "}") 83 | } 84 | 85 | func GetForegroundWindow() (hWnd uintptr) { 86 | hWnd, _, _ = user32.NewProc("GetForegroundWindow").Call() 87 | return 88 | } 89 | -------------------------------------------------------------------------------- /api/share/GetAssertionOpt.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import "golang.org/x/sys/windows" 4 | 5 | // RawAuthenticatorGetAssertionOptions ... 6 | type RawAuthenticatorGetAssertionOptions struct { 7 | // Version of this structure, to allow for modifications in the future. 8 | Version uint32 // DWORD 9 | 10 | // Time that the operation is expected to complete within. 11 | // This is used as guidance, and can be overridden by the platform. 12 | TimeoutMilliseconds uint32 // DWORD 13 | 14 | // Allowed Credentials List. 15 | CredentialList RawCredentials // WEBAUTHN_CREDENTIALS CredentialList 16 | 17 | // Optional extensions to parse when performing the operation. 18 | Extensions RawExtensions // WEBAUTHN_EXTENSIONS Extensions 19 | 20 | // Optional. Platform vs Cross-Platform Authenticators. 21 | AuthenticatorAttachment uint32 // DWORD dwAuthenticatorAttachment 22 | 23 | // User Verification Requirement. 24 | UserVerificationRequirement uint32 // DWORD dwUserVerificationRequirement 25 | 26 | // Flags 27 | Flags uint32 // DWORD dwFlags 28 | 29 | // 30 | // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2 31 | // 32 | 33 | // Optional identifier for the U2F AppId. Converted to UTF8 before being hashed. Not lower cased. 34 | U2fAppID *uint16 // PCWSTR pwszU2fAppId 35 | 36 | // If the following is non-NULL, then, set to TRUE if the above pwszU2fAppid was used instead of 37 | // PCWSTR pwszRpId; 38 | IsU2fAppIDUsed *bool // BOOL *pbU2fAppId 39 | 40 | // 41 | // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3 42 | // 43 | 44 | // Cancellation Id - Optional - See WebAuthNGetCancellationId 45 | CancellationID *windows.GUID // GUID *pCancellationId 46 | 47 | // 48 | // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4 49 | // 50 | 51 | // Allow Credential List. If present, "CredentialList" will be ignored. 52 | AllowCredentialList *RawCredentialList // PWEBAUTHN_CREDENTIAL_LIST pAllowCredentialList 53 | 54 | // 55 | // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5 56 | // 57 | 58 | CredLargeBlobOperation uint32 // DWORD dwCredLargeBlobOperation 59 | 60 | // Size of pbCredLargeBlob 61 | CredLargeBlobLen uint32 // DWORD cbCredLargeBlob 62 | // _Field_size_bytes_ (cbCredLargeBlob) 63 | CredLargeBlobPtr byte // PBYTE pbCredLargeBlob 64 | 65 | // 66 | // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6 67 | // 68 | 69 | // PRF values which will be converted into HMAC-SECRET values according to WebAuthn Spec. 70 | HMACSecretSaltValues *RawHMACSecretSaltValues // PWEBAUTHN_HMAC_SECRET_SALT_VALUES pHmacSecretSaltValues 71 | 72 | // Optional. BrowserInPrivate Mode. Defaulting to FALSE. 73 | BrowserInPrivateMode bool // BOOL bBrowserInPrivateMode 74 | } 75 | -------------------------------------------------------------------------------- /api/share/CommonAttestation.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "github.com/Ink-33/authn/api/utils" 7 | ) 8 | 9 | // RawCommonAttestation contains the common data for an attestation. 10 | type RawCommonAttestation struct { 11 | // Version of this structure, to allow for modifications in the future. 12 | Version uint32 // DWORD dwVersion; 13 | 14 | // Hash and Padding Algorithm 15 | // 16 | // The following won't be set for "fido-u2f" which assumes "ES256". 17 | AlgString *uint16 // PCWSTR pwszAlg; 18 | Alg int32 // LONG lAlg; // COSE algorithm 19 | 20 | // Signature that was generated for this attestation. 21 | SignatureLen uint32 // DWORD cbSignature; 22 | // _Field_size_bytes_(cbSignature) 23 | SignaturePtr *byte // PBYTE pbSignature; 24 | 25 | // Following is set for Full Basic Attestation. If not, set then, this is Self Attestation. 26 | // Array of X.509 DER encoded certificates. The first certificate is the signer, leaf certificate. 27 | X5CLen uint32 // DWORD cX5c; 28 | // _Field_size_(cX5c) 29 | X5CPtr *RawX5C // PWEBAUTHN_X5C pX5c; 30 | 31 | // Following are also set for tpm 32 | Ver *uint16 // PCWSTR pwszVer; // L"2.0" 33 | CertInfoLen uint32 // DWORD cbCertInfo; 34 | // _Field_size_bytes_(cbCertInfo) 35 | CertInfoPtr *byte // PBYTE pbCertInfo; 36 | PubAreaLen uint32 // DWORD cbPubArea; 37 | // _Field_size_bytes_(cbPubArea) 38 | PubAreaPtr *byte // PBYTE pbPubArea; 39 | } 40 | 41 | // CommonAttestation contains the common data for an attestation. 42 | type CommonAttestation struct { 43 | // Version of this structure, to allow for modifications in the future. 44 | Version uint32 // DWORD dwVersion; 45 | 46 | // Hash and Padding Algorithm 47 | // 48 | // The following won't be set for "fido-u2f" which assumes "ES256". 49 | AlgString string // PCWSTR pwszAlg; 50 | Alg int32 // LONG lAlg; // COSE algorithm 51 | 52 | // Signature that was generated for this attestation. 53 | Signature []byte // PBYTE pbSignature; 54 | 55 | // Following is set for Full Basic Attestation. If not, set then, this is Self Attestation. 56 | // Array of X.509 DER encoded certificates. The first certificate is the signer, leaf certificate. 57 | X5C []X5C // PWEBAUTHN_X5C pX5c; 58 | 59 | // Following are also set for tpm 60 | Ver string // PCWSTR pwszVer; // L"2.0" 61 | CertInfo []byte // PBYTE pbCertInfo; 62 | PubArea []byte // PBYTE pbPubArea; 63 | } 64 | 65 | func (c *RawCommonAttestation) DeRaw() *CommonAttestation { 66 | if c == nil { 67 | return nil 68 | } 69 | rx5c := unsafe.Slice(c.X5CPtr, c.X5CLen) 70 | x5c := make([]X5C, c.X5CLen) 71 | for i := 0; i < int(c.X5CLen); i++ { 72 | x5c[i] = rx5c[i].DeRaw() 73 | } 74 | 75 | return &CommonAttestation{ 76 | Version: c.Version, 77 | AlgString: utils.UTF16PtrtoString(c.AlgString), 78 | Alg: c.Alg, 79 | Signature: utils.BytesBuilder(c.SignaturePtr, c.SignatureLen), 80 | X5C: x5c, 81 | Ver: utils.UTF16PtrtoString(c.Ver), 82 | CertInfo: utils.BytesBuilder(c.SignaturePtr, c.SignatureLen), 83 | PubArea: utils.BytesBuilder(c.PubAreaPtr, c.PubAreaLen), 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /interact/choose.go: -------------------------------------------------------------------------------- 1 | package interact 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // ChoiceHanlder defines function signature of a vaild choice function. 8 | type ChoiceHanlder func() (func(), error) 9 | 10 | // ToPrevious implements error interface that let app step out current choose loop. 11 | type ToPrevious struct { 12 | Err error 13 | } 14 | 15 | func (e *ToPrevious) Error() string { 16 | return "exit the loop and go back to previous choose node" 17 | } 18 | 19 | // NewToPreviouswithErr warps error to ToPrevious. 20 | func NewToPreviouswithErr(err error) error { 21 | return &ToPrevious{Err: err} 22 | } 23 | 24 | // DefaultChoice implements Choice interface with essential fields. 25 | type DefaultChoice struct { 26 | Description string 27 | Function ChoiceHanlder 28 | } 29 | 30 | // Desc returnn description 31 | func (c *DefaultChoice) Desc() string { 32 | return c.Description 33 | } 34 | 35 | // Run the choose. 36 | func (c *DefaultChoice) Run() (func(), error) { 37 | return c.Function() 38 | } 39 | 40 | // NewChoice returns a Choice node. 41 | func NewChoice(desc string, function func() (func(), error)) Choice { 42 | return &DefaultChoice{ 43 | Description: desc, 44 | Function: function, 45 | } 46 | } 47 | 48 | // Choice defines the basic abilities of Choice node. 49 | // 50 | // This interface might be extended in the future. 51 | type Choice interface { 52 | Desc() string 53 | Run() (output func(), err error) 54 | } 55 | 56 | // Choose defines essential fields to let this interaction work. 57 | type Choose struct { 58 | Title string 59 | Choices []Choice 60 | Loop bool 61 | ToPreviousChooseDesc string 62 | 63 | // TODO 64 | ToNextPageDesc *Choice 65 | // TODO 66 | ToPreviousPageDesc *Choice 67 | } 68 | 69 | // ToClosure warps the Choose interaction to a ChoiceHanlder. 70 | // It can be used to create a nesting choose interaction. 71 | func (c *Choose) ToClosure() ChoiceHanlder { 72 | return func() (func(), error) { 73 | var msg func() 74 | for i := 0; ; i++ { 75 | if i != 0 { 76 | fmt.Printf("\033[2J") 77 | fmt.Printf("\033[H") 78 | } 79 | if msg != nil { 80 | fmt.Printf("Output:\n\n") 81 | msg() 82 | fmt.Println() 83 | } 84 | fmt.Printf("%v\n\n", c.Title) 85 | for i := range c.Choices { 86 | fmt.Printf("(%v): %v\n", i+1, c.Choices[i].Desc()) 87 | } 88 | 89 | fmt.Printf("\n(0): %v\n\n", c.ToPreviousChooseDesc) 90 | 91 | op := ScanInputAndCheck() 92 | if op > len(c.Choices) || op < 0 { 93 | msg = func() { fmt.Printf("Invaild input\n") } 94 | continue 95 | } 96 | if op == 0 { 97 | break 98 | } 99 | var err error 100 | msg, err = c.Choices[op-1].Run() 101 | 102 | if c.Loop { 103 | if err != nil { 104 | if warp, ok := err.(*ToPrevious); ok { 105 | return msg, warp.Err 106 | } 107 | msg = func() { fmt.Printf("Err: %v\n", err) } 108 | } 109 | continue 110 | } 111 | break 112 | } 113 | return nil, nil 114 | } 115 | } 116 | 117 | // Do the interaction. 118 | func (c *Choose) Do() (func(), error) { 119 | if c.ToPreviousChooseDesc == "" { 120 | c.ToPreviousChooseDesc = "Exit" 121 | } 122 | return c.ToClosure()() 123 | } 124 | -------------------------------------------------------------------------------- /api/warp.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/Ink-33/authn/api/define" 7 | "github.com/Ink-33/authn/api/share" 8 | "golang.org/x/sys/windows" 9 | ) 10 | 11 | // NewMakeCredOpts returns an AuthenticatorMakeCredentialOptions struct pointer whih default value. 12 | func NewMakeCredOpts() *share.RawAuthenticatorMakeCredentialOptions { 13 | return &share.RawAuthenticatorMakeCredentialOptions{ 14 | Version: define.WebAuthNAuthenticatorMakeCredentialOptionsCurrentVersion, 15 | TimeoutMilliseconds: 60000, 16 | CredentialList: share.RawCredentials{ 17 | CredentialsLen: 0, 18 | CredentialsPtr: nil, 19 | }, 20 | Extensions: share.RawExtensions{ 21 | ExtensionsLen: 0, 22 | ExtensionsPrt: nil, 23 | }, 24 | AuthenticatorAttachment: define.WebAuthNAuthenticatorAttachmentAny, 25 | RequireResidentKey: false, 26 | UserVerificationRequirement: define.WebAuthNUserVerificationRequirementDiscouraged, 27 | AttestationConveyancePreference: define.WebAuthNAttestationConveyancePreferenceNone, 28 | Flags: 0, 29 | CancellationID: nil, 30 | ExcludeCredentialList: &share.RawCredentialList{ 31 | Credentials: 0, 32 | CredentialsPtr: nil, 33 | }, 34 | EnterpriseAttestation: define.WebAuthNEnterpriseAttestationNone, 35 | LargeBlobSupport: define.WebAuthNCredLargeBlobOperationNone, 36 | PreferResidentKey: false, 37 | BrowserInPrivateMode: false, 38 | } 39 | } 40 | 41 | // NewGetAssertionOptions returns an AuthenticatorGetAssertionOptions struct pointer whih default value. 42 | func NewGetAssertionOptions() *share.RawAuthenticatorGetAssertionOptions { 43 | return &share.RawAuthenticatorGetAssertionOptions{ 44 | Version: define.WebAuthNAuthenticatorGetAssertionOptionsCurrentVersion, 45 | TimeoutMilliseconds: 60000, 46 | CredentialList: share.RawCredentials{ 47 | CredentialsLen: 0, 48 | CredentialsPtr: nil, 49 | }, 50 | Extensions: share.RawExtensions{ 51 | ExtensionsLen: 0, 52 | ExtensionsPrt: nil, 53 | }, 54 | AuthenticatorAttachment: define.WebAuthNAuthenticatorAttachmentAny, 55 | UserVerificationRequirement: define.WebAuthNUserVerificationRequirementDiscouraged, 56 | Flags: 0, 57 | U2fAppID: nil, 58 | IsU2fAppIDUsed: nil, 59 | CancellationID: nil, 60 | AllowCredentialList: &share.RawCredentialList{ 61 | Credentials: 0, 62 | CredentialsPtr: nil, 63 | }, 64 | CredLargeBlobOperation: define.WebAuthNCredLargeBlobOperationNone, 65 | CredLargeBlobLen: 0, 66 | CredLargeBlobPtr: 0, 67 | HMACSecretSaltValues: nil, 68 | } 69 | } 70 | 71 | // CreateClientData ... 72 | func CreateClientData(action, origin, challenge, HashAlgID string) (*share.RawCollectedClientData, error) { 73 | cd := share.RawCollectedClient{ 74 | Type: action, 75 | Challenge: challenge, 76 | Origin: origin, 77 | } 78 | cdjson, err := json.Marshal(cd) 79 | if err != nil { 80 | return nil, err 81 | } 82 | return &share.RawCollectedClientData{ 83 | Version: define.WebAuthNClientDataCurrentVersion, 84 | ClientDataJSONLen: uint32(len(cdjson)), 85 | ClientDataJSONPtr: &cdjson[0], 86 | HashAlgID: windows.StringToUTF16Ptr(HashAlgID), 87 | }, nil 88 | } 89 | -------------------------------------------------------------------------------- /cmd/showcert.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "crypto/dsa" 5 | "crypto/ecdsa" 6 | "crypto/ed25519" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "crypto/x509" 10 | "crypto/x509/pkix" 11 | "encoding/asn1" 12 | "fmt" 13 | "strings" 14 | 15 | "github.com/Ink-33/authn/api" 16 | "github.com/Ink-33/authn/api/define" 17 | "github.com/Ink-33/authn/api/share" 18 | ) 19 | 20 | func ShowCertInfo(c *api.WebAuthNClient) (func(), error) { 21 | id := make([]byte, 32) 22 | _, _ = rand.Read(id) 23 | u := &testUser{id} 24 | 25 | printCallAPI() 26 | opts := api.NewMakeCredOpts() 27 | opts.AttestationConveyancePreference = define.WebAuthNAttestationConveyancePreferenceDirect 28 | opts.AuthenticatorAttachment = define.WebAuthNAuthenticatorAttachmentAny 29 | a, err := c.MakeCredential(u, "local://demo.app", opts) 30 | if err != nil { 31 | return nil, err 32 | } 33 | return func() { printCertInfo(a.AttestationDecode.X5C) }, nil 34 | } 35 | 36 | func printCertInfo(x5c []share.X5C) { 37 | for i := range x5c { 38 | fmt.Printf("\nCertificate %v:\n", i+1) 39 | cert, err := x509.ParseCertificate(x5c[i]) 40 | if err != nil { 41 | fmt.Printf("cert: %v", err) 42 | } 43 | fmt.Printf("Version: %v (%#x)\n", cert.Version, cert.Version) 44 | fmt.Printf("Serial Number: %v (%#x)\n", cert.SerialNumber, cert.SerialNumber) 45 | fmt.Printf("Signature Algorithm: %v\n", cert.SignatureAlgorithm) 46 | fmt.Printf("Issuer: %v\n", cert.Issuer) 47 | fmt.Printf("Validity:\n\tNot Before: %v\n\tNot After: %v\n", 48 | cert.NotBefore, cert.NotAfter) 49 | fmt.Printf("Subject: %v\n", cert.Subject) 50 | 51 | ppk := &pkixPublicKey{} 52 | _, err = asn1.Unmarshal(cert.RawSubjectPublicKeyInfo, ppk) 53 | if err != nil { 54 | fmt.Printf("err: %v\n", err) 55 | return 56 | } 57 | pksize := 0 58 | name := "" 59 | switch pk := cert.PublicKey.(type) { 60 | case *rsa.PublicKey: 61 | pksize = pk.Size() 62 | name = "rsa" 63 | case *dsa.PublicKey: 64 | pksize = int(pk.Parameters.Q.Int64()) // maybe? 65 | name = "dsa" 66 | case *ecdsa.PublicKey: 67 | pksize = pk.Params().BitSize 68 | name = pk.Curve.Params().Name 69 | case ed25519.PublicKey: 70 | pksize = len(pk) // maybe? 71 | name = "ed25519" 72 | default: 73 | 74 | } 75 | 76 | fmt.Printf("Subject Public Key Info:\n\tPublic-Key: (%v bit)\n\tpub:\n%v\n\tAlgorithm Identifier: %v (%v)\n", 77 | pksize, buildBytesStr(ppk.BitString.Bytes, "\t\t"), ppk.Algo.Algorithm.String(), name) 78 | 79 | fmt.Printf("Signature Algorithm: %v\n\n%v", cert.SignatureAlgorithm, buildBytesStr(cert.Signature, "\t")) 80 | } 81 | } 82 | 83 | func buildBytesStr(b []byte, sep string) (str string) { 84 | l := len(b) / 18 85 | rest := func() int { 86 | if len(b)%18 != 0 { 87 | return 1 88 | } 89 | return 0 90 | }() 91 | 92 | strs := make([]string, l+rest+2) 93 | for i := 0; i < len(strs)-2; i++ { 94 | bs := b[i*18 : func() int { 95 | if (i+1)*18 > len(b) { 96 | return len(b) 97 | } 98 | return (i + 1) * 18 99 | }()] 100 | ss := make([]string, len(bs)) 101 | for k := range bs { 102 | ss[k] = fmt.Sprintf("%02x", bs[k]) 103 | } 104 | strs[i+1] = strings.Join(ss, ":") + "\n" 105 | } 106 | return strings.Join(strs, sep) 107 | } 108 | 109 | type pkixPublicKey struct { 110 | Algo pkix.AlgorithmIdentifier 111 | BitString asn1.BitString 112 | } 113 | -------------------------------------------------------------------------------- /api/share/Assertion.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import "github.com/Ink-33/authn/api/utils" 4 | 5 | // RawAssertion is authenticatorGetAssertion output. 6 | type RawAssertion struct { 7 | // Version of this structure, to allow for modifications in the future. 8 | Version uint32 // DWORD dwVersion; 9 | 10 | // Size of cbAuthenticatorData. 11 | AuthenticatorDataLen uint32 // DWORD cbAuthenticatorData; 12 | // Authenticator data that was created for this assertion. 13 | // _Field_size_bytes_(cbAuthenticatorData) 14 | AuthenticatorDataPtr *byte // PBYTE pbAuthenticatorData; 15 | 16 | // Size of pbSignature. 17 | SignatureLen uint32 // DWORD cbSignature; 18 | // Signature that was generated for this assertion. 19 | // _Field_size_bytes_(cbSignature) 20 | SignaturePtr *byte // PBYTE pbSignature; 21 | 22 | // Credential that was used for this assertion. 23 | Credential RawCredential // WEBAUTHN_CREDENTIAL Credential; 24 | 25 | // Size of User Id 26 | UserIDLen uint32 // DWORD cbUserId; 27 | // UserId 28 | // _Field_size_bytes_(cbUserId) 29 | UserIDPtr *byte // PBYTE pbUserId; 30 | 31 | // 32 | // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_2 33 | // 34 | 35 | Extensions RawExtensions // WEBAUTHN_EXTENSIONS Extensions 36 | 37 | // Size of pbCredLargeBlob 38 | CredLargeBlobLen uint32 // DWORD cbCredLargeBlob 39 | // _Field_size_bytes_ (cbCredLargeBlob) 40 | CredLargeBlobPtr *byte // PBYTE pbCredLargeBlob 41 | 42 | CredLargeBlobStatus uint32 // DWORD dwCredLargeBlobStatus 43 | 44 | // 45 | // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_3 46 | // 47 | 48 | HMACSecret *RawHMACSecretSalt //PWEBAUTHN_HMAC_SECRET_SALT pHmacSecret 49 | } 50 | 51 | // Assertion ... 52 | type Assertion struct { 53 | // Version of this structure, to allow for modifications in the future. 54 | Version uint32 55 | 56 | // Authenticator data that was created for this assertion. 57 | AuthenticatorData *AuthenticatorData 58 | 59 | // Signature that was generated for this assertion. 60 | Signature []byte 61 | 62 | // Credential that was used for this assertion. 63 | Credential Credential 64 | 65 | // UserID 66 | UserID []byte 67 | 68 | // 69 | // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_2 70 | // 71 | // TODO: warp this field 72 | Extensions RawExtensions 73 | 74 | CredLargeBlob []byte 75 | 76 | CredLargeBlobStatus uint32 // DWORD dwCredLargeBlobStatus 77 | 78 | // 79 | // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_3 80 | // 81 | 82 | HMACSecret *HMACSecretSalt //PWEBAUTHN_HMAC_SECRET_SALT pHmacSecret 83 | } 84 | 85 | // DeRaw ... 86 | func (c *RawAssertion) DeRaw() *Assertion { 87 | if c == nil { 88 | return nil 89 | } 90 | return &Assertion{ 91 | Version: c.Version, 92 | AuthenticatorData: func() *AuthenticatorData { 93 | d, _ := ParseAuthenticatorData( 94 | utils.BytesBuilder(c.AuthenticatorDataPtr, c.AuthenticatorDataLen), 95 | ) 96 | return d 97 | }(), 98 | Signature: utils.BytesBuilder(c.SignaturePtr, c.SignatureLen), 99 | Credential: *c.Credential.DeRaw(), 100 | UserID: utils.BytesBuilder(c.UserIDPtr, c.UserIDLen), 101 | Extensions: c.Extensions, 102 | CredLargeBlob: utils.BytesBuilder(c.CredLargeBlobPtr, c.CredLargeBlobLen), 103 | CredLargeBlobStatus: c.CredLargeBlobStatus, 104 | HMACSecret: c.HMACSecret.DeRaw(), 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /api/define/define.go: -------------------------------------------------------------------------------- 1 | package define 2 | 3 | // Ceremony type. 4 | const ( 5 | CollectedClientCeremonyCreate = "webauthn.create" 6 | CollectedClientCeremonyGet = "webauthn.get" 7 | ) 8 | 9 | // Options. 10 | const ( 11 | WebAuthNAuthenticatorAttachmentAny = 0 12 | WebAuthNAuthenticatorAttachmentPlatform = 1 13 | WebAuthNAuthenticatorAttachmentCrossPlatform = 2 14 | WebAuthNAuthenticatorAttachmentCrossPlatformU2Fv2 = 3 15 | 16 | WebAuthNUserVerificationRequirementAny = 0 17 | WebAuthNUserVerificationRequirementRequired = 1 18 | WebAuthNUserVerificationRequirementPreferred = 2 19 | WebAuthNUserVerificationRequirementDiscouraged = 3 20 | 21 | WebAuthNAttestationConveyancePreferenceAny = 0 22 | WebAuthNAttestationConveyancePreferenceNone = 1 23 | WebAuthNAttestationConveyancePreferenceIndirect = 2 24 | WebAuthNAttestationConveyancePreferenceDirect = 3 25 | 26 | WebAuthNEnterpriseAttestationNone = 0 27 | WebAuthNEnterpriseAttestationVendorFacilitated = 1 28 | WebAuthNEnterpriseAttestationPlatformManaged = 2 29 | 30 | WebAuthNLargeBlobSupportNone = 0 31 | WebAuthNLargeBlobSupportRequired = 1 32 | WebAuthNLargeBlobSupportPreferred = 2 33 | ) 34 | 35 | // version 36 | const ( 37 | WebAuthNRPEntityInformationCurrentVersion = 1 38 | WebAuthNUserEntityInformationCurrentVersion = 1 39 | WebAuthNClientDataCurrentVersion = 1 40 | WebAuthNCOSECredentialParameterCurrentVersion = 1 41 | WebAuthNCredentialCurrentVersion = 1 42 | WebAuthNCredentialDetailsCurrentVersion = 1 43 | 44 | WebAuthNCredentialAttestationVersion1 = 1 45 | WebAuthNCredentialAttestationVersion2 = 2 46 | WebAuthNCredentialAttestationVersion3 = 3 47 | WebAuthNCredentialAttestationVersion4 = 4 48 | WebAuthNCredentialAttestationCurrentVersion = WebAuthNCredentialAttestationVersion4 49 | 50 | WebAuthNCredentialEXCurrentVersion = 1 51 | 52 | WebAuthNCommonAttestationCurrentVersion = 1 53 | 54 | WebAuthNGetCredentialsOptionsVersion1 = 1 55 | WebAuthNGetCredentialsOptionsCurrentVersion = WebAuthNGetCredentialsOptionsVersion1 56 | 57 | WebauthnCredentialDetailsVersion1 = 1 58 | WebauthnCredentialDetailsCurrentVersion = WebauthnCredentialDetailsVersion1 59 | 60 | WebAuthNAuthenticatorMakeCredentialOptionsVersion1 = 1 61 | WebAuthNAuthenticatorMakeCredentialOptionsVersion2 = 2 62 | WebAuthNAuthenticatorMakeCredentialOptionsVersion3 = 3 63 | WebAuthNAuthenticatorMakeCredentialOptionsVersion4 = 4 64 | WebAuthNAuthenticatorMakeCredentialOptionsVersion5 = 5 65 | WebAuthNAuthenticatorMakeCredentialOptionsCurrentVersion = WebAuthNAuthenticatorMakeCredentialOptionsVersion5 66 | 67 | WebAuthNAuthenticatorGetAssertionOptionsVersion1 = 1 68 | WebAuthNAuthenticatorGetAssertionOptionsVersion2 = 2 69 | WebAuthNAuthenticatorGetAssertionOptionsVersion3 = 3 70 | WebAuthNAuthenticatorGetAssertionOptionsVersion4 = 4 71 | WebAuthNAuthenticatorGetAssertionOptionsVersion5 = 5 72 | WebAuthNAuthenticatorGetAssertionOptionsVersion6 = 6 73 | WebAuthNAuthenticatorGetAssertionOptionsCurrentVersion = WebAuthNAuthenticatorGetAssertionOptionsVersion6 74 | ) 75 | 76 | // credential parameters. 77 | const ( 78 | WebAuthNCredentialTypePublicKey = "public-key" 79 | 80 | WebAuthNCOSEAlgorithmECDSAP256WithSHA256 = -7 81 | WebAuthNCOSEAlgorithmECDSAP384WithSHA384 = -35 82 | WebAuthNCOSEAlgorithmECDSAP521WithSHA512 = -36 83 | 84 | WebAuthNCOSEAlgorithmRSASSAPKCS1V15WithSHA256 = -257 85 | WebAuthNCOSEAlgorithmRSASSAPKCS1V15WithSHA384 = -258 86 | WebAuthNCOSEAlgorithmRSASSAPKCS1V15WithSHA512 = -259 87 | 88 | WebAuthNCOSEAlgorithmRSAPSSWithSHA256 = -37 89 | WebAuthNCOSEAlgorithmRSAPSSWithSHA384 = -38 90 | WebAuthNCOSEAlgorithmRSAPSSWithSHA512 = -39 91 | ) 92 | 93 | // client data 94 | const ( 95 | WebAuthNHashAlgorithmSHA256 = "SHA-256" 96 | WebAuthNHashAlgorithmSHA384 = "SHA-384" 97 | WebAuthNHashAlgorithmSHA512 = "SHA-512" 98 | ) 99 | 100 | // credential attestation 101 | const ( 102 | WebAuthNAttestationTypePacked = "packed" 103 | WebAuthNAttestationTypeU2F = "fido-u2f" 104 | WebAuthNAttestationTypeTPM = "tpm" 105 | WebAuthNAttestationTypeNone = "none" 106 | WebAuthAttestationDecodeNone = 0 107 | WebAuthAttestationDecodeCommon = 1 108 | WebAuthAttestationVerTPM20 = "2.0" 109 | ) 110 | 111 | // credential extra information (Transports) 112 | const ( 113 | WebAuthNCTAPTransportUSB = 0x00000001 114 | WebAuthNCTAPTransportNFC = 0x00000002 115 | WebAuthNCTAPTransportBLE = 0x00000004 116 | WebAuthNCTAPTransportTest = 0x00000008 117 | WebAuthNCTAPTransportInternal = 0x00000010 118 | WebAuthNCTAPTransportFlagsMask = 0x0000001F 119 | ) 120 | 121 | // get attestation options 122 | const ( 123 | WebAuthNCredLargeBlobOperationNone = 0 124 | WebAuthNCredLargeBlobOperationGet = 1 125 | WebAuthNCredLargeBlobOperationSet = 2 126 | WebAuthNCredLargeBlobOperationDelete = 3 127 | 128 | /* 129 | Information about flags. 130 | */ 131 | WebAuthNAuthenticatorHMACSecretValuesFlag = 0x00100000 132 | ) 133 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/Ink-33/authn/api/define" 5 | "github.com/Ink-33/authn/api/raw" 6 | "github.com/Ink-33/authn/api/share" 7 | "github.com/Ink-33/authn/api/utils" 8 | "golang.org/x/sys/windows" 9 | ) 10 | 11 | type User interface { 12 | GetID() []byte 13 | GetName() string 14 | GetDisplayName() string 15 | GetIcon() string 16 | } 17 | 18 | // WebAuthNClient ... 19 | type WebAuthNClient struct { 20 | RPInfo share.RawRPInfo 21 | COSECredentialParameters []*share.RawCOSECredentialParameter 22 | makeCredOpts share.RawAuthenticatorMakeCredentialOptions 23 | challengeLength int 24 | Timeout uint32 25 | } 26 | 27 | // NewClient inits a WebAuthN client with the basic information. 28 | // 29 | // An rpID MUST be a valid domain string. See https://w3c.github.io/webauthn/#rp-id 30 | func NewClient(rpID, rpName, rpIcon string) *WebAuthNClient { 31 | c := &WebAuthNClient{ 32 | RPInfo: share.RawRPInfo{Version: define.WebAuthNRPEntityInformationCurrentVersion, ID: windows.StringToUTF16Ptr(rpID), Name: windows.StringToUTF16Ptr(rpName), Icon: windows.StringToUTF16Ptr(rpIcon)}, 33 | COSECredentialParameters: []*share.RawCOSECredentialParameter{}, 34 | challengeLength: 32, 35 | } 36 | c.SetDefaultCOSE() 37 | return c 38 | } 39 | 40 | // SetDefaultCOSE sets COSECredentialParameters to ES256 and RS256. 41 | func (c *WebAuthNClient) SetDefaultCOSE() { 42 | c.COSECredentialParameters = []*share.RawCOSECredentialParameter{ 43 | { 44 | Version: define.WebAuthNCOSECredentialParameterCurrentVersion, 45 | CredentialType: windows.StringToUTF16Ptr(define.WebAuthNCredentialTypePublicKey), 46 | Alg: define.WebAuthNCOSEAlgorithmECDSAP256WithSHA256, 47 | }, 48 | { 49 | Version: define.WebAuthNCOSECredentialParameterCurrentVersion, 50 | CredentialType: windows.StringToUTF16Ptr(define.WebAuthNCredentialTypePublicKey), 51 | Alg: define.WebAuthNCOSEAlgorithmRSASSAPKCS1V15WithSHA256, 52 | }, 53 | } 54 | } 55 | 56 | func (c *WebAuthNClient) MakeCredential(user User, origin string, opts *share.RawAuthenticatorMakeCredentialOptions) (*share.CredentialAttestation, error) { 57 | if len(c.COSECredentialParameters) == 0 { 58 | c.SetDefaultCOSE() 59 | } 60 | chanlleng, err := utils.CreateChallenge(c.challengeLength) 61 | if err != nil { 62 | return nil, err 63 | } 64 | cd, err := CreateClientData(define.CollectedClientCeremonyCreate, origin, chanlleng, define.WebAuthNHashAlgorithmSHA256) 65 | if err != nil { 66 | return nil, err 67 | } 68 | if opts == nil { 69 | opts = NewMakeCredOpts() 70 | } 71 | if c.Timeout != 0 { 72 | opts.TimeoutMilliseconds = c.Timeout 73 | } 74 | cancelID, err := utils.CreateCancelID() 75 | if err != nil { 76 | return nil, err 77 | } 78 | opts.CancellationID = &cancelID 79 | return raw.AuthenticatorMakeCredential(utils.GetHostWindow(), 80 | &c.RPInfo, 81 | &share.RawUserInfo{ 82 | Version: define.WebAuthNUserEntityInformationCurrentVersion, 83 | IDLen: uint32(len(user.GetID())), 84 | IDPtr: &user.GetID()[0], 85 | Name: windows.StringToUTF16Ptr(user.GetName()), 86 | Icon: windows.StringToUTF16Ptr(user.GetIcon()), 87 | DisplayName: windows.StringToUTF16Ptr(user.GetDisplayName()), 88 | }, 89 | &share.RawCOSECredentialParameters{ 90 | CredentialParametersLen: uint32(len(c.COSECredentialParameters)), 91 | CredentialParameters: c.COSECredentialParameters[0], 92 | }, 93 | cd, 94 | opts) 95 | } 96 | 97 | func (c *WebAuthNClient) GetAssertion(origin string, opts *share.RawAuthenticatorGetAssertionOptions) (*share.Assertion, error) { 98 | chanlleng, err := utils.CreateChallenge(c.challengeLength) 99 | if err != nil { 100 | return nil, err 101 | } 102 | cd, err := CreateClientData(define.CollectedClientCeremonyGet, origin, chanlleng, define.WebAuthNHashAlgorithmSHA256) 103 | if err != nil { 104 | return nil, err 105 | } 106 | return raw.AuthenticatorGetAssertion( 107 | utils.GetHostWindow(), 108 | c.RPInfo.ID, 109 | cd, 110 | opts, 111 | ) 112 | } 113 | 114 | // GetPlatformCredentialList gets the list of WebAuthN/FIDO2 credentials currently stored for the user. 115 | // 116 | // If rpid is not given, will use client rpid. 117 | // 118 | // If rpid is "-", will let this argurement empty when calling win32 api. 119 | func (c *WebAuthNClient) GetPlatformCredentialList(rpid string) ([]*share.CredentialDetails, error) { 120 | rpidu16 := (*uint16)(nil) 121 | switch rpid { 122 | case "-": 123 | rpidu16 = nil 124 | case "": 125 | rpidu16 = c.RPInfo.ID 126 | default: 127 | rpidu16 = windows.StringToUTF16Ptr(rpid) 128 | } 129 | return raw.GetPlatformCredentialList( 130 | &share.RawGetCredentialsOptions{ 131 | Version: define.WebAuthNGetCredentialsOptionsCurrentVersion, 132 | RPID: rpidu16, 133 | BrowserInPrivateMode: false, 134 | }, 135 | ) 136 | } 137 | 138 | // DeletePlatformCred removes a Public Key Credential Source stored on a Virtual Authenticator. 139 | func (c *WebAuthNClient) DeletePlatformCred(cbCred uint32, pbCred *byte) error { 140 | return raw.DeletePlatformCred(cbCred, pbCred) 141 | } 142 | -------------------------------------------------------------------------------- /api/raw/api.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | 3 | package raw 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "time" 9 | "unsafe" 10 | 11 | "github.com/Ink-33/authn/api/hresult" 12 | "github.com/Ink-33/authn/api/share" 13 | "github.com/Ink-33/authn/api/utils" 14 | "golang.org/x/sys/windows" 15 | ) 16 | 17 | var webauthn *windows.LazyDLL 18 | 19 | func init() { 20 | webauthn = windows.NewLazySystemDLL("webauthn.dll") 21 | err := webauthn.Load() 22 | if err != nil { 23 | fmt.Printf("Fatal: Cannot load webauthn.dll. %v", err) 24 | time.Sleep(5 * time.Second) 25 | os.Exit(1) 26 | } 27 | } 28 | 29 | // GetAPIVersionNumber returns supported webauthn version of the current system. 30 | func GetAPIVersionNumber() uintptr { 31 | ver, _, _ := webauthn.NewProc("WebAuthNGetApiVersionNumber").Call() 32 | return ver 33 | } 34 | 35 | // IsUserVerifyingPlatformAuthenticatorAvailable checks if the device owns platform authenticators. 36 | func IsUserVerifyingPlatformAuthenticatorAvailable() (is bool) { 37 | _, _, _ = webauthn.NewProc("WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable"). 38 | Call(uintptr(unsafe.Pointer(&is))) 39 | return is 40 | } 41 | 42 | // AuthenticatorMakeCredential ... 43 | func AuthenticatorMakeCredential(hWnd uintptr, 44 | rpInfo *share.RawRPInfo, 45 | userInfo *share.RawUserInfo, 46 | pkCredParams *share.RawCOSECredentialParameters, 47 | clientData *share.RawCollectedClientData, 48 | options *share.RawAuthenticatorMakeCredentialOptions) (attestation *share.CredentialAttestation, err error) { 49 | raw := &share.RawCredentialAttestation{} 50 | res, _, _ := webauthn.NewProc("WebAuthNAuthenticatorMakeCredential"). 51 | Call( 52 | hWnd, 53 | uintptr(unsafe.Pointer(rpInfo)), 54 | uintptr(unsafe.Pointer(userInfo)), 55 | uintptr(unsafe.Pointer(pkCredParams)), 56 | uintptr(unsafe.Pointer(clientData)), 57 | uintptr(unsafe.Pointer(options)), 58 | uintptr(unsafe.Pointer(&raw)), 59 | ) 60 | if res == 0 { 61 | defer freeCredentialAttestation(raw) 62 | return raw.DeRaw(), nil 63 | } 64 | return nil, hresult.HResult(res) 65 | } 66 | func freeCredentialAttestation(attestation *share.RawCredentialAttestation) { 67 | _, _, _ = webauthn.NewProc("WebAuthNFreeCredentialAttestation"). 68 | Call(uintptr(unsafe.Pointer(attestation))) 69 | } 70 | 71 | // GetErrorName returns the following Error Names: 72 | // 73 | // L"Success" - S_OK 74 | // L"InvalidStateError" - NTE_EXISTS 75 | // L"ConstraintError" - HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), 76 | // NTE_NOT_SUPPORTED, 77 | // NTE_TOKEN_KEYSET_STORAGE_FULL 78 | // L"NotSupportedError" - NTE_INVALID_PARAMETER 79 | // L"NotAllowedError" - NTE_DEVICE_NOT_FOUND, 80 | // NTE_NOT_FOUND, 81 | // HRESULT_FROM_WIN32(ERROR_CANCELLED), 82 | // NTE_USER_CANCELLED, 83 | // HRESULT_FROM_WIN32(ERROR_TIMEOUT) 84 | // L"UnknownError" - All other hr values 85 | func GetErrorName(hr hresult.HResult) string { 86 | msg, _, _ := webauthn.NewProc("WebAuthNGetErrorName"). 87 | Call(uintptr(hr)) 88 | return utils.UTF16PtrtoString((*uint16)(unsafe.Pointer(msg))) 89 | } 90 | 91 | // GetPlatformCredentialList ... 92 | func GetPlatformCredentialList(options *share.RawGetCredentialsOptions) (credList []*share.CredentialDetails, err error) { 93 | proc := webauthn.NewProc("WebAuthNGetPlatformCredentialList") 94 | err = proc.Find() 95 | if err != nil { 96 | return nil, err 97 | } 98 | raw := &share.RawCredentialDetailsList{} 99 | res, _, _ := proc.Call( 100 | uintptr(unsafe.Pointer(options)), 101 | uintptr(unsafe.Pointer(&raw)), 102 | ) 103 | if res == 0 { 104 | defer freePlatformCredentialList(raw) 105 | return raw.DeRaw(), nil 106 | } 107 | return nil, hresult.HResult(res) 108 | } 109 | 110 | // freePlatformCredentialList frees the allocation for the WEBAUTHN_CREDENTIAL_DETAILS_LIST. 111 | func freePlatformCredentialList(list *share.RawCredentialDetailsList) { 112 | _, _, _ = webauthn.NewProc("WebAuthNFreePlatformCredentialList"). 113 | Call(uintptr(unsafe.Pointer(list))) 114 | } 115 | 116 | // AuthenticatorGetAssertion produces an assertion signature representing 117 | // an assertion by the authenticator that the user has consented to a specific transaction, 118 | // such as logging in or completing a purchase. 119 | func AuthenticatorGetAssertion(hWnd uintptr, 120 | rpID *uint16, 121 | clientData *share.RawCollectedClientData, 122 | opts *share.RawAuthenticatorGetAssertionOptions) (assertion *share.Assertion, err error) { 123 | raw := &share.RawAssertion{} 124 | res, _, _ := webauthn.NewProc("WebAuthNAuthenticatorGetAssertion"). 125 | Call( 126 | hWnd, 127 | uintptr(unsafe.Pointer(rpID)), 128 | uintptr(unsafe.Pointer(clientData)), 129 | uintptr(unsafe.Pointer(opts)), 130 | uintptr(unsafe.Pointer(&raw)), 131 | ) 132 | if res == 0 { 133 | defer freeAssertion(raw) 134 | return raw.DeRaw(), nil 135 | } 136 | return nil, hresult.HResult(res) 137 | } 138 | 139 | // FreeAssertion frees an assertion previously allocated by calling WebAuthNAuthenticatorGetAssertion. 140 | func freeAssertion(assertion *share.RawAssertion) { 141 | _, _, _ = webauthn.NewProc("WebAuthNFreeAssertion"). 142 | Call(uintptr(unsafe.Pointer(assertion))) 143 | } 144 | 145 | // DeletePlatformCred removes a Public Key Credential Source stored on a Virtual Authenticator. 146 | func DeletePlatformCred(cbCred uint32, pbCred *byte) error { 147 | proc := webauthn.NewProc("WebAuthNDeletePlatformCredential") 148 | err := proc.Find() 149 | if err != nil { 150 | return err 151 | } 152 | res, _, _ := proc.Call(uintptr(cbCred), uintptr(unsafe.Pointer(pbCred))) 153 | if res == 0 { 154 | return nil 155 | } 156 | return hresult.HResult(res) 157 | } 158 | -------------------------------------------------------------------------------- /api/share/Attestation.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "github.com/Ink-33/authn/api/define" 7 | "github.com/Ink-33/authn/api/utils" 8 | ) 9 | 10 | // RawCredentialAttestation info. 11 | type RawCredentialAttestation struct { 12 | // Version of this structure, to allow for modifications in the future. 13 | Version uint32 // DWORD dwVersion; 14 | 15 | // Attestation format type 16 | FormatType *uint16 // PCWSTR pwszFormatType 17 | 18 | // Size of cbAuthenticatorData. 19 | AuthenticatorDataLen uint32 // DWORD cbAuthenticatorData 20 | // Authenticator data that was created for this credential. 21 | // _Field_size_bytes_ (cbAuthenticatorData) 22 | AuthenticatorDataPtr *byte // PBYTE pbAuthenticatorData 23 | 24 | // Size of CBOR encoded attestation information 25 | //0 => encoded as CBOR null value. 26 | AttestationLen uint32 // DWORD cbAttestation 27 | //Encoded CBOR attestation information 28 | // _Field_size_bytes_ (cbAttestation) 29 | AttestationPtr *byte // PBYTE pbAttestation 30 | 31 | AttestationDecodeType uint32 // DWORD dwAttestationDecodeType 32 | // Following depends on the dwAttestationDecodeType 33 | // WEBAUTHN_ATTESTATION_DECODE_NONE 34 | // NULL - not able to decode the CBOR attestation information 35 | // WEBAUTHN_ATTESTATION_DECODE_COMMON 36 | // PWEBAUTHN_COMMON_ATTESTATION; 37 | AttestationDecode unsafe.Pointer // PVOID pvAttestationDecode 38 | 39 | // The CBOR encoded Attestation Object to be returned to the RP. 40 | AttestationObjectLen uint32 // DWORD cbAttestationObject 41 | // _Field_size_bytes_ (cbAttestationObject) 42 | AttestationObjectPtr *byte // PBYTE pbAttestationObject 43 | 44 | // The CredentialId bytes extracted from the Authenticator Data. 45 | // Used by Edge to return to the RP. 46 | CredentialIDLen uint32 // DWORD cbCredentialId 47 | // _Field_size_bytes_ (cbCredentialId) 48 | CredentialIDPtr *byte // PBYTE pbCredentialId 49 | 50 | // 51 | // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 52 | // 53 | 54 | Extensions *RawExtensions // WEBAUTHN_EXTENSIONS Extensions 55 | 56 | // 57 | // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3 58 | // 59 | 60 | // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to 61 | // the transport that was used. 62 | UsedTransport uint32 // DWORD dwUsedTransport 63 | 64 | // 65 | // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 66 | // 67 | 68 | EpAtt bool // BOOL bEpAtt 69 | LargeBlobSupported bool // BOOL bLargeBlobSupported 70 | ResidentKey bool // BOOL bResidentKey 71 | } 72 | 73 | // CredentialAttestation info. 74 | type CredentialAttestation struct { 75 | // Version of this structure, to allow for modifications in the future. 76 | Version uint32 // DWORD dwVersion; 77 | 78 | // Attestation format type 79 | FormatType string // PCWSTR pwszFormatType 80 | 81 | // Authenticator data that was created for this credential. 82 | AuthenticatorData *AuthenticatorData // PBYTE pbAuthenticatorData 83 | 84 | //Encoded CBOR attestation information 85 | Attestation []byte // PBYTE pbAttestation 86 | 87 | // Following depends on the dwAttestationDecodeType 88 | // WEBAUTHN_ATTESTATION_DECODE_NONE 89 | // NULL - not able to decode the CBOR attestation information 90 | // WEBAUTHN_ATTESTATION_DECODE_COMMON 91 | // PWEBAUTHN_COMMON_ATTESTATION; 92 | AttestationDecode *CommonAttestation // PVOID pvAttestationDecode 93 | 94 | // The CBOR encoded Attestation Object to be returned to the RP. 95 | AttestationObject []byte // PBYTE pbAttestationObject 96 | 97 | // The CredentialId bytes extracted from the Authenticator Data. 98 | // Used by Edge to return to the RP. 99 | CredentialID []byte // PBYTE pbCredentialId 100 | 101 | // 102 | // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 103 | // 104 | 105 | Extensions Extensions // WEBAUTHN_EXTENSIONS Extensions 106 | 107 | // 108 | // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3 109 | // 110 | 111 | // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to 112 | // the transport that was used. 113 | UsedTransport uint32 // DWORD dwUsedTransport 114 | 115 | // 116 | // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 117 | // 118 | 119 | EpAtt bool // BOOL bEpAtt 120 | LargeBlobSupported bool // BOOL bLargeBlobSupported 121 | ResidentKey bool // BOOL bResidentKey 122 | } 123 | 124 | func (c *RawCredentialAttestation) DeRaw() *CredentialAttestation { 125 | if c == nil { 126 | return nil 127 | } 128 | decode := (*CommonAttestation)(nil) 129 | switch c.AttestationDecodeType { 130 | case define.WebAuthAttestationDecodeNone: 131 | decode = nil 132 | case define.WebAuthAttestationDecodeCommon: 133 | decode = (*RawCommonAttestation)(c.AttestationDecode).DeRaw() 134 | } 135 | return &CredentialAttestation{ 136 | Version: c.Version, 137 | FormatType: utils.UTF16PtrtoString(c.FormatType), 138 | AuthenticatorData: func() *AuthenticatorData { 139 | d, _ := ParseAuthenticatorData( 140 | utils.BytesBuilder(c.AuthenticatorDataPtr, c.AuthenticatorDataLen), 141 | ) 142 | return d 143 | }(), 144 | Attestation: utils.BytesBuilder(c.AttestationPtr, c.AttestationLen), 145 | AttestationDecode: decode, 146 | AttestationObject: utils.BytesBuilder(c.AttestationObjectPtr, c.AttestationObjectLen), 147 | CredentialID: utils.BytesBuilder(c.CredentialIDPtr, c.CredentialIDLen), 148 | Extensions: c.Extensions.DeRaw(), 149 | UsedTransport: c.UsedTransport, 150 | EpAtt: c.EpAtt, 151 | LargeBlobSupported: c.LargeBlobSupported, 152 | ResidentKey: c.ResidentKey, 153 | } 154 | } 155 | --------------------------------------------------------------------------------