[
  {
    "path": ".gitignore",
    "content": "\r\n# Created by https://www.gitignore.io/api/go,intellij+all,visualstudiocode\r\n\r\n### Go ###\r\n# Binaries for programs and plugins\r\n*.exe\r\n*.exe~\r\n*.dll\r\n*.so\r\n*.dylib\r\n\r\n# Test binary, build with `go test -c`\r\n*.test\r\n\r\n# Output of the go coverage tool, specifically when used with LiteIDE\r\n*.out\r\n\r\n### Go Patch ###\r\n/vendor/\r\n/Godeps/\r\n\r\n### Intellij+all ###\r\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm\r\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\r\n\r\n# User-specific stuff\r\n.idea/**/workspace.xml\r\n.idea/**/tasks.xml\r\n.idea/**/usage.statistics.xml\r\n.idea/**/dictionaries\r\n.idea/**/shelf\r\n\r\n# Generated files\r\n.idea/**/contentModel.xml\r\n\r\n# Sensitive or high-churn files\r\n.idea/**/dataSources/\r\n.idea/**/dataSources.ids\r\n.idea/**/dataSources.local.xml\r\n.idea/**/sqlDataSources.xml\r\n.idea/**/dynamic.xml\r\n.idea/**/uiDesigner.xml\r\n.idea/**/dbnavigator.xml\r\n\r\n# Gradle\r\n.idea/**/gradle.xml\r\n.idea/**/libraries\r\n\r\n# Gradle and Maven with auto-import\r\n# When using Gradle or Maven with auto-import, you should exclude module files,\r\n# since they will be recreated, and may cause churn.  Uncomment if using\r\n# auto-import.\r\n# .idea/modules.xml\r\n# .idea/*.iml\r\n# .idea/modules\r\n\r\n# CMake\r\ncmake-build-*/\r\n\r\n# Mongo Explorer plugin\r\n.idea/**/mongoSettings.xml\r\n\r\n# File-based project format\r\n*.iws\r\n\r\n# IntelliJ\r\nout/\r\n\r\n# mpeltonen/sbt-idea plugin\r\n.idea_modules/\r\n\r\n# JIRA plugin\r\natlassian-ide-plugin.xml\r\n\r\n# Cursive Clojure plugin\r\n.idea/replstate.xml\r\n\r\n# Crashlytics plugin (for Android Studio and IntelliJ)\r\ncom_crashlytics_export_strings.xml\r\ncrashlytics.properties\r\ncrashlytics-build.properties\r\nfabric.properties\r\n\r\n# Editor-based Rest Client\r\n.idea/httpRequests\r\n\r\n### Intellij+all Patch ###\r\n# Ignores the whole .idea folder and all .iml files\r\n# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360\r\n\r\n.idea/\r\n\r\n# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023\r\n\r\n*.iml\r\nmodules.xml\r\n.idea/misc.xml\r\n*.ipr\r\n\r\n### VisualStudioCode ###\r\n.vscode/*\r\n!.vscode/settings.json\r\n!.vscode/tasks.json\r\n!.vscode/launch.json\r\n!.vscode/extensions.json\r\n\r\n\r\n# End of https://www.gitignore.io/api/go,intellij+all,visualstudiocode"
  },
  {
    "path": ".travis.yml",
    "content": "language: go\r\ngo:\r\n  - \"1.11.x\"\r\n\r\ninstall: true\r\n\r\nscript:\r\n  - env GO111MODULE=on go build ./...\r\n  - env GO111MODULE=on go test ./..."
  },
  {
    "path": "LICENSE",
    "content": "MIT License\r\n\r\nCopyright (c) 2020 Koen Vlaswinkel\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# webauthn : Web Authentication API in Go\r\n\r\n## Overview [![GoDoc](https://godoc.org/github.com/koesie10/webauthn?status.svg)](https://godoc.org/github.com/koesie10/webauthn) [![Build Status](https://travis-ci.org/koesie10/webauthn.svg?branch=master)](https://travis-ci.org/koesie10/webauthn)\r\n\r\nThis project provides a low-level and a high-level API to use the [Web Authentication API](https://www.w3.org/TR/webauthn/) (WebAuthn).\r\n\r\n[Demo](https://github.com/koesie10/webauthn-demo)\r\n\r\n## Install\r\n\r\n```\r\ngo get github.com/koesie10/webauthn\r\n```\r\n\r\n## Attestation\r\n\r\nBy default, this library does not support any attestation statement formats. To use the default attestation formats,\r\nyou will need to import `github.com/koesie10/webauthn/attestation` or any of its subpackages if you would just like\r\nto support some attestation statement formats.\r\n\r\nPlease note that the Android SafetyNet attestation statement format depends on\r\n[`gopkg.in/square/go-jose.v2`](https://github.com/square/go-jose), which means that this package will be imported\r\nwhen you import either `github.com/koesie10/webauthn/attestation` or\r\n`github.com/koesie10/webauthn/attestation/androidsafetynet`.\r\n\r\n## High-level API\r\n\r\nThe high-level API can be used with the `net/http` package and simplifies the low-level API. It is located in the `webauthn` subpackage. It is intended\r\nfor use with e.g. `fetch` or `XMLHttpRequest` JavaScript clients.\r\n\r\nFirst, make sure your user entity implements [`User`](https://godoc.org/github.com/koesie10/webauthn/webauthn#User). Then, create a new entity\r\nimplements [`Authenticator`](https://godoc.org/github.com/koesie10/webauthn/webauthn#Authenticator) that stores each authenticator the user\r\nregisters.\r\n\r\nThen, either make your existing repository implement [`AuthenticatorStore`](https://godoc.org/github.com/koesie10/webauthn/webauthn#AuthenticatorStore)\r\nor create a new repository.\r\n\r\nFinally, you can create the main [`WebAuthn`](https://godoc.org/github.com/koesie10/webauthn/webauthn#WebAuthn) struct supplying the\r\n[`Config`](https://godoc.org/github.com/koesie10/webauthn/webauthn#Config) options:\r\n\r\n```golang\r\nw, err := webauthn.New(&webauthn.Config{\r\n    // A human-readable identifier for the relying party (i.e. your app), intended only for display.\r\n    RelyingPartyName:   \"webauthn-demo\",\r\n    // Storage for the authenticator.\r\n    AuthenticatorStore: storage,\r\n})\t\t\r\n```\r\n\r\nThen, you can use the methods defined, such as [`StartRegistration`](https://godoc.org/github.com/koesie10/webauthn/webauthn#WebAuthn.StartRegistration)\r\nto handle registration and login. Every handler requires a [`Session`](https://godoc.org/github.com/koesie10/webauthn/webauthn#Session), which stores\r\nintermediate registration/login data. If you use [`gorilla/sessions`](https://github.com/gorilla/sessions), use\r\n[`webauthn.WrapMap`](https://godoc.org/github.com/koesie10/webauthn/webauthn#WrapMap)`(session.Values)`. Read the documentation for complete information\r\non what parameters need to be passed and what values are returned.\r\n\r\nFor example, a handler for finishing the registration might look like this:\r\n\r\n```golang\r\nfunc (r *http.Request, rw http.ResponseWriter) {\r\n    ctx := r.Context()\r\n\r\n    // Get the user in some way, in this case from the context\r\n    user, ok := UserFromContext(ctx)\r\n    if !ok {\r\n        rw.WriteHeader(http.StatusForbidden)\r\n        return\r\n    }\r\n\r\n    // Get or create a session in some way, in this case from the context\r\n    sess := SessionFromContext(ctx)\r\n\r\n    // Then call FinishRegistration to register the authenticator to the user\r\n    h.webauthn.FinishRegistration(r, rw, user, webauthn.WrapMap(sess))\r\n}\r\n```\r\n\r\nA complete demo application using the high-level API which implements all of these interfaces and stores data in memory is available\r\n[here](https://github.com/koesie10/webauthn-demo).\r\n\r\n## JavaScript examples\r\n\r\n[This class](webauthn.js) is an example that can be used to handle the registration and login phases. It can be used as follows:\r\n\r\n```javascript\r\nconst w = new WebAuthn();\r\n\r\n// Registration\r\nw.register().then(() => {\r\n    alert('This authenticator has been registered.');\r\n}).catch(err => {\r\n    console.error(err);\r\n    alert('Failed to register: ' + err);\r\n});\r\n\r\n// Login\r\nw.login().then(() => {\r\n    alert('You have been logged in.');\r\n}).catch(err => {\r\n    console.error(err);\r\n    alert('Failed to login: ' + err);\r\n});\r\n```\r\n\r\nOr, with latest `async/await` paradigm:\r\n\r\n```javascript\r\nconst w = new WebAuthn();\r\n\r\n// Registration\r\ntry {\r\n    await w.register();\r\n    alert('This authenticator has been registered.');\r\n} catch (err) {\r\n    console.error(err)\r\n    alert('Failed to register: ' + err);\r\n}\r\n\r\n// Login\r\ntry {\r\n    await w.login();\r\n    alert('You have been logged in.');\r\n} catch(err) {\r\n    console.error(err);\r\n    alert('Failed to login: ' + err);\r\n}\r\n```\r\n\r\n## Low-level API\r\n\r\nThe low-level closely resembles the specification and the high-level API should be preferred. However, if you would like to use the low-level\r\nAPI, the main entry points are:\r\n\r\n* [`ParseAttestationResponse`](https://godoc.org/github.com/koesie10/webauthn/protocol#ParseAttestationResponse)\r\n* [`IsValidAttestation`](https://godoc.org/github.com/koesie10/webauthn/protocol#IsValidAttestation)\r\n* [`ParseAssertionResponse`](https://godoc.org/github.com/koesie10/webauthn/protocol#ParseAssertionResponse)\r\n* [`IsValidAssertion`](https://godoc.org/github.com/koesie10/webauthn/protocol#IsValidAssertion)\r\n\r\n## License\r\n\r\nMIT.\r\n"
  },
  {
    "path": "attestation/androidsafetynet/androidsafetynet.go",
    "content": "// androidsafetynet implements the Android SafetyNet (WebAuthn spec section 8.5) attestation statement format\npackage androidsafetynet\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"gopkg.in/square/go-jose.v2\"\n\n\t\"github.com/koesie10/webauthn/protocol\"\n)\n\n// Now is used to overwrite the time at which the certificate is verified and is just used for tests.\nvar now = time.Now\n\nfunc init() {\n\tprotocol.RegisterFormat(\"android-safetynet\", verifyAndroidSafetynet)\n}\n\ntype AndroidSafetyNetAttestionResponse struct {\n\tNonce                      []byte   `json:\"nonce\"`\n\tTimestampMs                int64    `json:\"timestampMs\"`\n\tApkPackageName             string   `json:\"apkPackageName\"`\n\tApkDigestSha256            []byte   `json:\"apkDigestSha256\"`\n\tCtsProfileMatch            bool     `json:\"ctsProfileMatch\"`\n\tApkCertificateDigestSha256 [][]byte `json:\"apkCertificateDigestSha256\"`\n\tBasicIntegrity             bool     `json:\"basicIntegrity\"`\n}\n\nfunc verifyAndroidSafetynet(a protocol.Attestation, clientDataHash []byte) error {\n\t// Verify that response is a valid SafetyNet response of version ver.\n\trawVer, ok := a.AttStmt[\"ver\"]\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"missing ver for android-safetynet\")\n\t}\n\tver, ok := rawVer.(string)\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid ver for android-safetynet, is of invalid type %T\", rawVer)\n\t}\n\n\tif ver == \"\" {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"invalid ver for android-safetynet\")\n\t}\n\n\trawResponse, ok := a.AttStmt[\"response\"]\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"missing response for android-safetynet\")\n\t}\n\tresponseBytes, ok := rawResponse.([]byte)\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid response for android-safetynet, is of invalid type %T\", responseBytes)\n\t}\n\n\tresponse, err := jose.ParseSigned(string(responseBytes))\n\tif err != nil {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid response for android-safetynet: %v\", err)\n\t}\n\n\tif len(response.Signatures) != 1 {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid response for android-safetynet: more or less than 1 signature\")\n\t}\n\n\t// Verify that the attestation certificate is issued to the hostname \"attest.android.com\"\n\tcert, err := response.Signatures[0].Protected.Certificates(x509.VerifyOptions{\n\t\tDNSName:     \"attest.android.com\",\n\t\tCurrentTime: now(),\n\t})\n\tif err != nil {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid response for android-safetynet: %v\", err).WithCause(err)\n\t}\n\tleaf := cert[0][0]\n\n\tpayload, err := response.Verify(leaf.PublicKey)\n\tif err != nil {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid response for android-safetynet: %v\", err).WithCause(err)\n\t}\n\n\tattestationResponse := AndroidSafetyNetAttestionResponse{}\n\n\tif err := json.Unmarshal(payload, &attestationResponse); err != nil {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid response for android-safetynet: %v\", err)\n\t}\n\n\t// Verify that the nonce in the response is identical to the SHA-256 hash of the concatenation of authenticatorData and clientDataHash.\n\tnonceBytes := append(a.AuthData.Raw, clientDataHash...)\n\texpectedNonce := sha256.Sum256(nonceBytes)\n\n\tif !bytes.Equal(expectedNonce[:], attestationResponse.Nonce) {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid response for android-safetynet: invalid nonce\")\n\t}\n\n\t// Verify that the ctsProfileMatch attribute in the payload of response is true.\n\tif !attestationResponse.CtsProfileMatch {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid response for android-safetynet: does not match CTS profile\")\n\t}\n\n\t// If successful, return attestation type Basic with the attestation trust path set to the above attestation certificate.\n\treturn nil\n}\n"
  },
  {
    "path": "attestation/androidsafetynet/androidsafetynet_test.go",
    "content": "package androidsafetynet\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/koesie10/webauthn/protocol\"\n)\n\nfunc TestIsValidAttestation(t *testing.T) {\n\tnow = func() time.Time {\n\t\treturn time.Date(2018, 10, 24, 18, 39, 21, 0, time.UTC)\n\t}\n\n\tfor i := range attestationRequests {\n\t\tt.Run(fmt.Sprintf(\"Run %d\", i), func(t *testing.T) {\n\t\t\tr := protocol.CredentialCreationOptions{}\n\t\t\tif err := json.Unmarshal([]byte(attestationRequests[i]), &r); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tb := protocol.AttestationResponse{}\n\t\t\tif err := json.Unmarshal([]byte(attestationResponses[i]), &b); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tp, err := protocol.ParseAttestationResponse(b)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\td, err := protocol.IsValidAttestation(p, r.PublicKey.Challenge, \"\", \"\")\n\t\t\tif err != nil {\n\t\t\t\te := protocol.ToWebAuthnError(err)\n\t\t\t\tt.Fatal(fmt.Sprintf(\"%s, %s: %s\", e.Name, e.Description, e.Debug))\n\t\t\t}\n\n\t\t\tif !d {\n\t\t\t\tt.Fatal(\"is not valid\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar attestationRequests = []string{\n\t`{\"publicKey\":{\"rp\":{\"name\":\"webauthn-demo\"},\"user\":{\"name\":\"Bewus\",\"id\":\"QmV3dXM=\",\"displayName\":\"koen\"},\"challenge\":\"d3cY1I6n1ar6gLpDEhTi5nBgP1xwIGsb6HM/NR8PK1o=\",\"pubKeyCredParams\":[{\"type\":\"public-key\",\"alg\":-7}],\"timeout\":30000,\"authenticatorSelection\":{\"requireResidentKey\":false},\"attestation\":\"direct\"}}`,\n}\n\nvar attestationResponses = []string{\n\t`{\"id\":\"ARKBRFD84uLN6qG_rHsV0K2Bh9Lj3_HaJsXdC_DpPslKO6ZWmD38-hz90Lf_MzELErMa9AqR21Sr9brNzE2un1U\",\"rawId\":\"ARKBRFD84uLN6qG/rHsV0K2Bh9Lj3/HaJsXdC/DpPslKO6ZWmD38+hz90Lf/MzELErMa9AqR21Sr9brNzE2un1U=\",\"response\":{\"attestationObject\":\"o2NmbXRxYW5kcm9pZC1zYWZldHluZXRnYXR0U3RtdKJjdmVyaDE0MzY2MDE5aHJlc3BvbnNlWRS9ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbmcxWXlJNld5Sk5TVWxHYTJwRFEwSkljV2RCZDBsQ1FXZEpVVkpZY205T01GcFBaRkpyUWtGQlFVRkJRVkIxYm5wQlRrSm5hM0ZvYTJsSE9YY3dRa0ZSYzBaQlJFSkRUVkZ6ZDBOUldVUldVVkZIUlhkS1ZsVjZSV1ZOUW5kSFFURlZSVU5vVFZaU01qbDJXako0YkVsR1VubGtXRTR3U1VaT2JHTnVXbkJaTWxaNlRWSk5kMFZSV1VSV1VWRkVSWGR3U0ZaR1RXZFJNRVZuVFZVNGVFMUNORmhFVkVVMFRWUkJlRTFFUVROTlZHc3dUbFp2V0VSVVJUVk5WRUYzVDFSQk0wMVVhekJPVm05M1lrUkZURTFCYTBkQk1WVkZRbWhOUTFaV1RYaEZla0ZTUW1kT1ZrSkJaMVJEYTA1b1lrZHNiV0l6U25WaFYwVjRSbXBCVlVKblRsWkNRV05VUkZVeGRtUlhOVEJaVjJ4MVNVWmFjRnBZWTNoRmVrRlNRbWRPVmtKQmIxUkRhMlIyWWpKa2MxcFRRazFVUlUxNFIzcEJXa0puVGxaQ1FVMVVSVzFHTUdSSFZucGtRelZvWW0xU2VXSXliR3RNYlU1MllsUkRRMEZUU1hkRVVWbEtTMjlhU1doMlkwNUJVVVZDUWxGQlJHZG5SVkJCUkVORFFWRnZRMmRuUlVKQlRtcFlhM293WlVzeFUwVTBiU3N2UnpWM1QyOHJXRWRUUlVOeWNXUnVPRGh6UTNCU04yWnpNVFJtU3pCU2FETmFRMWxhVEVaSWNVSnJOa0Z0V2xaM01rczVSa2N3VHpseVVsQmxVVVJKVmxKNVJUTXdVWFZ1VXpsMVowaEROR1ZuT1c5MmRrOXRLMUZrV2pKd09UTllhSHAxYmxGRmFGVlhXRU40UVVSSlJVZEtTek5UTW1GQlpucGxPVGxRVEZNeU9XaE1ZMUYxV1ZoSVJHRkROMDlhY1U1dWIzTnBUMGRwWm5NNGRqRnFhVFpJTDNob2JIUkRXbVV5YkVvck4wZDFkSHBsZUV0d2VIWndSUzkwV2xObVlsazVNRFZ4VTJ4Q2FEbG1jR293TVRWamFtNVJSbXRWYzBGVmQyMUxWa0ZWZFdWVmVqUjBTMk5HU3pSd1pYWk9UR0Y0UlVGc0swOXJhV3hOZEVsWlJHRmpSRFZ1Wld3MGVFcHBlWE0wTVROb1lXZHhWekJYYUdnMVJsQXpPV2hIYXpsRkwwSjNVVlJxWVhwVGVFZGtkbGd3YlRaNFJsbG9hQzh5VmsxNVdtcFVORXQ2VUVwRlEwRjNSVUZCWVU5RFFXeG5kMmRuU2xWTlFUUkhRVEZWWkVSM1JVSXZkMUZGUVhkSlJtOUVRVlJDWjA1V1NGTlZSVVJFUVV0Q1oyZHlRbWRGUmtKUlkwUkJWRUZOUW1kT1ZraFNUVUpCWmpoRlFXcEJRVTFDTUVkQk1WVmtSR2RSVjBKQ1VYRkNVWGRIVjI5S1FtRXhiMVJMY1hWd2J6UlhObmhVTm1veVJFRm1RbWRPVmtoVFRVVkhSRUZYWjBKVFdUQm1hSFZGVDNaUWJTdDRaMjU0YVZGSE5rUnlabEZ1T1V0NlFtdENaMmR5UW1kRlJrSlJZMEpCVVZKWlRVWlpkMHAzV1VsTGQxbENRbEZWU0UxQlIwZEhNbWd3WkVoQk5reDVPWFpaTTA1M1RHNUNjbUZUTlc1aU1qbHVUREprTUdONlJuWk5WRUZ5UW1kbmNrSm5SVVpDVVdOM1FXOVpabUZJVWpCalJHOTJURE5DY21GVE5XNWlNamx1VERKa2VtTnFTWFpTTVZKVVRWVTRlRXh0VG5sa1JFRmtRbWRPVmtoU1JVVkdha0ZWWjJoS2FHUklVbXhqTTFGMVdWYzFhMk50T1hCYVF6VnFZakl3ZDBsUldVUldVakJuUWtKdmQwZEVRVWxDWjFwdVoxRjNRa0ZuU1hkRVFWbExTM2RaUWtKQlNGZGxVVWxHUVhwQmRrSm5UbFpJVWpoRlMwUkJiVTFEVTJkSmNVRm5hR2cxYjJSSVVuZFBhVGgyV1ROS2MweHVRbkpoVXpWdVlqSTVia3d3WkZWVmVrWlFUVk0xYW1OdGQzZG5aMFZGUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpKU0RGQ1NVaDVRVkJCUVdSM1EydDFVVzFSZEVKb1dVWkpaVGRGTmt4TldqTkJTMUJFVjFsQ1VHdGlNemRxYW1RNE1FOTVRVE5qUlVGQlFVRlhXbVJFTTFCTVFVRkJSVUYzUWtsTlJWbERTVkZEVTFwRFYyVk1Tblp6YVZaWE5rTm5LMmRxTHpsM1dWUktVbnAxTkVocGNXVTBaVmswWXk5dGVYcHFaMGxvUVV4VFlta3ZWR2g2WTNweGRHbHFNMlJyTTNaaVRHTkpWek5NYkRKQ01HODNOVWRSWkdoTmFXZGlRbWRCU0ZWQlZtaFJSMjFwTDFoM2RYcFVPV1ZIT1ZKTVNTdDRNRm95ZFdKNVdrVldla0UzTlZOWlZtUmhTakJPTUVGQlFVWnRXRkU1ZWpWQlFVRkNRVTFCVW1wQ1JVRnBRbU5EZDBFNWFqZE9WRWRZVURJM09IbzBhSEl2ZFVOSWFVRkdUSGx2UTNFeVN6QXJlVXhTZDBwVlltZEpaMlk0WjBocWRuQjNNbTFDTVVWVGFuRXlUMll6UVRCQlJVRjNRMnR1UTJGRlMwWlZlVm8zWmk5UmRFbDNSRkZaU2t0dldrbG9kbU5PUVZGRlRFSlJRVVJuWjBWQ1FVazVibFJtVWt0SlYyZDBiRmRzTTNkQ1REVTFSVlJXTm10aGVuTndhRmN4ZVVGak5VUjFiVFpZVHpReGExcDZkMG8yTVhkS2JXUlNVbFF2VlhORFNYa3hTMFYwTW1Nd1JXcG5iRzVLUTBZeVpXRjNZMFZYYkV4UldUSllVRXg1Um1wclYxRk9ZbE5vUWpGcE5GY3lUbEpIZWxCb2RETnRNV0kwT1doaWMzUjFXRTAyZEZnMVEzbEZTRzVVYURoQ2IyMDBMMWRzUm1sb2VtaG5iamd4Ukd4a2IyZDZMMHN5VlhkTk5sTTJRMEl2VTBWNGEybFdabllyZW1KS01ISnFkbWM1TkVGc1pHcFZabFYzYTBrNVZrNU5ha1ZRTldVNGVXUkNNMjlNYkRabmJIQkRaVVkxWkdkbVUxZzBWVGw0TXpWdmFpOUpTV1F6VlVVdlpGQndZaTl4WjBkMmMydG1aR1Y2ZEcxVmRHVXZTMU50Y21sM1kyZFZWMWRsV0daVVlra3plbk5wYTNkYVltdHdiVkpaUzIxcVVHMW9kalJ5YkdsNlIwTkhkRGhRYmpod2NUaE5Na3RFWmk5UU0ydFdiM1F6WlRFNFVUMGlMQ0pOU1VsRlUycERRMEY2UzJkQmQwbENRV2RKVGtGbFR6QnRjVWRPYVhGdFFrcFhiRkYxUkVGT1FtZHJjV2hyYVVjNWR6QkNRVkZ6UmtGRVFrMU5VMEYzU0dkWlJGWlJVVXhGZUdSSVlrYzVhVmxYZUZSaFYyUjFTVVpLZG1JelVXZFJNRVZuVEZOQ1UwMXFSVlJOUWtWSFFURlZSVU5vVFV0U01uaDJXVzFHYzFVeWJHNWlha1ZVVFVKRlIwRXhWVVZCZUUxTFVqSjRkbGx0Um5OVk1teHVZbXBCWlVaM01IaE9la0V5VFZSVmQwMUVRWGRPUkVwaFJuY3dlVTFVUlhsTlZGVjNUVVJCZDA1RVNtRk5SVWw0UTNwQlNrSm5UbFpDUVZsVVFXeFdWRTFTTkhkSVFWbEVWbEZSUzBWNFZraGlNamx1WWtkVloxWklTakZqTTFGblZUSldlV1J0YkdwYVdFMTRSWHBCVWtKblRsWkNRVTFVUTJ0a1ZWVjVRa1JSVTBGNFZIcEZkMmRuUldsTlFUQkhRMU54UjFOSllqTkVVVVZDUVZGVlFVRTBTVUpFZDBGM1oyZEZTMEZ2U1VKQlVVUlJSMDA1UmpGSmRrNHdOWHByVVU4NUszUk9NWEJKVW5aS2VucDVUMVJJVnpWRWVrVmFhRVF5WlZCRGJuWlZRVEJSYXpJNFJtZEpRMlpMY1VNNVJXdHpRelJVTW1aWFFsbHJMMnBEWmtNelVqTldXazFrVXk5a1RqUmFTME5GVUZwU2NrRjZSSE5wUzFWRWVsSnliVUpDU2pWM2RXUm5lbTVrU1UxWlkweGxMMUpIUjBac05YbFBSRWxMWjJwRmRpOVRTa2d2VlV3clpFVmhiSFJPTVRGQ2JYTkxLMlZSYlUxR0t5dEJZM2hIVG1oeU5UbHhUUzg1YVd3M01Va3laRTQ0UmtkbVkyUmtkM1ZoWldvMFlsaG9jREJNWTFGQ1ltcDRUV05KTjBwUU1HRk5NMVEwU1N0RWMyRjRiVXRHYzJKcWVtRlVUa001ZFhwd1JteG5UMGxuTjNKU01qVjRiM2x1VlhoMk9IWk9iV3R4TjNwa1VFZElXR3Q0VjFrM2IwYzVhaXRLYTFKNVFrRkNhemRZY2twbWIzVmpRbHBGY1VaS1NsTlFhemRZUVRCTVMxY3dXVE42Tlc5Nk1rUXdZekYwU2t0M1NFRm5UVUpCUVVkcVoyZEZlazFKU1VKTWVrRlBRbWRPVmtoUk9FSkJaamhGUWtGTlEwRlpXWGRJVVZsRVZsSXdiRUpDV1hkR1FWbEpTM2RaUWtKUlZVaEJkMFZIUTBOelIwRlJWVVpDZDAxRFRVSkpSMEV4VldSRmQwVkNMM2RSU1UxQldVSkJaamhEUVZGQmQwaFJXVVJXVWpCUFFrSlpSVVpLYWxJclJ6UlJOamdyWWpkSFEyWkhTa0ZpYjA5ME9VTm1NSEpOUWpoSFFURlZaRWwzVVZsTlFtRkJSa3AyYVVJeFpHNUlRamRCWVdkaVpWZGlVMkZNWkM5alIxbFpkVTFFVlVkRFEzTkhRVkZWUmtKM1JVSkNRMnQzU25wQmJFSm5aM0pDWjBWR1FsRmpkMEZaV1ZwaFNGSXdZMFJ2ZGt3eU9XcGpNMEYxWTBkMGNFeHRaSFppTW1OMldqTk9lVTFxUVhsQ1owNVdTRkk0UlV0NlFYQk5RMlZuU21GQmFtaHBSbTlrU0ZKM1QyazRkbGt6U25OTWJrSnlZVk0xYm1JeU9XNU1NbVI2WTJwSmRsb3pUbmxOYVRWcVkyMTNkMUIzV1VSV1VqQm5Ra1JuZDA1cVFUQkNaMXB1WjFGM1FrRm5TWGRMYWtGdlFtZG5ja0puUlVaQ1VXTkRRVkpaWTJGSVVqQmpTRTAyVEhrNWQyRXlhM1ZhTWpsMlduazVlVnBZUW5aak1td3dZak5LTlV4NlFVNUNaMnR4YUd0cFJ6bDNNRUpCVVhOR1FVRlBRMEZSUlVGSGIwRXJUbTV1TnpoNU5uQlNhbVE1V0d4UlYwNWhOMGhVWjJsYUwzSXpVazVIYTIxVmJWbElVRkZ4TmxOamRHazVVRVZoYW5aM1VsUXlhVmRVU0ZGeU1ESm1aWE54VDNGQ1dUSkZWRlYzWjFwUksyeHNkRzlPUm5ab2MwODVkSFpDUTA5SllYcHdjM2RYUXpsaFNqbDRhblUwZEZkRVVVZzRUbFpWTmxsYVdpOVlkR1ZFVTBkVk9WbDZTbkZRYWxrNGNUTk5SSGh5ZW0xeFpYQkNRMlkxYnpodGR5OTNTalJoTWtjMmVIcFZjalpHWWpaVU9FMWpSRTh5TWxCTVVrdzJkVE5OTkZSNmN6TkJNazB4YWpaaWVXdEtXV2s0ZDFkSlVtUkJka3RNVjFwMUwyRjRRbFppZWxsdGNXMTNhMjAxZWt4VFJGYzFia2xCU21KRlRFTlJRMXAzVFVnMU5uUXlSSFp4YjJaNGN6WkNRbU5EUmtsYVZWTndlSFUyZURaMFpEQldOMU4yU2tORGIzTnBjbE50U1dGMGFpODVaRk5UVmtSUmFXSmxkRGh4THpkVlN6UjJORnBWVGpnd1lYUnVXbm94ZVdjOVBTSmRmUS5leUp1YjI1alpTSTZJbEJQT1U0MWVrY3pZbTlOUms1Nk5UbHFWRU01ZG1vdlp6QkxlalExTjJoRVMyOTRTREZzZURSbVlWazlJaXdpZEdsdFpYTjBZVzF3VFhNaU9qRTFOREEwTURZeU16RTBOellzSW1Gd2ExQmhZMnRoWjJWT1lXMWxJam9pWTI5dExtZHZiMmRzWlM1aGJtUnliMmxrTG1kdGN5SXNJbUZ3YTBScFoyVnpkRk5vWVRJMU5pSTZJbVZSWXl0MmVsVmpaSGd3UmxaT1RIWllTSFZIY0VRd0sxSTRNRGR6VlVWMmNDdEtaV3hsV1ZwemFVRTlJaXdpWTNSelVISnZabWxzWlUxaGRHTm9JanAwY25WbExDSmhjR3REWlhKMGFXWnBZMkYwWlVScFoyVnpkRk5vWVRJMU5pSTZXeUk0VURGelZ6QkZVRXBqYzJ4M04xVjZVbk5wV0V3Mk5IY3JUelV3UldRclVrSkpRM1JoZVRGbk1qUk5QU0pkTENKaVlYTnBZMGx1ZEdWbmNtbDBlU0k2ZEhKMVpYMC5qaFE5a3RMLVVCdEJpX2NQNXd4SGRFejJZWXNGMXBLWXpqOEZUZXdXbVVvWE5CTWJSOWRBaEdDSnJCZ2w4RGZNSzFrMUJFQXdQUzRTMWJVczBXQ3haYmN4cWJtcS12UF9OWHI1QjlDQXkxUUpxdC1tRlRMUm5EZkZ1a2hfQjdZMUxEZUJaaGYtc1E4WnBfQUlncHRnYlBWa2Z6TE1PQVpkeE5xVk91dmU0YmJTSjQ5bWQwVklBbDkwc3h1YXVUT0x5bFpxN2ZhYXRZMGFqQ1VKZkNpRUJiLVZxSUhOZmQySExhaUZwcjVxRUFPeU8tQmx1V204TmVfSWZiMnFkTVZBa2p1a1YyVmZheElLbG93a05HZ2ZycjFjVVpJNE5oVTNfeDhLTjRGclpQd29tT2ZFdmlHWk9tZFRvNnNPSXpROTJVYkx2MXlGYUw1TDZIZ1I2Z1NnX0FoYXV0aERhdGFYxSpD77HzPafHbmULkXmwl2mw9P/lQRkLyNgWFO1qQIjVRQAAAAAAAAAAAAAAAAAAAAAAAAAAAEEBEoFEUPzi4s3qob+sexXQrYGH0uPf8domxd0L8Ok+yUo7plaYPfz6HP3Qt/8zMQsSsxr0CpHbVKv1us3MTa6fVaUBAgMmIAEhWCDavINo/+JM4T1eJeKjaZ+vGa2Do7YVh2EyD0vtmoZrrCJYIOtAindNbNXogAQxBAJii2Vd1Wl5rZb9KPak8J6iTKle\",\"clientDataJSON\":\"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiZDNjWTFJNm4xYXI2Z0xwREVoVGk1bkJnUDF4d0lHc2I2SE1fTlI4UEsxbyIsIm9yaWdpbiI6Imh0dHBzOlwvXC9iMzk5ZmEwMC5uZ3Jvay5pbyIsImFuZHJvaWRQYWNrYWdlTmFtZSI6ImNvbS5hbmRyb2lkLmNocm9tZSJ9\"},\"type\":\"public-key\"}`,\n}\n"
  },
  {
    "path": "attestation/attestion.go",
    "content": "// attestation can be imported to import all supported attestation formats\npackage attestation\n\nimport (\n\t_ \"github.com/koesie10/webauthn/attestation/androidsafetynet\"\n\t_ \"github.com/koesie10/webauthn/attestation/fido\"\n\t_ \"github.com/koesie10/webauthn/attestation/packed\"\n)\n"
  },
  {
    "path": "attestation/fido/fido.go",
    "content": "// fido implements the FIDO U2F (WebAuthn spec section 8.6) attestation statement format\npackage fido\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/x509\"\n\n\t\"github.com/koesie10/webauthn/protocol\"\n)\n\nfunc init() {\n\tprotocol.RegisterFormat(\"fido-u2f\", verifyFIDO)\n}\n\nfunc verifyFIDO(a protocol.Attestation, clientDataHash []byte) error {\n\trawSig, ok := a.AttStmt[\"sig\"]\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"missing sig for fido-u2f\")\n\t}\n\tsig, ok := rawSig.([]byte)\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"invalid sig for fido-u2f\")\n\t}\n\n\trawX5c, ok := a.AttStmt[\"x5c\"]\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"missing x5c for fido-u2f\")\n\t}\n\tx5c, ok := rawX5c.([]interface{})\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"invalid x5c for fido-u2f\")\n\t}\n\n\t// Check that x5c has exactly one element\n\tif len(x5c) != 1 {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"invalid x5c for fido-u2f\")\n\t}\n\n\t// let attCert be that element\n\tattCert, ok := x5c[0].([]byte)\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"invalid x5c for fido-u2f\")\n\t}\n\n\t// Let certificate public key be the public key conveyed by attCert\n\tcert, err := x509.ParseCertificate(attCert)\n\tif err != nil {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid x5c for fido-u2f: %v\", err)\n\t}\n\n\t// If certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve, terminate\n\t// this algorithm and return an appropriate error\n\tif cert.PublicKeyAlgorithm != x509.ECDSA {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"x5c public key algorithm is invalid\")\n\t}\n\n\tif cert.PublicKey.(*ecdsa.PublicKey).Curve != elliptic.P256() {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"x5c signature algorithm is invalid\")\n\t}\n\n\tpublicKey, ok := a.AuthData.AttestedCredentialData.COSEKey.(*ecdsa.PublicKey)\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"COSE public key algorithm is invalid\")\n\t}\n\n\tx := publicKey.X.Bytes()\n\ty := publicKey.Y.Bytes()\n\n\tif len(x) != 32 {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"COSE public key x is invalid\")\n\t}\n\tif len(y) != 32 {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"COSE public key y is invalid\")\n\t}\n\n\t// Let publicKeyU2F be the concatenation 0x04 || x || y\n\tpublicKeyU2F := []byte{0x04}\n\tpublicKeyU2F = append(publicKeyU2F, x...)\n\tpublicKeyU2F = append(publicKeyU2F, y...)\n\n\t// Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F)\n\tverificationData := []byte{0x00}\n\tverificationData = append(verificationData, a.AuthData.RPIDHash...)\n\tverificationData = append(verificationData, clientDataHash...)\n\tverificationData = append(verificationData, a.AuthData.AttestedCredentialData.CredentialID...)\n\tverificationData = append(verificationData, publicKeyU2F...)\n\n\t// Verify the sig using verificationData and certificate public key per [SEC1].\n\tif err := cert.CheckSignature(x509.ECDSAWithSHA256, verificationData, sig); err != nil {\n\t\treturn protocol.ErrInvalidSignature.WithDebug(err.Error())\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "attestation/fido/fido_test.go",
    "content": "package fido_test\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/koesie10/webauthn/protocol\"\n)\n\nfunc TestIsValidAttestation(t *testing.T) {\n\tfor i := range attestationRequests {\n\t\tt.Run(fmt.Sprintf(\"Run %d\", i), func(t *testing.T) {\n\t\t\tr := protocol.CredentialCreationOptions{}\n\t\t\tif err := json.Unmarshal([]byte(attestationRequests[i]), &r); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tb := protocol.AttestationResponse{}\n\t\t\tif err := json.Unmarshal([]byte(attestationResponses[i]), &b); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tp, err := protocol.ParseAttestationResponse(b)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\td, err := protocol.IsValidAttestation(p, r.PublicKey.Challenge, \"\", \"\")\n\t\t\tif err != nil {\n\t\t\t\te := protocol.ToWebAuthnError(err)\n\t\t\t\tt.Fatal(fmt.Sprintf(\"%s, %s: %s\", e.Name, e.Description, e.Debug))\n\t\t\t}\n\n\t\t\tif !d {\n\t\t\t\tt.Fatal(\"is not valid\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar attestationRequests = []string{\n\t`{\"publicKey\":{\"rp\":{\"name\":\"accountsvc\"},\"user\":{\"id\":\"MTAwNjg1ODU4NDE3ODI5NDc4NA==\",\"name\":\"Koen Vlaswinkel\",\"displayName\":\"Koen Vlaswinkel\"},\"pubKeyCredParams\":[{\"type\":\"public-key\",\"alg\":-7}],\"timeout\":10000,\"attestation\":\"direct\",\"challenge\":\"+1jQysnwaIjNU+GrwRp4PWNBMlX0i9/caRkcKd7LPj8=\"}}`,\n\t`{\"publicKey\":{\"rp\":{\"name\":\"webauthn-demo\"},\"user\":{\"name\":\"koen\",\"id\":\"a29lbg==\",\"displayName\":\"koen\"},\"challenge\":\"2HzAlPIGskbn53hBJZeH3kZ6XfcHWMnzbATVG/FSgkI=\",\"pubKeyCredParams\":[{\"type\":\"public-key\",\"alg\":-7}],\"timeout\":30000,\"authenticatorSelection\":{\"requireResidentKey\":false},\"attestation\":\"direct\"}}`,\n}\n\nvar attestationResponses = []string{\n\t`{\"id\":\"LOXI3xfiLvIP04MD_S2ZmJYwn3cvMX1FUXxiQO7xlfUvrfcj99UVO2aMrMAwsGvsujY7NHWiM6G3B6ryKJDBBdab-cl4tVZeOwOMhgvHLXk\",\"rawId\":\"LOXI3xfiLvIP04MD/S2ZmJYwn3cvMX1FUXxiQO7xlfUvrfcj99UVO2aMrMAwsGvsujY7NHWiM6G3B6ryKJDBBdab+cl4tVZeOwOMhgvHLXk=\",\"response\":{\"attestationObject\":\"o2dhdHRTdG10omNzaWdYRjBEAiAJ8Q7i8DQzKlb00g4Wby4PoEjlI+s3bS+kVKI3PKoyXQIgDzcP2c5vpplZdmftN+zUDNfXtG1TniWbJv2+6kGZ8bljeDVjgVkBKzCCAScwgc6gAwIBAgIBADAKBggqhkjOPQQDAjAWMRQwEgYDVQQDDAtLcnlwdG9uIEtleTAeFw0xODA5MTcxODQ3NDJaFw0yODA5MTcxODQ3NDJaMBYxFDASBgNVBAMMC0tyeXB0b24gS2V5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwzIpvM5A6mZQXYxRIhfp0sb/21yTcr/sp5Y5DU0IWODQf5ldS2rlDCl62yEaQDM9Akxbsay/vA/S5ut4VSsvoKMNMAswCQYDVR0TBAIwADAKBggqhkjOPQQDAgNIADBFAiA4Yx+5MtKVnjme6V3qXKQ2qcgaHfO6DMgXM9kwOCZcNAIhAJdNk5PPSA04ITfrX9HQy5azo8sH9yhkW7c6gLdb/Kz+aGF1dGhEYXRhWNRJlg3liA6MaHQ0Fw9kdmBbj+SuuaKGMseZXPO6gx2XY0EAAAAALOXI3xfiLvIP04MD/S2ZmABQLOXI3xfiLvIP04MD/S2ZmJYwn3cvMX1FUXxiQO7xlfUvrfcj99UVO2aMrMAwsGvsujY7NHWiM6G3B6ryKJDBBdab+cl4tVZeOwOMhgvHLXmlAQIDJiABIVggwzIpvM5A6mZQXYxRIhfp0sb/21yTcr/sp5Y5DU0IWOAiWCDQf5ldS2rlDCl62yEaQDM9Akxbsay/vA/S5ut4VSsvoGNmbXRoZmlkby11MmY=\",\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiItMWpReXNud2FJak5VLUdyd1JwNFBXTkJNbFgwaTlfY2FSa2NLZDdMUGo4IiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo1Mzg3OSIsInRva2VuQmluZGluZyI6eyJzdGF0dXMiOiJub3Qtc3VwcG9ydGVkIn0sInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ==\"},\"type\":\"public-key\"}`,\n\t`{\"id\":\"EBT1LOefp-8ID0n2jchlyaPrKcWZ6jdHH8nb0Z-hi9JHsOpTpCNUbJ7ijJOKdetLOy2cqdxNq8zkWYmCgpapKg\",\"rawId\":\"EBT1LOefp+8ID0n2jchlyaPrKcWZ6jdHH8nb0Z+hi9JHsOpTpCNUbJ7ijJOKdetLOy2cqdxNq8zkWYmCgpapKg==\",\"response\":{\"attestationObject\":\"o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAJkpVpWsMm/Z1OnF/+B/juq/IAlKqhakms5HkNf6ZKLWAiEAm2qNX/bHUkkdaJ0seanz5xxVDCn+bKGEPyQP3ZpPczNjeDVjgVkCUzCCAk8wggE3oAMCAQICBA0ACxYwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMDExLzAtBgNVBAMMJll1YmljbyBVMkYgRUUgU2VyaWFsIDIzOTI1NzM0MDE1NzY1MjcwMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETKz6btEEuhlL1uBm1+E/zGpgDxDSSFx+o9vUTNDVDbJROHujvR665t7mJQoFWMbpvmEYpEOOWkNfHtLrDOi7haM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAI7CTaiBlYLnMIQZnJ8UCvrqgFuin80CTT4UAiGWsBwh0eY+CRSwL4LEFZITkLlFYyOsfMDlI7oddSN/Jmn8HzrPWvzKVP/+mCuRMSdz735wFNYX5xle+NLkoctZjyHOCqdd4B8lgX0nzwNiPZuf+sdY5fhzhLRmtbpfBDToTP57tLR5WlIY6kJ6QKecpZ5sVNxCzSVxRncAptZV7YSsX2we05Kt5mHkBHqhi5CTPQQmOObHov7cB+4q5CpufDzEBFTKPL3tWxV6HvQr0J6Mp6bZFICq5nTP7VPatnnJelRA9VmPSpQuLjpRqpJFKRobj8eQ9yuveXG/7uutBOzBHW9oYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAQFPUs55+n7wgPSfaNyGXJo+spxZnqN0cfydvRn6GL0kew6lOkI1RsnuKMk4p160s7LZyp3E2rzORZiYKClqkqpQECAyYgASFYIF6oiA6H+mU150XH7WJ2vnzNmdzgr5YloPao7ePjNjlOIlggg0f3u4CtxsBkkKjo7v4luyJui9tJ1rGTBF3YkYlcADo=\",\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiIySHpBbFBJR3NrYm41M2hCSlplSDNrWjZYZmNIV01uemJBVFZHX0ZTZ2tJIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo5MDAwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9\"},\"type\":\"public-key\"}`,\n}\n"
  },
  {
    "path": "attestation/packed/packed.go",
    "content": "// packed implements the Packed (WebAuthn spec section 8.2) attestation statement format\npackage packed\n\nimport (\n\t\"bytes\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"encoding/asn1\"\n\t\"math/big\"\n\n\t\"github.com/koesie10/webauthn/protocol\"\n)\n\nfunc init() {\n\tprotocol.RegisterFormat(\"packed\", verifyPacked)\n}\n\nvar extensionIDFIDOGenCAAAGUID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 45724, 1, 1, 4}\n\nfunc verifyPacked(a protocol.Attestation, clientDataHash []byte) error {\n\trawAlg, ok := a.AttStmt[\"alg\"]\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"missing alg for packed\")\n\t}\n\talgInt, ok := rawAlg.(int64)\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid alg for packed, is of invalid type %T\", rawAlg)\n\t}\n\n\talg := protocol.COSEAlgorithmIdentifier(algInt)\n\n\trawSig, ok := a.AttStmt[\"sig\"]\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"missing sig for packed\")\n\t}\n\tsig, ok := rawSig.([]byte)\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"invalid sig for packed\")\n\t}\n\n\t// 2. If x5c is present, this indicates that the attestation type is not ECDAA. In this case:\n\tif _, ok := a.AttStmt[\"x5c\"]; ok {\n\t\treturn verifyBasic(a, clientDataHash, alg, sig)\n\t}\n\n\t// 3. If ecdaaKeyId is present, then the attestation type is ECDAA. In this case:\n\tif _, ok := a.AttStmt[\"ecdaaKeyId\"]; ok {\n\t\treturn verifyECDAA(a, clientDataHash, alg, sig)\n\t}\n\n\t// 4. If neither x5c nor ecdaaKeyId is present, self attestation is in use.\n\treturn verifySelf(a, clientDataHash, alg, sig)\n}\n\nfunc verifyBasic(a protocol.Attestation, clientDataHash []byte, alg protocol.COSEAlgorithmIdentifier, sig []byte) error {\n\tx5c, ok := a.AttStmt[\"x5c\"].([]interface{})\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"invalid x5c for packed\")\n\t}\n\n\t// let attCert be that element\n\tattestnCert, ok := x5c[0].([]byte)\n\tif !ok {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"invalid x5c for packed\")\n\t}\n\n\t// Let certificate public key be the public key conveyed by attCert\n\tcert, err := x509.ParseCertificate(attestnCert)\n\tif err != nil {\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid x5c for packed: %v\", err)\n\t}\n\n\t// 2.1 Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash using\n\t// the attestation public key in attestnCert with the algorithm specified in alg.\n\tsignedBytes := append(a.AuthData.Raw, clientDataHash...)\n\tif err := cert.CheckSignature(cert.SignatureAlgorithm, signedBytes, sig); err != nil {\n\t\t// Fallback to ECDSAWithSA256 if signature algorithm is incorret, as is the case with Yubico's keys\n\t\terr = cert.CheckSignature(x509.ECDSAWithSHA256, signedBytes, sig)\n\t\tif err != nil {\n\t\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid signature for packed: %v\", err)\n\t\t}\n\t}\n\n\t// 2.2 Verify that attestnCert meets the requirements in §8.2.1 Packed attestation statement certificate requirements.\n\n\t// Version MUST be set to 3 (which is indicated by an ASN.1 INTEGER with value 2).\n\tif cert.Version != 3 {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"invalid version for certificate\")\n\t}\n\n\t// The Basic Constraints extension MUST have the CA component set to false.\n\tif cert.IsCA {\n\t\treturn protocol.ErrInvalidAttestation.WithDebug(\"CA is set for certificate\")\n\t}\n\n\tvar aaguidValue []byte\n\n\tfor _, ext := range cert.Extensions {\n\t\t// If the related attestation root certificate is used for multiple authenticator models, the Extension\n\t\t// OID 1.3.6.1.4.1.45724.1.1.4 (id-fido-gen-ce-aaguid) MUST be present, containing the AAGUID as a 16-byte\n\t\t// OCTET STRING.\n\t\tif ext.Id.Equal(extensionIDFIDOGenCAAAGUID) {\n\t\t\t// The extension MUST NOT be marked as critical.\n\t\t\tif ext.Critical {\n\t\t\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"extension id-fido-gen-ce-aaguid is present, but is marked as critical\")\n\t\t\t}\n\t\t\taaguidValue = ext.Value\n\t\t}\n\t}\n\n\t// 2.3 If attestnCert contains an extension with OID 1.3.6.1.4.1.45724.1.1.4 (id-fido-gen-ce-aaguid) verify that\n\t// the value of this extension matches the aaguid in authenticatorData.\n\tif len(aaguidValue) > 0 {\n\t\t// Note that an X.509 Extension encodes the DER-encoding of the value in an OCTET STRING. Thus, the AAGUID MUST\n\t\t// be wrapped in two OCTET STRINGS to be valid\n\t\tvar aaguid []byte\n\t\tif _, err := asn1.Unmarshal(aaguidValue, &aaguid); err != nil {\n\t\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid AAGUID: %v\", err)\n\t\t}\n\n\t\tif !bytes.Equal(a.AuthData.AttestedCredentialData.AAGUID, aaguid) {\n\t\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid AAGUID\")\n\t\t}\n\n\t}\n\n\t// If successful, return attestation type Basic and attestation trust path x5c.\n\treturn nil\n}\n\nfunc verifyECDAA(a protocol.Attestation, clientDataHash []byte, alg protocol.COSEAlgorithmIdentifier, sig []byte) error {\n\treturn protocol.ErrInvalidAttestation.WithDebugf(\"unsupported packed format ECDAA\")\n}\n\nfunc verifySelf(a protocol.Attestation, clientDataHash []byte, alg protocol.COSEAlgorithmIdentifier, sig []byte) error {\n\t// 4.1 Validate that alg matches the algorithm of the credentialPublicKey in authenticatorData.\n\n\t// 4.2 Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash using\n\t// the credential public key with alg.\n\tsignedBytes := append(a.AuthData.Raw, clientDataHash...)\n\n\tswitch v := a.AuthData.AttestedCredentialData.COSEKey.(type) {\n\tcase *ecdsa.PublicKey:\n\t\t// Right now, only EC256 is supported\n\t\tif alg != protocol.ES256 || v.Curve != elliptic.P256() {\n\t\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"unsupported packed self attestation ECDSA key curve %s\", v.Curve.Params().Name)\n\t\t}\n\n\t\t// 6.4.5.1 Signature Formats for Packed Attestation ES256\n\t\tsignature := make([]*big.Int, 2)\n\t\tif rest, err := asn1.Unmarshal(sig, signature); err != nil {\n\t\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid ECDSA signature: %v\", err).WithCause(err)\n\t\t} else if rest != nil {\n\t\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid ECDSA signature: too much data\")\n\t\t}\n\n\t\thash := sha256.Sum256(signedBytes)\n\t\tif !ecdsa.Verify(v, hash[:], signature[0], signature[1]) {\n\t\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"invalid signature for packed\")\n\t\t}\n\tdefault:\n\t\treturn protocol.ErrInvalidAttestation.WithDebugf(\"unsupported packed self attestation public key type %T\", a.AuthData.AttestedCredentialData.COSEKey)\n\t}\n\n\t// If successful, return implementation-specific values representing attestation type Self and an empty attestation trust path.\n\treturn nil\n}\n"
  },
  {
    "path": "attestation/packed/packed_test.go",
    "content": "package packed_test\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/koesie10/webauthn/protocol\"\n)\n\nfunc TestIsValidAttestation(t *testing.T) {\n\tfor i := range attestationRequests {\n\t\tt.Run(fmt.Sprintf(\"Run %d\", i), func(t *testing.T) {\n\t\t\tr := protocol.CredentialCreationOptions{}\n\t\t\tif err := json.Unmarshal([]byte(attestationRequests[i]), &r); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tb := protocol.AttestationResponse{}\n\t\t\tif err := json.Unmarshal([]byte(attestationResponses[i]), &b); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tp, err := protocol.ParseAttestationResponse(b)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\td, err := protocol.IsValidAttestation(p, r.PublicKey.Challenge, \"\", \"\")\n\t\t\tif err != nil {\n\t\t\t\te := protocol.ToWebAuthnError(err)\n\t\t\t\tt.Fatal(fmt.Sprintf(\"%s, %s: %s\", e.Name, e.Description, e.Debug))\n\t\t\t}\n\n\t\t\tif !d {\n\t\t\t\tt.Fatal(\"is not valid\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar attestationRequests = []string{\n\t`{\"publicKey\":{\"rp\":{\"name\":\"webauthn-demo\"},\"user\":{\"name\":\"koen\",\"id\":\"a29lbg==\",\"displayName\":\"koen\"},\"challenge\":\"JUtlYcgpkSiFNzsThDYuOrtSVY1VeLofM+mWTRCCXqU=\",\"pubKeyCredParams\":[{\"type\":\"public-key\",\"alg\":-7}],\"timeout\":30000,\"authenticatorSelection\":{\"requireResidentKey\":false},\"attestation\":\"direct\"}}`,\n}\n\nvar attestationResponses = []string{\n\t`{\"id\":\"SNBSJTt1DHEuG9XBd6lfc4XXqxkppWfFbt4P5sRVQEPIPANIHHCmPo1AwY5pkUGcpVL3W-uHyWEn4vbgzp34Qw\",\"rawId\":\"SNBSJTt1DHEuG9XBd6lfc4XXqxkppWfFbt4P5sRVQEPIPANIHHCmPo1AwY5pkUGcpVL3W+uHyWEn4vbgzp34Qw==\",\"response\":{\"attestationObject\":\"o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIgFls/elhmdZmqEBEKafdcyvQPDrTdBRMW92v6RKJj1bACIQCZ+46sXn65dMEpPuGxvMUruV5i7XN25ctFV/iAi3wSomN4NWOBWQLCMIICvjCCAaagAwIBAgIEdIb9wjANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbzELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTk1NTAwMzg0MjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJVd8633JH0xde/9nMTzGk6HjrrhgQlWYVD7OIsuX2Unv1dAmqWBpQ0KxS8YRFwKE1SKE1PIpOWacE5SO8BN6+2jbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBguUcAgEBBAQDAgUgMCEGCysGAQQBguUcAQEEBBIEEPigEfOMCk0VgAYXER+e3H0wDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAMVxIgOaaUn44Zom9af0KqG9J655OhUVBVW+q0As6AIod3AH5bHb2aDYakeIyyBCnnGMHTJtuekbrHbXYXERIn4aKdkPSKlyGLsA/A+WEi+OAfXrNVfjhrh7iE6xzq0sg4/vVJoywe4eAJx0fS+Dl3axzTTpYl71Nc7p/NX6iCMmdik0pAuYJegBcTckE3AoYEg4K99AM/JaaKIblsbFh8+3LxnemeNf7UwOczaGGvjS6UzGVI0Odf9lKcPIwYhuTxM5CaNMXTZQ7xq4/yTfC3kPWtE4hFT34UJJflZBiLrxG4OsYxkHw/n5vKgmpspB3GfYuYTWhkDKiE8CYtyg87mhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NBAAAAA/igEfOMCk0VgAYXER+e3H0AQEjQUiU7dQxxLhvVwXepX3OF16sZKaVnxW7eD+bEVUBDyDwDSBxwpj6NQMGOaZFBnKVS91vrh8lhJ+L24M6d+EOlAQIDJiABIVggLxxTguKmjCV4N5OMqd2Sl9AIxSltaPevmQxSqnyNlAciWCDEHOaQDaZ6pC2gC+Z0KS4Ln/XQiJp0X1BmTd+K+FdqSg==\",\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiJKVXRsWWNncGtTaUZOenNUaERZdU9ydFNWWTFWZUxvZk0tbVdUUkNDWHFVIiwibmV3X2tleXNfbWF5X2JlX2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0=\"},\"type\":\"public-key\"}`,\n}\n"
  },
  {
    "path": "cose/cose.go",
    "content": "package cose\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\n\t\"github.com/ugorji/go/codec\"\n)\n\n// Errors\nvar (\n\tErrMissingKeyType       = fmt.Errorf(\"cose: missing key type\")\n\tErrMissingAlgorithm     = fmt.Errorf(\"cose: missing algorithm\")\n\tErrUnsupportedKeyType   = fmt.Errorf(\"cose: unsupported key type\")\n\tErrUnsupportedAlgorithm = fmt.Errorf(\"cose: unsupported algorithm\")\n\tErrInvalidFormat        = fmt.Errorf(\"cose: invalid format\")\n)\n\n// ParseCOSE parses a raw COSE key into a public key, either *ecdsa.PublicKey or *rsa.PublicKey.\nfunc ParseCOSE(buf []byte) (interface{}, error) {\n\tm := make(map[int]interface{})\n\n\tcbor := codec.CborHandle{}\n\n\tif err := codec.NewDecoder(bytes.NewReader(buf), &cbor).Decode(&m); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ParseCOSEMap(m)\n}\n\n// ParseCOSEMap parses a COSE key that has been decoded from it's CBOR format to a dictionary.\nfunc ParseCOSEMap(m map[int]interface{}) (interface{}, error) {\n\trawKty, ok := m[1]\n\tif !ok {\n\t\treturn nil, ErrMissingKeyType\n\t}\n\tkty, ok := rawKty.(uint64)\n\tif !ok {\n\t\treturn nil, ErrMissingKeyType\n\t}\n\n\trawAlg, ok := m[3]\n\tif !ok {\n\t\treturn nil, ErrMissingAlgorithm\n\t}\n\talg, ok := rawAlg.(int64)\n\tif !ok {\n\t\treturn nil, ErrMissingAlgorithm\n\t}\n\n\t// https://tools.ietf.org/html/rfc8152#section-13\n\tswitch kty {\n\tcase 2: // EC2\n\t\treturn parseECDSA(alg, m)\n\tdefault:\n\t\treturn nil, ErrUnsupportedKeyType\n\t}\n}\n"
  },
  {
    "path": "cose/cose_test.go",
    "content": "package cose_test\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"testing\"\n\n\t\"github.com/koesie10/webauthn/cose\"\n)\n\nfunc TestParseCOSE(t *testing.T) {\n\tkey, err := cose.ParseCOSE(coseKey)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_ = key.(*ecdsa.PublicKey)\n}\n\nvar coseKey = []byte{165, 1, 2, 3, 38, 32, 1, 33, 88, 32, 216, 135, 166, 35, 155, 95, 158, 137, 152, 93, 252, 213, 238, 69, 20, 97, 196, 158, 87, 181, 241, 175, 77, 207, 20, 244, 241, 201, 179, 138, 100, 239, 34, 88, 32, 163, 48, 62, 105, 84, 41, 231, 50, 219, 25, 77, 105, 244, 230, 187, 108, 215, 105, 155, 163, 198, 146, 133, 33, 252, 5, 101, 90, 174, 75, 99, 141}\n"
  },
  {
    "path": "cose/doc.go",
    "content": "// cose contains utility functions related to COSE keys, Section 7 of [RFC8152].\n//\npackage cose // import \"github.com/koesie10/webauthn/cose\"\n"
  },
  {
    "path": "cose/ecdsa.go",
    "content": "package cose\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"math/big\"\n)\n\nfunc parseECDSA(alg int64, m map[int]interface{}) (interface{}, error) {\n\tvar curve elliptic.Curve\n\tswitch alg {\n\tcase -7:\n\t\tcurve = elliptic.P256()\n\tcase -35:\n\t\tcurve = elliptic.P384()\n\tcase -36:\n\t\tcurve = elliptic.P521()\n\tdefault:\n\t\treturn nil, ErrUnsupportedAlgorithm\n\t}\n\n\trawD, ok := m[-4]\n\tif !ok { // public key if there is no d\n\t\treturn parseECDSAPublicKey(curve, m)\n\t}\n\n\t// otherwise, we have a private key\n\n\tdBytes, ok := rawD.([]byte)\n\tif !ok {\n\t\treturn nil, ErrInvalidFormat\n\t}\n\n\treturn &ecdsa.PrivateKey{\n\t\tD: big.NewInt(0).SetBytes(dBytes),\n\t}, nil\n}\n\nfunc parseECDSAPublicKey(curve elliptic.Curve, m map[int]interface{}) (*ecdsa.PublicKey, error) {\n\trawX, ok := m[-2]\n\tif !ok {\n\t\treturn nil, ErrInvalidFormat\n\t}\n\txBytes, ok := rawX.([]byte)\n\tif !ok {\n\t\treturn nil, ErrInvalidFormat\n\t}\n\n\trawY, ok := m[-3]\n\tif !ok {\n\t\treturn nil, ErrInvalidFormat\n\t}\n\tyBytes, ok := rawY.([]byte)\n\tif !ok {\n\t\treturn nil, ErrInvalidFormat\n\t}\n\n\tx := big.NewInt(0).SetBytes(xBytes)\n\ty := big.NewInt(0).SetBytes(yBytes)\n\n\treturn &ecdsa.PublicKey{\n\t\tCurve: curve,\n\t\tX:     x,\n\t\tY:     y,\n\t}, nil\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/koesie10/webauthn\n\ngo 1.13\n\nrequire (\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/ugorji/go/codec v1.1.7\n\tgolang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d // indirect\n\tgopkg.in/square/go-jose.v2 v2.4.1\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=\ngithub.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=\ngithub.com/ugorji/go/codec v0.0.0-20180918125716-ed9a3b5f078b h1:pvvReAGi9NbL0Z7RgD5a7GFc3WAW0oE9q57MfVKlstw=\ngithub.com/ugorji/go/codec v0.0.0-20180918125716-ed9a3b5f078b/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=\ngithub.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=\ngithub.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=\ngolang.org/x/crypto v0.0.0-20181024171144-74cb1d3d52f4 h1:4v3KN0hcTEAkyusBypx0RrpPAhKsTP3YXj10LonM8J8=\ngolang.org/x/crypto v0.0.0-20181024171144-74cb1d3d52f4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=\ngolang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngopkg.in/square/go-jose.v2 v2.1.9 h1:YCFbL5T2gbmC2sMG12s1x2PAlTK5TZNte3hjZEIcCAg=\ngopkg.in/square/go-jose.v2 v2.1.9/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=\ngopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\n"
  },
  {
    "path": "protocol/api.go",
    "content": "package protocol\n\n// CredentialCreationOptions contains the options that should be passed to navigator.credentials.create().\n// https://www.w3.org/TR/webauthn/#credentialcreationoptions-extension\ntype CredentialCreationOptions struct {\n\tPublicKey PublicKeyCredentialCreationOptions `json:\"publicKey\"`\n}\n\n// CredentialRequestOptions contains the options that should be passed to navigator.credentials.get().\n// https://www.w3.org/TR/webauthn/#credentialrequestoptions-extension\ntype CredentialRequestOptions struct {\n\tPublicKey PublicKeyCredentialRequestOptions `json:\"publicKey\"`\n}\n\n// The PublicKeyCredentialCreationOptions dictionary supplies create() with the data it needs to generate an attestation.\n// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialcreationoptions\ntype PublicKeyCredentialCreationOptions struct {\n\t// This member contains data about the Relying Party responsible for the request.\n\t// Its value’s name member is REQUIRED. See §5.4.1 Public Key Entity Description (dictionary\n\t// PublicKeyCredentialEntity) for further details.\n\t// Its value’s id member specifies the RP ID with which the credential should be associated. If omitted, its value\n\t// will be the CredentialsContainer object’s relevant settings object's origin's effective domain. See §5.4.2\n\t// Relying Party Parameters for Credential Generation (dictionary PublicKeyCredentialRpEntity) for further details.\n\tRP PublicKeyCredentialRpEntity `json:\"rp\"`\n\t// This member contains data about the user account for which the Relying Party is requesting attestation.\n\t// Its value’s name, displayName and id members are REQUIRED. See §5.4.1 Public Key Entity Description\n\t// (dictionary PublicKeyCredentialEntity) and §5.4.3 User Account Parameters for Credential Generation\n\t// (dictionary PublicKeyCredentialUserEntity) for further details.\n\tUser PublicKeyCredentialUserEntity `json:\"user\"`\n\n\t// This member contains a challenge intended to be used for generating the newly created credential’s attestation\n\t// object. See the §13.1 Cryptographic Challenges security consideration.\n\tChallenge Challenge `json:\"challenge\"`\n\t// This member contains information about the desired properties of the credential to be created. The sequence is\n\t// ordered from most preferred to least preferred. The client makes a best-effort to create the most preferred\n\t// credential that it can.\n\tPubKeyCredParams []PublicKeyCredentialParameters `json:\"pubKeyCredParams,omitempty\"`\n\n\t// This member specifies a time, in milliseconds, that the caller is willing to wait for the call to complete.\n\t// This is treated as a hint, and MAY be overridden by the client.\n\tTimeout uint `json:\"timeout,omitempty\"`\n\t// This member is intended for use by Relying Parties that wish to limit the creation of multiple credentials for\n\t// the same account on a single authenticator. The client is requested to return an error if the new credential\n\t// would be created on an authenticator that also contains one of the credentials enumerated in this parameter.\n\tExcludeCredentials []PublicKeyCredentialDescriptor `json:\"excludeCredentials,omitempty\"`\n\t// This member is intended for use by Relying Parties that wish to select the appropriate authenticators to\n\t// participate in the create() operation.\n\tAuthenticatorSelection AuthenticatorSelectionCriteria `json:\"authenticatorSelection,omitempty\"`\n\t// This member is intended for use by Relying Parties that wish to express their preference for attestation\n\t// conveyance. The default is none.\n\tAttestation AttestationConveyancePreference `json:\"attestation,omitempty\"`\n\t// This member contains additional parameters requesting additional processing by the client and authenticator. For\n\t// example, the caller may request that only authenticators with certain capabilities be used to create the\n\t// credential, or that particular information be returned in the attestation object. Some extensions are defined in\n\t// §9 WebAuthn Extensions; consult the IANA \"WebAuthn Extension Identifier\" registry established by\n\t// [WebAuthn-Registries] for an up-to-date list of registered WebAuthn Extensions.\n\tExtensions AuthenticationExtensionsClientInputs `json:\"extensions,omitempty\"`\n}\n\n// The PublicKeyCredentialRequestOptions dictionary supplies get() with the data it needs to generate an assertion. Its\n// challenge member MUST be present, while its other members are OPTIONAL.\n// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrequestoptions\ntype PublicKeyCredentialRequestOptions struct {\n\t// This member represents a challenge that the selected authenticator signs, along with other data, when producing\n\t// an authentication assertion. See the §13.1 Cryptographic Challenges security consideration.\n\tChallenge Challenge `json:\"challenge\"`\n\t// This OPTIONAL member specifies a time, in milliseconds, that the caller is willing to wait for the call to\n\t// complete. The value is treated as a hint, and MAY be overridden by the client.\n\tTimeout uint `json:\"timeout,omitempty\"`\n\t// This OPTIONAL member specifies the relying party identifier claimed by the caller. If omitted, its value will be\n\t// the CredentialsContainer object’s relevant settings object's origin's effective domain.\n\tRPID string `json:\"rpId,omitempty\"`\n\t// This OPTIONAL member contains a list of PublicKeyCredentialDescriptor objects representing public key credentials\n\t// acceptable to the caller, in descending order of the caller’s preference (the first item in the list is the most\n\t// preferred credential, and so on down the list).\n\tAllowCredentials []PublicKeyCredentialDescriptor `json:\"allowCredentials,omitempty\"`\n\t// This member describes the Relying Party's requirements regarding user verification for the get() operation.\n\t// Eligible authenticators are filtered to only those capable of satisfying this requirement.\n\tUserVerification UserVerificationRequirement `json:\"userVerification,omitempty\"`\n\t// This OPTIONAL member contains additional parameters requesting additional processing by the client and\n\t// authenticator. For example, if transaction confirmation is sought from the user, then the prompt string might\n\t// be included as an extension.\n\tExtensions AuthenticationExtensionsClientInputs `json:\"extensions,omitempty\"`\n}\n\n// The PublicKeyCredentialRpEntity dictionary is used to supply additional Relying Party attributes when creating a\n// new credential.\n// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity\ntype PublicKeyCredentialRpEntity struct {\n\tPublicKeyCredentialEntity\n\t// A unique identifier for the Relying Party entity, which sets the RP ID.\n\tID string `json:\"id,omitempty\"`\n}\n\n// The PublicKeyCredentialEntity dictionary describes a user account, or a WebAuthn Relying Party, with which a\n// public key credential is associated.\n// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialentity\ntype PublicKeyCredentialEntity struct {\n\t// A human-palatable name for the entity. Its function depends on what the PublicKeyCredentialEntity represents.\n\tName string `json:\"name\"`\n}\n\n// The PublicKeyCredentialUserEntity dictionary is used to supply additional user account attributes when creating a\n// new credential.\n// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity\ntype PublicKeyCredentialUserEntity struct {\n\tPublicKeyCredentialEntity\n\n\t// The user handle of the user account entity. To ensure secure operation, authentication and authorization\n\t// decisions MUST be made on the basis of this id member, not the displayName nor name members. See\n\t// Section 6.1 of [RFC8266].\n\tID []byte `json:\"id\"`\n\t// A human-palatable name for the user account, intended only for display. For example, \"Alex P. Müller\" or\n\t// \"田中 倫\". The Relying Party SHOULD let the user choose this, and SHOULD NOT restrict the choice more than\n\t// necessary.\n\tDisplayName string `json:\"displayName\"`\n}\n\n// PublicKeyCredentialType defines the valid credential types. It is an extension point; values can be added to it in the\n// future, as more credential types are defined. The values of this enumeration are used for versioning the\n// Authentication Assertion and attestation structures according to the type of the authenticator.\n// Currently one credential type is defined, namely \"public-key\".\n// https://www.w3.org/TR/webauthn/#enumdef-publickeycredentialtype\ntype PublicKeyCredentialType string\n\nconst (\n\t// PublicKeyCredentialTypePublicKey is the only credential type defined, namely \"public-key\".\n\tPublicKeyCredentialTypePublicKey PublicKeyCredentialType = \"public-key\"\n)\n\n// A COSEAlgorithmIdentifier's value is a number identifying a cryptographic algorithm. The algorithm identifiers\n// SHOULD be values registered in the IANA COSE Algorithms registry [IANA-COSE-ALGS-REG], for instance, -7 for\n// \"ES256\" and -257 for \"RS256\".\n// https://www.w3.org/TR/webauthn/#alg-identifier\ntype COSEAlgorithmIdentifier int\n\nconst (\n\t// ES256 is the COSE Algorithm Identifier of ECDSA 256\n\tES256 COSEAlgorithmIdentifier = -7\n\t// RS256 is the COSE Algorithm Identifier of RSA 256\n\tRS256 COSEAlgorithmIdentifier = -257\n)\n\n// AuthenticatorTransport represents the transport used by an authenticator. Authenticators may implement various\n// transports for communicating with clients. This enumeration defines hints as to\n// how clients might communicate with a particular authenticator in order to obtain an assertion for a specific\n// credential. Note that these hints represent the WebAuthn Relying Party's best belief as to how an authenticator may\n// be reached. A Relying Party may obtain a list of transports hints from some attestation statement formats or via\n// some out-of-band mechanism; it is outside the scope of this specification to define that mechanism.\n// https://www.w3.org/TR/webauthn/#enumdef-authenticatortransport\ntype AuthenticatorTransport string\n\nconst (\n\t// AuthenticatorTransportUSB indicates the respective authenticator can be contacted over removable USB.\n\tAuthenticatorTransportUSB AuthenticatorTransport = \"usb\"\n\t// AuthenticatorTransportNFC indicates the respective authenticator can be contacted over Near Field Communication (NFC).\n\tAuthenticatorTransportNFC = \"nfc\"\n\t// AuthenticatorTransportBLE indicates the respective authenticator can be contacted over Bluetooth Smart (Bluetooth Low Energy / BLE).\n\tAuthenticatorTransportBLE = \"ble\"\n\t// AuthenticatorTransportInternal indicates the respective authenticator is contacted using a client device-specific transport. These\n\t// authenticators are not removable from the client device.\n\tAuthenticatorTransportInternal = \"internal\"\n)\n\n// PublicKeyCredentialParameters is used to supply additional parameters when creating a new credential.\n// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialparameters\ntype PublicKeyCredentialParameters struct {\n\t// This member specifies the type of credential to be created.\n\tType PublicKeyCredentialType `json:\"type\"`\n\t// This member specifies the cryptographic signature algorithm with which the newly generated credential will be\n\t// used, and thus also the type of asymmetric key pair to be generated, e.g., RSA or Elliptic Curve.\n\tAlgorithm COSEAlgorithmIdentifier `json:\"alg\"`\n}\n\n// PublicKeyCredentialDescriptor contains the attributes that are specified by a caller when referring to a public key credential as\n// an input parameter to the create() or get() methods. It mirrors the fields of the PublicKeyCredential object\n// returned by the latter methods.\n// https://www.w3.org/TR/webauthn/#credential-dictionary\ntype PublicKeyCredentialDescriptor struct {\n\t// This member contains the type of the public key credential the caller is referring to.\n\tType PublicKeyCredentialType `json:\"type\"`\n\t// This member contains the credential ID of the public key credential the caller is referring to.\n\tID []byte `json:\"id\"`\n\t// This OPTIONAL member contains a hint as to how the client might communicate with the managing authenticator of\n\t// the public key credential the caller is referring to.\n\tTransport []AuthenticatorTransport `json:\"transports,omitempty\"`\n}\n\n// The AuthenticatorSelectionCriteria may be used by WebAuthn Relying Parties to specify their requirements\n// regarding authenticator attributes.\n// https://www.w3.org/TR/webauthn/#dictdef-authenticatorselectioncriteria\ntype AuthenticatorSelectionCriteria struct {\n\t// If this member is present, eligible authenticators are filtered to only authenticators attached with the\n\t// specified §5.4.5 Authenticator Attachment enumeration (enum AuthenticatorAttachment).\n\tAuthenticatorAttachment AuthenticatorAttachment `json:\"authenticatorAttachment,omitempty\"`\n\t// This member describes the Relying Parties' requirements regarding resident credentials. If the parameter is set\n\t// to true, the authenticator MUST create a client-side-resident public key credential source when creating a\n\t// public key credential.\n\tRequireResidentKey bool `json:\"requireResidentKey\"`\n\t// This member describes the Relying Party's requirements regarding user verification for the create() operation.\n\t// Eligible authenticators are filtered to only those capable of satisfying this requirement.\n\tUserVerification UserVerificationRequirement `json:\"userVerification,omitempty\"`\n}\n\n// AuthenticatorAttachment's values describe authenticators' attachment modalities. Relying Parties use this for two purposes:\n// to express a preferred authenticator attachment modality when calling navigator.credentials.create() to create a\n// credential, and\n// to inform the client of the Relying Party's best belief about how to locate the managing authenticators of the\n// credentials listed in allowCredentials when calling navigator.credentials.get().\n// https://www.w3.org/TR/webauthn/#enumdef-authenticatorattachment\ntype AuthenticatorAttachment string\n\nconst (\n\t// AuthenticatorAttachmentPlatform indicates platform attachment.\n\tAuthenticatorAttachmentPlatform AuthenticatorAttachment = \"platform\"\n\t// AuthenticatorAttachmentCrossPlatform indicates cross-platform attachment.\n\tAuthenticatorAttachmentCrossPlatform = \"cross-platform\"\n)\n\n// UserVerificationRequirement may be used by a WebAuthn Relying Party to require user verification for some of its\n// operations but not for others.\n// https://www.w3.org/TR/webauthn/#enumdef-userverificationrequirement\ntype UserVerificationRequirement string\n\nconst (\n\t// UserVerificationRequired indicates that the Relying Party requires user verification for the operation and will fail the\n\t// operation if the response does not have the UV flag set.\n\tUserVerificationRequired UserVerificationRequirement = \"required\"\n\t// UserVerificationPreferred indicates that the Relying Party prefers user verification for the operation if possible, but\n\t// will not fail the operation if the response does not have the UV flag set.\n\tUserVerificationPreferred = \"preferred\"\n\t// UserVerificationDiscouraged indicates that the Relying Party does not want user verification employed during the operation\n\t// (e.g., in the interest of minimizing disruption to the user interaction flow).\n\tUserVerificationDiscouraged = \"discouraged\"\n)\n\n// AttestationConveyancePreference may be used by WebAuthn Relying Parties to specify their preference regarding attestation\n// conveyance during credential generation.\n// https://www.w3.org/TR/webauthn/#enumdef-attestationconveyancepreference\ntype AttestationConveyancePreference string\n\nconst (\n\t// AttestationConveyancePreferenceNone indicates that the Relying Party is not interested in authenticator attestation. For example, in\n\t// order to potentially avoid having to obtain user consent to relay identifying information to the Relying Party,\n\t// or to save a roundtrip to an Attestation CA. This is the default value.\n\tAttestationConveyancePreferenceNone = \"none\"\n\t// AttestationConveyancePreferenceIndirect indicates that the Relying Party prefers an attestation conveyance yielding verifiable attestation\n\t// statements, but allows the client to decide how to obtain such attestation statements. The client MAY replace\n\t// the authenticator-generated attestation statements with attestation statements generated by an Anonymization CA,\n\t// in order to protect the user’s privacy, or to assist Relying Parties with attestation verification in a\n\t// heterogeneous ecosystem.\n\tAttestationConveyancePreferenceIndirect = \"indirect\"\n\t// AttestationConveyancePreferenceDirect indicates that the Relying Party wants to receive the attestation statement as generated by the\n\t// authenticator.\n\tAttestationConveyancePreferenceDirect = \"direct\"\n)\n\n// AuthenticationExtensionsClientInputs contains the client extension input values for zero or more WebAuthn extensions, as defined\n// in §9 WebAuthn Extensions.\n// https://www.w3.org/TR/webauthn/#dictdef-authenticationextensionsclientinputs\ntype AuthenticationExtensionsClientInputs map[string]interface{}\n"
  },
  {
    "path": "protocol/assertion.go",
    "content": "package protocol\n\nimport (\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n)\n\n// AssertionResponse contains the attributes that are returned to the caller when a new assertion is requested.\n// https://www.w3.org/TR/webauthn/#publickeycredential\ntype AssertionResponse struct {\n\tPublicKeyCredential\n\t// This attribute contains the authenticator's response to the client’s request to generate an authentication assertion.\n\tResponse AuthenticatorAssertionResponse `json:\"response\"`\n}\n\n// ParsedAssertionResponse is a parsed version of AssertionResponse.\n// https://www.w3.org/TR/webauthn/#publickeycredential\ntype ParsedAssertionResponse struct {\n\tParsedPublicKeyCredential\n\t// This attribute contains the authenticator's response to the client’s request to generate an authentication assertion.\n\tResponse ParsedAuthenticatorAssertionResponse\n\t// RawResponse contains the unparsed AssertionResponse.\n\tRawResponse AssertionResponse\n}\n\n// The AuthenticatorAssertionResponse interface represents an authenticator's response to a client’s request for\n// generation of a new authentication assertion given the WebAuthn Relying Party's challenge and OPTIONAL list of\n// credentials it is aware of. This response contains a cryptographic signature proving possession of the credential\n// private key, and optionally evidence of user consent to a specific transaction.\n// https://www.w3.org/TR/webauthn/#authenticatorassertionresponse\ntype AuthenticatorAssertionResponse struct {\n\tAuthenticatorResponse\n\t// This attribute contains the authenticator data returned by the authenticator. See §6.1 Authenticator data.\n\tAuthenticatorData []byte `json:\"authenticatorData\"`\n\t// This attribute contains the raw signature returned from the authenticator. See §6.3.3 The\n\t// authenticatorGetAssertion operation.\n\tSignature []byte `json:\"signature\"`\n\t// This attribute contains the user handle returned from the authenticator, or null if the authenticator did not\n\t// return a user handle. See §6.3.3 The authenticatorGetAssertion operation.\n\tUserHandle []byte `json:\"userHandle,omitempty\"`\n}\n\n// ParsedAuthenticatorAssertionResponse is a parsed version of AuthenticatorAssertionResponse.\n// https://www.w3.org/TR/webauthn/#authenticatorassertionresponse\ntype ParsedAuthenticatorAssertionResponse struct {\n\tParsedAuthenticatorResponse\n\t// This attribute contains the authenticator data returned by the authenticator. See §6.1 Authenticator data.\n\tAuthData AuthenticatorData\n\t// This attribute contains the raw signature returned from the authenticator. See §6.3.3 The\n\t// authenticatorGetAssertion operation.\n\tSignature []byte\n\t// This attribute contains the user handle returned from the authenticator, or null if the authenticator did not\n\t// return a user handle. See §6.3.3 The authenticatorGetAssertion operation.\n\tUserHandle []byte\n}\n\n// ParseAssertionResponse will parse a raw AssertionResponse as supplied by a client to a ParsedAssertionResponse\n// that may be used by clients to examine data. If the data is invalid, an error is returned, usually of the type\n// Error.\nfunc ParseAssertionResponse(p AssertionResponse) (ParsedAssertionResponse, error) {\n\tr := ParsedAssertionResponse{}\n\tr.ID, r.RawID, r.Type = p.ID, p.RawID, p.Type\n\tr.Response.Signature = p.Response.Signature\n\tr.RawResponse = p\n\n\t// 6. Let C, the client data claimed as used for the signature, be the result of running an implementation-specific\n\t// JSON parser on JSONtext.\n\tif err := json.Unmarshal(p.Response.ClientDataJSON, &r.Response.ClientData); err != nil {\n\t\treturn ParsedAssertionResponse{}, ErrInvalidRequest.WithDebug(err.Error()).WithHint(\"Unable to parse client data\")\n\t}\n\n\tif err := r.Response.AuthData.UnmarshalBinary(p.Response.AuthenticatorData); err != nil {\n\t\treturn ParsedAssertionResponse{}, ErrInvalidRequest.WithDebug(err.Error()).WithHint(\"Unable to parse auth data\")\n\t}\n\n\treturn r, nil\n}\n\n// IsValidAssertion may be used to check whether an assertion is valid. If originalChallenge is nil, the challenge value\n// will not be checked (INSECURE). If relyingPartyID is empty, the relying party hash will not be checked (INSECURE). If\n// relyingPartyOrigin is empty, the relying party origin will not be checked (INSEUCRE).\n// If cert is nil, the hash will not be checked (INSECURE). Before calling this method, clients should execute the\n// following steps: If the allowCredentials option was given when this authentication ceremony was initiated, verify that\n// credential.id identifies one of the public key credentials that were listed in allowCredentials; If\n// credential.response.userHandle is present, verify that the user identified by this value is the owner of the public\n// key credential identified by credential.id. If the data is invalid, an error is returned, usually of the type\n// Error.\nfunc IsValidAssertion(p ParsedAssertionResponse, originalChallenge []byte, relyingPartyID, relyingPartyOrigin string, cert *x509.Certificate) (bool, error) {\n\t// Check the client data, i.e. steps 7-10\n\tif err := p.Response.ClientData.IsValid(\"webauthn.get\", originalChallenge, relyingPartyOrigin); err != nil {\n\t\treturn false, err\n\t}\n\n\t// Check the auth data, i.e. steps 10-13\n\tif err := p.Response.AuthData.IsValid(relyingPartyID); err != nil {\n\t\treturn false, err\n\t}\n\n\tif cert != nil {\n\t\t// 15. Let hash be the result of computing a hash over the cData using SHA-256.\n\t\tclientDataHash := sha256.Sum256(p.RawResponse.Response.ClientDataJSON)\n\n\t\t// 16. Using the credential public key looked up in step 3, verify that sig is a valid signature over the binary\n\t\t// concatenation of authData and hash.\n\t\tverificationData := append(p.RawResponse.Response.AuthenticatorData, clientDataHash[:]...)\n\t\tif err := cert.CheckSignature(x509.ECDSAWithSHA256, verificationData, p.Response.Signature); err != nil {\n\t\t\treturn false, ErrInvalidSignature.WithDebug(err.Error())\n\t\t}\n\t}\n\n\t// TODO: 17. If the signature counter value authData.signCount is nonzero or the value stored in conjunction with\n\t// credential’s id attribute is nonzero, then run the following sub-step: ...\n\n\treturn true, nil\n}\n"
  },
  {
    "path": "protocol/attestation.go",
    "content": "package protocol\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/json\"\n\n\t\"github.com/ugorji/go/codec\"\n)\n\n// AttestationResponse contains the attributes that are returned to the caller when a new credential is created.\n// https://www.w3.org/TR/webauthn/#publickeycredential\ntype AttestationResponse struct {\n\tPublicKeyCredential\n\t// This attribute contains the authenticator's response to the client’s request to create a public key credential.\n\tResponse AuthenticatorAttestationResponse `json:\"response\"`\n}\n\n// ParsedAttestationResponse is a parsed version of AttestationResponse\n// https://www.w3.org/TR/webauthn/#publickeycredential\ntype ParsedAttestationResponse struct {\n\tParsedPublicKeyCredential\n\t// This attribute contains the authenticator's response to the client’s request to create a public key credential.\n\tResponse ParsedAuthenticatorAttestationResponse\n\t// RawResponse contains the unparsed AttestationResponse.\n\tRawResponse AttestationResponse\n}\n\n// The AuthenticatorAttestationResponse interface represents the authenticator's response to a client’s request for the\n// creation of a new public key credential. It contains information about the new credential that can be used to\n// identify it for later use, and metadata that can be used by the WebAuthn Relying Party to assess the characteristics\n// of the credential during registration.\n// https://www.w3.org/TR/webauthn/#authenticatorattestationresponse\ntype AuthenticatorAttestationResponse struct {\n\tAuthenticatorResponse\n\t// This attribute contains an attestation object, which is opaque to, and cryptographically protected against\n\t// tampering by, the client. The attestation object contains both authenticator data and an attestation statement.\n\t// The former contains the AAGUID, a unique credential ID, and the credential public key. The contents of the\n\t// attestation statement are determined by the attestation statement format used by the authenticator. It also\n\t// contains any additional information that the Relying Party's server requires to validate the attestation\n\t// statement, as well as to decode and validate the authenticator data along with the JSON-serialized client data.\n\t// For more details, see §6.4 Attestation, §6.4.4 Generating an Attestation Object, and Figure 5.\n\tAttestationObject []byte `json:\"attestationObject\"`\n}\n\n// ParsedAuthenticatorAttestationResponse is a parsed version of AuthenticatorAttestationResponse\n// https://www.w3.org/TR/webauthn/#authenticatorattestationresponse\ntype ParsedAuthenticatorAttestationResponse struct {\n\tParsedAuthenticatorResponse\n\t// This attribute contains an attestation object, which is opaque to, and cryptographically protected against\n\t// tampering by, the client. The attestation object contains both authenticator data and an attestation statement.\n\t// The former contains the AAGUID, a unique credential ID, and the credential public key. The contents of the\n\t// attestation statement are determined by the attestation statement format used by the authenticator. It also\n\t// contains any additional information that the Relying Party's server requires to validate the attestation\n\t// statement, as well as to decode and validate the authenticator data along with the JSON-serialized client data.\n\t// For more details, see §6.4 Attestation, §6.4.4 Generating an Attestation Object, and Figure 5.\n\tAttestation Attestation\n}\n\n// Attestation represents the attestionObject. An important component of the attestation object is the attestation\n// statement. This is a specific type of  signed data object, containing statements about a public key credential itself\n// and the authenticator that created it. It contains an attestation signature created using the key of the attesting\n// authority (except for the case of self attestation, when it is created using the credential private key). In order to\n// correctly  interpret an attestation statement, a Relying Party needs to understand these two aspects of attestation:\n// https://www.w3.org/TR/webauthn/#attestation-object\ntype Attestation struct {\n\tFmt      string                 `json:\"fmt\"`\n\tAuthData AuthenticatorData      `json:\"authData\"`\n\tAttStmt  map[string]interface{} `json:\"attStmt\"`\n}\n\n// ParseAttestationResponse will parse a raw AttestationResponse as supplied by a client to a ParsedAttestationResponse\n// that may be used by clients to examine data. If the data is invalid, an error is returned, usually of the type\n// Error.\nfunc ParseAttestationResponse(p AttestationResponse) (ParsedAttestationResponse, error) {\n\tr := ParsedAttestationResponse{}\n\tr.ID, r.RawID, r.Type = p.ID, p.RawID, p.Type\n\tr.RawResponse = p\n\n\t// 2. Let C, the client data claimed as collected during the credential creation, be the result of running an\n\t// implementation-specific JSON parser on JSONtext.\n\tif err := json.Unmarshal(p.Response.ClientDataJSON, &r.Response.ClientData); err != nil {\n\t\treturn ParsedAttestationResponse{}, ErrInvalidRequest.WithDebug(err.Error()).WithHint(\"Unable to parse client data\")\n\t}\n\n\tcbor := codec.CborHandle{}\n\n\t// 8. Perform CBOR decoding on the attestationObject field of the AuthenticatorAttestationResponse structure to\n\t// obtain the attestation statement format fmt, the authenticator data authData, and the attestation statement\n\t// attStmt.\n\tif err := codec.NewDecoder(bytes.NewReader(p.Response.AttestationObject), &cbor).Decode(&r.Response.Attestation); err != nil {\n\t\treturn ParsedAttestationResponse{}, ErrInvalidRequest.WithDebug(err.Error()).WithHint(\"Unable to parse attestation\")\n\t}\n\n\treturn r, nil\n}\n\n// IsValidAttestation may be used to check whether an attestation is valid. If originalChallenge is nil, the challenge value\n// will not be checked (INSECURE). If relyingPartyID is empty, the relying party ID hash will not be checked (INSECURE). If\n// relyingPartyOrigin is empty, the relying party origin will not be checked (INSEUCRE).\n// If the data is invalid, an error is returned, usually of the type Error.\nfunc IsValidAttestation(p ParsedAttestationResponse, originalChallenge []byte, relyingPartyID, relyingPartyOrigin string) (bool, error) {\n\t// Check the client data, i.e. steps 3-6\n\tif err := p.Response.ClientData.IsValid(\"webauthn.create\", originalChallenge, relyingPartyOrigin); err != nil {\n\t\treturn false, err\n\t}\n\n\t// 7. Compute the hash of response.clientDataJSON using SHA-256\n\tclientDataHash := sha256.Sum256(p.RawResponse.Response.ClientDataJSON)\n\n\t// Check the attestation, i.e. steps 9-14\n\tif err := p.Response.Attestation.IsValid(relyingPartyID, clientDataHash[:]); err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\n// IsValid checks whether the Attestation is valid. If relyingPartyID is empty, the relying party ID hash will not be\n// checked (INSEUCRE). To register a new attestation type, use RegisterFormat. If the data is invalid, an error is\n// returned, usually of the type Error.\nfunc (a Attestation) IsValid(relyingPartyID string, clientDataHash []byte) error {\n\t// Check the auth data, i.e. steps 9-11\n\tif err := a.AuthData.IsValid(relyingPartyID); err != nil {\n\t\treturn err\n\t}\n\n\t// 13. Determine the attestation statement format by performing a USASCII case-sensitive match on fmt against the set\n\t// of supported WebAuthn Attestation Statement Format Identifier values.\n\tformat, ok := attestationFormats[a.Fmt]\n\tif !ok {\n\t\treturn ErrUnsupportedAttestationFormat.WithDebugf(\"The attestation format %q is unknown\", a.Fmt)\n\t}\n\n\t// 14. Verify that attStmt is a correct attestation statement, conveying a valid attestation signature, by using the\n\t// attestation statement format fmt’s verification procedure given attStmt, authData and the hash of the serialized\n\t// client data computed in step 7.\n\tif err := format(a, clientDataHash); err != nil {\n\t\treturn err\n\t}\n\n\t// NOTE: However, if permitted by policy, the Relying Party MAY register the credential ID and credential public\n\t// key but treat the credential as one with self attestation (see §6.4.3 Attestation Types). If doing so, the\n\t// Relying Party is asserting there is no cryptographic proof that the public key credential has been generated\n\t// by a particular authenticator model. See [FIDOSecRef] and [UAFProtocol] for a more detailed discussion.\n\n\treturn nil\n}\n"
  },
  {
    "path": "protocol/attestation_registry.go",
    "content": "package protocol\n\n// AttestationFormatFunction will be called when checking whether an Attestation is valid.\ntype AttestationFormatFunction func(Attestation, []byte) error\n\nvar attestationFormats = make(map[string]AttestationFormatFunction)\n\n// RegisterFormat will register an attestation format. If the name already exists, it will be overwritten without\n// warning.\nfunc RegisterFormat(name string, f AttestationFormatFunction) {\n\tattestationFormats[name] = f\n}\n"
  },
  {
    "path": "protocol/challenge.go",
    "content": "package protocol\n\nimport \"crypto/rand\"\n\n// ChallengeSize represents the size of a challenge created by NewChallenge.\nconst ChallengeSize = 32\n\n// Challenge represents a challenge. It is defined as a separate type to make it clear that NewChallenge should\n// be used to create it.\ntype Challenge []byte\n\n// NewChallenge creates a new cryptographically secure random challenge of ChallengeSize bytes.\nfunc NewChallenge() (Challenge, error) {\n\tb := make([]byte, ChallengeSize)\n\t_, err := rand.Read(b)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn b, nil\n}\n"
  },
  {
    "path": "protocol/common.go",
    "content": "package protocol\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\n\t\"github.com/koesie10/webauthn/cose\"\n)\n\n// The PublicKeyCredential interface inherits from Credential [CREDENTIAL-MANAGEMENT-1], and contains the attributes\n// that are returned to the caller when a new credential is created, or a new assertion is requested.\n// See AttestationResponse and AssertionResponse\n// https://www.w3.org/TR/webauthn/#publickeycredential\ntype PublicKeyCredential struct {\n\t// This attribute is inherited from Credential, though PublicKeyCredential overrides Credential's getter, instead\n\t// returning the base64url encoding of the data contained in the object’s [[identifier]] internal slot.\n\tID string `json:\"id\"`\n\t// This attribute returns the ArrayBuffer contained in the [[identifier]] internal slot.\n\tRawID []byte `json:\"rawId\"`\n\t// The PublicKeyCredential interface object's [[type]] internal slot's value is the string \"public-key\".\n\tType string `json:\"type\"`\n}\n\n// ParsedPublicKeyCredential is a parsed version of PublicKeyCredential\n// https://www.w3.org/TR/webauthn/#publickeycredential\ntype ParsedPublicKeyCredential struct {\n\t// This attribute is inherited from Credential, though PublicKeyCredential overrides Credential's getter, instead\n\t// returning the base64url encoding of the data contained in the object’s [[identifier]] internal slot.\n\tID string\n\t// This attribute returns the ArrayBuffer contained in the [[identifier]] internal slot.\n\tRawID []byte\n\t// The PublicKeyCredential interface object's [[type]] internal slot's value is the string \"public-key\".\n\tType string\n}\n\n// AuthenticatorResponse is used by authenticators to respond to Relying Party requests.\n// https://www.w3.org/TR/webauthn/#authenticatorresponse\ntype AuthenticatorResponse struct {\n\t// This attribute contains a JSON serialization of the client data passed to the authenticator by the client in\n\t// its call to either create() or get().\n\tClientDataJSON []byte `json:\"clientDataJSON\"`\n}\n\n// ParsedAuthenticatorResponse is a parsed version of AuthenticatorResponse.\n// https://www.w3.org/TR/webauthn/#authenticatorresponse\ntype ParsedAuthenticatorResponse struct {\n\t// This attribute contains the parsed client data passed to the authenticator by the client in its call to either\n\t// create() or get().\n\tClientData CollectedClientData\n}\n\n// CollectedClientData represents the contextual bindings of both the WebAuthn Relying Party and the client. It is a\n// key-value mapping whose keys are strings. Values can be any type that has a valid encoding in JSON. Its\n// structure is defined by the following Web IDL.\n// https://www.w3.org/TR/webauthn/#client-data\ntype CollectedClientData struct {\n\t// This member contains the string \"webauthn.create\" when creating new credentials, and \"webauthn.get\" when getting\n\t// an assertion from an existing credential. The purpose of this member is to prevent certain types of signature\n\t// confusion attacks (where an attacker substitutes one legitimate signature for another).\n\tType string `json:\"type\"`\n\t// This member contains the base64url encoding of the challenge provided by the RP. See the §13.1 Cryptographic\n\t// Challenges security consideration.\n\tChallenge string `json:\"challenge\"`\n\t// This member contains the fully qualified origin of the requester, as provided to the authenticator by the client,\n\t// in the syntax defined by [RFC6454].\n\tOrigin string `json:\"origin\"`\n\t// This OPTIONAL member contains information about the state of the Token Binding protocol used when communicating\n\t// with the Relying Party. Its absence indicates that the client doesn’t support token binding.\n\tTokenBinding *TokenBinding `json:\"tokenBinding,omitempty\"`\n}\n\n// TokenBinding represents the token binding.\n// https://www.w3.org/TR/webauthn/#dictdef-tokenbinding\ntype TokenBinding struct {\n\t// This member is one of the following:\n\tStatus TokenBindingStatus `json:\"status,omitempty\"`\n\t// This member MUST be present if status is present, and MUST a base64url encoding of the Token Binding ID that was\n\t// used when communicating with the Relying Party.\n\tID string `json:\"id,omitempty\"`\n}\n\n// TokenBindingStatus represents the status of a TokenBinding.\n// https://www.w3.org/TR/webauthn/#enumdef-tokenbindingstatus\ntype TokenBindingStatus string\n\nconst (\n\t// TokenBindingStatusPresent indicates the client supports token binding, but it was not negotiated when\n\t// communicating with the Relying Party.\n\tTokenBindingStatusPresent TokenBindingStatus = \"present\"\n\t// TokenBindingStatusSupported indicates token binding was used when communicating with the Relying Party. In this\n\t// case, the id member MUST be present.\n\tTokenBindingStatusSupported = \"supported\"\n)\n\n// IsValid checks whether the CollectedClientData is valid. If originalChallenge is nil, the challenge value\n// will not be checked (INSECURE). If relyingPartyOrigin is empty, the relying party will not be checked (INSEUCRE).\n// If the data is invalid, an error is returned, usually of the type Error.\nfunc (c CollectedClientData) IsValid(requiredType string, originalChallenge []byte, relyingPartyOrigin string) error {\n\t// Verify that the value of C.type is requiredType\n\tif c.Type != requiredType {\n\t\treturn ErrInvalidType.WithDebugf(\"%q did not match required %q\", c.Type, requiredType)\n\t}\n\n\tif originalChallenge != nil {\n\t\t// Verify that the value of C.challenge matches the challenge that was sent to the authenticator in the\n\t\t// create()/get() call\n\t\tchallenge, err := base64.RawURLEncoding.DecodeString(c.Challenge) // This is raw URL encoding, so the JSON parser does not handle it\n\t\tif err != nil {\n\t\t\treturn ErrInvalidChallenge.WithDebug(err.Error())\n\t\t}\n\t\tif !bytes.Equal(challenge, originalChallenge) {\n\t\t\treturn ErrInvalidChallenge\n\t\t}\n\t}\n\n\t// Verify that the value of C.origin matches the Relying Party's origin.\n\tif relyingPartyOrigin != \"\" && c.Origin != relyingPartyOrigin {\n\t\treturn ErrInvalidOrigin.WithDebugf(\"%q did not match required %q\", relyingPartyOrigin, c.Origin)\n\t}\n\n\t// TODO: Verify that the value of C.tokenBinding.status matches the state of Token Binding for the TLS connection\n\t// over which the assertion was obtained. If Token Binding was used on that TLS connection, also verify that\n\t// C.tokenBinding.id matches the base64url encoding of the Token Binding ID for the connection.\n\n\treturn nil\n}\n\n// AuthenticatorData encodes contextual bindings made by the authenticator. These bindings are controlled\n// by the authenticator itself, and derive their trust from the WebAuthn Relying Party's assessment of the security\n// properties of the authenticator. In one extreme case, the authenticator may be embedded in the client, and its\n// bindings may be no more trustworthy than the client data. At the other extreme, the authenticator may be a discrete\n// entity with high-security hardware and software, connected to the client over a secure channel. In both cases, the\n// Relying Party receives the authenticator data in the same format, and uses its knowledge of the authenticator to\n// make trust decisions.\ntype AuthenticatorData struct {\n\t// SHA-256 hash of the RP ID associated with the credential.\n\tRPIDHash []byte\n\t// Flags\n\tFlags AuthenticatorDataFlags\n\t// Signature counter, 32-bit unsigned big-endian integer.\n\tSignCount uint32\n\t// attested credential data (if present). See §6.4.1 Attested credential data for details. Its length depends on the\n\t// length of the credential ID and credential public key being attested.\n\tAttestedCredentialData AttestedCredentialData\n\t// Raw contains the raw bytes of this AuthenticatorData.\n\tRaw []byte\n}\n\n// IsValid checks whether the AuthenticatorData is valid. If relyingPartyID is empty, the relying party will not be\n// checked (INSEUCRE). If the data is invalid, an error is returned, usually of the type Error.\nfunc (a AuthenticatorData) IsValid(relyingPartyID string) error {\n\t// Verify that the RP ID hash in authData is indeed the SHA-256 hash of the RP ID expected by the RP\n\trpHash := sha256.Sum256([]byte(relyingPartyID))\n\tif relyingPartyID != \"\" && !bytes.Equal(rpHash[:], a.RPIDHash) {\n\t\treturn ErrInvalidOrigin.WithDebugf(\"hash %X did not match required %X\", a.RPIDHash, rpHash[:])\n\t}\n\n\t// Verify that the User Present bit of the flags in authData is set\n\tif !a.Flags.UserPresent() {\n\t\treturn ErrNoUserPresent\n\t}\n\n\treturn nil\n}\n\nvar _ encoding.BinaryUnmarshaler = (*AuthenticatorData)(nil)\nvar _ encoding.BinaryMarshaler = (*AuthenticatorData)(nil)\n\n// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.\nfunc (a *AuthenticatorData) UnmarshalBinary(authData []byte) error {\n\tif len(authData) < 37 {\n\t\treturn ErrInvalidRequest.WithDebug(\"invalid authenticator data\")\n\t}\n\n\ta.RPIDHash = authData[0:32]\n\ta.Flags = AuthenticatorDataFlags(authData[32])\n\ta.SignCount = binary.BigEndian.Uint32(authData[33:37])\n\n\tif a.Flags.HasAttestedCredentialData() && len(authData) > 37 {\n\t\ta.AttestedCredentialData.AAGUID = authData[37:53]\n\t\tcredentialIDLength := binary.BigEndian.Uint16(authData[53:55])\n\n\t\ta.AttestedCredentialData.CredentialID = authData[55 : 55+credentialIDLength]\n\n\t\tvar err error\n\t\ta.AttestedCredentialData.COSEKey, err = cose.ParseCOSE(authData[55+credentialIDLength:])\n\t\tif err != nil {\n\t\t\treturn ErrInvalidRequest.WithDebugf(\"unable to parse COSE key: %v\", err.Error())\n\t\t}\n\t}\n\n\ta.Raw = authData\n\n\treturn nil\n}\n\n// MarshalBinary implements the encoding.BinaryMarshaler interface.\nfunc (a *AuthenticatorData) MarshalBinary() ([]byte, error) {\n\treturn nil, fmt.Errorf(\"unsupported operation\")\n}\n\n// AuthenticatorDataFlags are the flags that are present in the authenticator data.\ntype AuthenticatorDataFlags byte\n\nconst (\n\t// AuthenticatorDataFlagUserPresent indicates the UP flag.\n\tAuthenticatorDataFlagUserPresent = 0x001 // 0000 0001\n\t// AuthenticatorDataFlagUserVerified indicates the UV flag.\n\tAuthenticatorDataFlagUserVerified = 0x004 // 0000 0100\n\t// AuthenticatorDataFlagHasCredentialData indicates the AT flag.\n\tAuthenticatorDataFlagHasCredentialData = 0x040 // 0100 0000\n\t// AuthenticatorDataFlagHasExtension indicates the ED flag.\n\tAuthenticatorDataFlagHasExtension = 0x080 // 1000 0000\n)\n\n// UserPresent returns whether the UP flag is set.\nfunc (f AuthenticatorDataFlags) UserPresent() bool {\n\treturn (f & AuthenticatorDataFlagUserPresent) == AuthenticatorDataFlagUserPresent\n}\n\n// UserVerified returns whether the UV flag is set.\nfunc (f AuthenticatorDataFlags) UserVerified() bool {\n\treturn (f & AuthenticatorDataFlagUserVerified) == AuthenticatorDataFlagUserVerified\n}\n\n// HasAttestedCredentialData returns whether the AT flag is set.\nfunc (f AuthenticatorDataFlags) HasAttestedCredentialData() bool {\n\treturn (f & AuthenticatorDataFlagHasCredentialData) == AuthenticatorDataFlagHasCredentialData\n}\n\n// HasExtensions returns whether the ED flag is set.\nfunc (f AuthenticatorDataFlags) HasExtensions() bool {\n\treturn (f & AuthenticatorDataFlagHasExtension) == AuthenticatorDataFlagHasExtension\n}\n\n// AttestedCredentialData represents the AttestedCredentialData type in the WebAuthn specification.\n// https://www.w3.org/TR/webauthn/#attested-credential-data\ntype AttestedCredentialData struct {\n\t// The AAGUID of the authenticator.\n\tAAGUID []byte\n\t// A probabilistically-unique byte sequence identifying a public key credential source and its authentication\n\t// assertions.\n\tCredentialID []byte\n\t// The decoded credential public key.\n\tCOSEKey interface{}\n}\n"
  },
  {
    "path": "protocol/doc.go",
    "content": "// protocol is a low-level package that closely resembles the WebAuthn specification. You should prefer to use the\n// webauthn package. The main methods in this package are ParseAttestationResponse, ParseAssertionResponse,\n// IsValidAssertion and IsValidAttestation.\n//\n// The version of the specification that is implemented is https://www.w3.org/TR/2018/CR-webauthn-20180807/.\npackage protocol // import \"github.com/koesie10/webauthn/protocol\"\n"
  },
  {
    "path": "protocol/errors.go",
    "content": "package protocol\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// Default errors\nvar (\n\tErrInvalidSignature = &Error{\n\t\tName:        \"invalid_signature\",\n\t\tDescription: \"The signature is invalid\",\n\t\tHint:        \"Check that the provided token is in the correct format\",\n\t\tCode:        http.StatusUnauthorized,\n\t}\n\tErrInvalidRequest = &Error{\n\t\tName:        \"invalid_request\",\n\t\tDescription: \"The request is malformed\",\n\t\tHint:        \"Make sure that the parameters provided are correct\",\n\t\tCode:        http.StatusBadRequest,\n\t}\n\tErrUnsupportedAttestationFormat = &Error{\n\t\tName:        \"unsupported_attestation_format\",\n\t\tDescription: \"The attestation format is unsupported\",\n\t\tCode:        http.StatusBadRequest,\n\t}\n\tErrInvalidAttestation = &Error{\n\t\tName:        \"invalid_attestation\",\n\t\tDescription: \"The attestation is malformed\",\n\t\tHint:        \"Check that you provided a token in the right format.\",\n\t\tCode:        http.StatusBadRequest,\n\t}\n\tErrInvalidType = &Error{\n\t\tName:        \"invalid_type\",\n\t\tDescription: \"The attestion/assertion type is invalid\",\n\t\tHint:        \"Check that the client data was submitted for the right call\",\n\t\tCode:        http.StatusBadRequest,\n\t}\n\tErrInvalidChallenge = &Error{\n\t\tName:        \"invalid_challenge\",\n\t\tDescription: \"The challenge is invalid\",\n\t\tHint:        \"Check that the challenge was supplied for the right request\",\n\t\tCode:        http.StatusBadRequest,\n\t}\n\tErrInvalidOrigin = &Error{\n\t\tName:        \"invalid_origin\",\n\t\tDescription: \"The origin is invalid\",\n\t\tCode:        http.StatusBadRequest,\n\t}\n\tErrNoUserPresent = &Error{\n\t\tName:        \"no_user_present\",\n\t\tDescription: \"No user was presented during authentication\",\n\t\tCode:        http.StatusBadRequest,\n\t}\n)\n\n// Error is a representation of errors returned from this package.\ntype Error struct {\n\t// Name is the name of this error.\n\tName string `json:\"error\"`\n\t// Description is the description of this error.\n\tDescription string `json:\"description\"`\n\t// Hint contains further information about the error.\n\tHint string `json:\"hint,omitempty\"`\n\t// Code contains the status code that should be returned when this error is returned.\n\tCode int `json:\"status_code,omitempty\"`\n\t// Debug contains debug information about this error that should not be shown to the user.\n\tDebug string `json:\"debug,omitempty\"`\n\t// Cause contains the error that caused this error, if available\n\tCause error `json:\"-\"`\n}\n\n// ToWebAuthnError converts any error into the *Error type. If that is not possible, it will return an *Error\n// which wraps the error.\nfunc ToWebAuthnError(err error) *Error {\n\tif e, ok := err.(*Error); ok {\n\t\treturn e\n\t} else if e, ok := errors.Cause(err).(*Error); ok {\n\t\treturn e\n\t}\n\treturn &Error{\n\t\tName:        \"error\",\n\t\tDescription: \"This error was not recognized\",\n\t\tDebug:       err.Error(),\n\t\tCode:        http.StatusInternalServerError,\n\t}\n}\n\n// Error implements the error interface.\nfunc (e *Error) Error() string {\n\treturn e.Name\n}\n\n// WithHintf will add/replace the hint of the error.\nfunc (e *Error) WithHintf(hint string, args ...interface{}) *Error {\n\treturn e.WithHint(fmt.Sprintf(hint, args...))\n}\n\n// WithHint will add/replace the hint of the error.\nfunc (e *Error) WithHint(hint string) *Error {\n\terr := *e\n\terr.Hint = hint\n\treturn &err\n}\n\n// WithDebugf will add/replace the debug information of the error.\nfunc (e *Error) WithDebugf(debug string, args ...interface{}) *Error {\n\treturn e.WithDebug(fmt.Sprintf(debug, args...))\n}\n\n// WithDebug will add/replace the debug information of the error.\nfunc (e *Error) WithDebug(debug string) *Error {\n\terr := *e\n\terr.Debug = debug\n\treturn &err\n}\n\nfunc (e *Error) WithCause(cause error) *Error {\n\terr := *e\n\terr.Cause = cause\n\treturn &err\n}\n"
  },
  {
    "path": "protocol/webauthn_test.go",
    "content": "package protocol_test\n\nimport (\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/koesie10/webauthn/protocol\"\n)\n\nfunc TestIsValidAssertion(t *testing.T) {\n\tfor i := range assertionRequests {\n\t\tt.Run(fmt.Sprintf(\"Run %d\", i), func(t *testing.T) {\n\t\t\trawAttestation := protocol.AttestationResponse{}\n\t\t\tif err := json.Unmarshal([]byte(attestationResponses[i]), &rawAttestation); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tattestation, err := protocol.ParseAttestationResponse(rawAttestation)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tpublicKey := attestation.Response.Attestation.AuthData.AttestedCredentialData.COSEKey\n\n\t\t\tcert := &x509.Certificate{\n\t\t\t\tPublicKey: publicKey,\n\t\t\t}\n\n\t\t\tr := protocol.CredentialCreationOptions{}\n\t\t\tif err := json.Unmarshal([]byte(assertionRequests[i]), &r); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tb := protocol.AssertionResponse{}\n\t\t\tif err := json.Unmarshal([]byte(assertionResponses[i]), &b); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tp, err := protocol.ParseAssertionResponse(b)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\td, err := protocol.IsValidAssertion(p, r.PublicKey.Challenge, \"\", \"\", cert)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif !d {\n\t\t\t\tt.Fatal(\"is not valid\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar attestationResponses = []string{\n\t`{\"id\":\"LOXI3xfiLvIP04MD_S2ZmJYwn3cvMX1FUXxiQO7xlfUvrfcj99UVO2aMrMAwsGvsujY7NHWiM6G3B6ryKJDBBdab-cl4tVZeOwOMhgvHLXk\",\"rawId\":\"LOXI3xfiLvIP04MD/S2ZmJYwn3cvMX1FUXxiQO7xlfUvrfcj99UVO2aMrMAwsGvsujY7NHWiM6G3B6ryKJDBBdab+cl4tVZeOwOMhgvHLXk=\",\"response\":{\"attestationObject\":\"o2dhdHRTdG10omNzaWdYRjBEAiAJ8Q7i8DQzKlb00g4Wby4PoEjlI+s3bS+kVKI3PKoyXQIgDzcP2c5vpplZdmftN+zUDNfXtG1TniWbJv2+6kGZ8bljeDVjgVkBKzCCAScwgc6gAwIBAgIBADAKBggqhkjOPQQDAjAWMRQwEgYDVQQDDAtLcnlwdG9uIEtleTAeFw0xODA5MTcxODQ3NDJaFw0yODA5MTcxODQ3NDJaMBYxFDASBgNVBAMMC0tyeXB0b24gS2V5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwzIpvM5A6mZQXYxRIhfp0sb/21yTcr/sp5Y5DU0IWODQf5ldS2rlDCl62yEaQDM9Akxbsay/vA/S5ut4VSsvoKMNMAswCQYDVR0TBAIwADAKBggqhkjOPQQDAgNIADBFAiA4Yx+5MtKVnjme6V3qXKQ2qcgaHfO6DMgXM9kwOCZcNAIhAJdNk5PPSA04ITfrX9HQy5azo8sH9yhkW7c6gLdb/Kz+aGF1dGhEYXRhWNRJlg3liA6MaHQ0Fw9kdmBbj+SuuaKGMseZXPO6gx2XY0EAAAAALOXI3xfiLvIP04MD/S2ZmABQLOXI3xfiLvIP04MD/S2ZmJYwn3cvMX1FUXxiQO7xlfUvrfcj99UVO2aMrMAwsGvsujY7NHWiM6G3B6ryKJDBBdab+cl4tVZeOwOMhgvHLXmlAQIDJiABIVggwzIpvM5A6mZQXYxRIhfp0sb/21yTcr/sp5Y5DU0IWOAiWCDQf5ldS2rlDCl62yEaQDM9Akxbsay/vA/S5ut4VSsvoGNmbXRoZmlkby11MmY=\",\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiItMWpReXNud2FJak5VLUdyd1JwNFBXTkJNbFgwaTlfY2FSa2NLZDdMUGo4IiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo1Mzg3OSIsInRva2VuQmluZGluZyI6eyJzdGF0dXMiOiJub3Qtc3VwcG9ydGVkIn0sInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ==\"},\"type\":\"public-key\"}`,\n\t`{\"id\":\"SNBSJTt1DHEuG9XBd6lfc4XXqxkppWfFbt4P5sRVQEPIPANIHHCmPo1AwY5pkUGcpVL3W-uHyWEn4vbgzp34Qw\",\"rawId\":\"SNBSJTt1DHEuG9XBd6lfc4XXqxkppWfFbt4P5sRVQEPIPANIHHCmPo1AwY5pkUGcpVL3W+uHyWEn4vbgzp34Qw==\",\"response\":{\"attestationObject\":\"o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIgFls/elhmdZmqEBEKafdcyvQPDrTdBRMW92v6RKJj1bACIQCZ+46sXn65dMEpPuGxvMUruV5i7XN25ctFV/iAi3wSomN4NWOBWQLCMIICvjCCAaagAwIBAgIEdIb9wjANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbzELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTk1NTAwMzg0MjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJVd8633JH0xde/9nMTzGk6HjrrhgQlWYVD7OIsuX2Unv1dAmqWBpQ0KxS8YRFwKE1SKE1PIpOWacE5SO8BN6+2jbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBguUcAgEBBAQDAgUgMCEGCysGAQQBguUcAQEEBBIEEPigEfOMCk0VgAYXER+e3H0wDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAMVxIgOaaUn44Zom9af0KqG9J655OhUVBVW+q0As6AIod3AH5bHb2aDYakeIyyBCnnGMHTJtuekbrHbXYXERIn4aKdkPSKlyGLsA/A+WEi+OAfXrNVfjhrh7iE6xzq0sg4/vVJoywe4eAJx0fS+Dl3axzTTpYl71Nc7p/NX6iCMmdik0pAuYJegBcTckE3AoYEg4K99AM/JaaKIblsbFh8+3LxnemeNf7UwOczaGGvjS6UzGVI0Odf9lKcPIwYhuTxM5CaNMXTZQ7xq4/yTfC3kPWtE4hFT34UJJflZBiLrxG4OsYxkHw/n5vKgmpspB3GfYuYTWhkDKiE8CYtyg87mhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NBAAAAA/igEfOMCk0VgAYXER+e3H0AQEjQUiU7dQxxLhvVwXepX3OF16sZKaVnxW7eD+bEVUBDyDwDSBxwpj6NQMGOaZFBnKVS91vrh8lhJ+L24M6d+EOlAQIDJiABIVggLxxTguKmjCV4N5OMqd2Sl9AIxSltaPevmQxSqnyNlAciWCDEHOaQDaZ6pC2gC+Z0KS4Ln/XQiJp0X1BmTd+K+FdqSg==\",\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiJKVXRsWWNncGtTaUZOenNUaERZdU9ydFNWWTFWZUxvZk0tbVdUUkNDWHFVIiwibmV3X2tleXNfbWF5X2JlX2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0=\"},\"type\":\"public-key\"}`,\n\t`{\"id\":\"EBT1LOefp-8ID0n2jchlyaPrKcWZ6jdHH8nb0Z-hi9JHsOpTpCNUbJ7ijJOKdetLOy2cqdxNq8zkWYmCgpapKg\",\"rawId\":\"EBT1LOefp+8ID0n2jchlyaPrKcWZ6jdHH8nb0Z+hi9JHsOpTpCNUbJ7ijJOKdetLOy2cqdxNq8zkWYmCgpapKg==\",\"response\":{\"attestationObject\":\"o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAJkpVpWsMm/Z1OnF/+B/juq/IAlKqhakms5HkNf6ZKLWAiEAm2qNX/bHUkkdaJ0seanz5xxVDCn+bKGEPyQP3ZpPczNjeDVjgVkCUzCCAk8wggE3oAMCAQICBA0ACxYwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMDExLzAtBgNVBAMMJll1YmljbyBVMkYgRUUgU2VyaWFsIDIzOTI1NzM0MDE1NzY1MjcwMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETKz6btEEuhlL1uBm1+E/zGpgDxDSSFx+o9vUTNDVDbJROHujvR665t7mJQoFWMbpvmEYpEOOWkNfHtLrDOi7haM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAI7CTaiBlYLnMIQZnJ8UCvrqgFuin80CTT4UAiGWsBwh0eY+CRSwL4LEFZITkLlFYyOsfMDlI7oddSN/Jmn8HzrPWvzKVP/+mCuRMSdz735wFNYX5xle+NLkoctZjyHOCqdd4B8lgX0nzwNiPZuf+sdY5fhzhLRmtbpfBDToTP57tLR5WlIY6kJ6QKecpZ5sVNxCzSVxRncAptZV7YSsX2we05Kt5mHkBHqhi5CTPQQmOObHov7cB+4q5CpufDzEBFTKPL3tWxV6HvQr0J6Mp6bZFICq5nTP7VPatnnJelRA9VmPSpQuLjpRqpJFKRobj8eQ9yuveXG/7uutBOzBHW9oYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAQFPUs55+n7wgPSfaNyGXJo+spxZnqN0cfydvRn6GL0kew6lOkI1RsnuKMk4p160s7LZyp3E2rzORZiYKClqkqpQECAyYgASFYIF6oiA6H+mU150XH7WJ2vnzNmdzgr5YloPao7ePjNjlOIlggg0f3u4CtxsBkkKjo7v4luyJui9tJ1rGTBF3YkYlcADo=\",\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiIySHpBbFBJR3NrYm41M2hCSlplSDNrWjZYZmNIV01uemJBVFZHX0ZTZ2tJIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo5MDAwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9\"},\"type\":\"public-key\"}`,\n\t// Android SafetyNet\n\t`{\"id\":\"ARKBRFD84uLN6qG_rHsV0K2Bh9Lj3_HaJsXdC_DpPslKO6ZWmD38-hz90Lf_MzELErMa9AqR21Sr9brNzE2un1U\",\"rawId\":\"ARKBRFD84uLN6qG/rHsV0K2Bh9Lj3/HaJsXdC/DpPslKO6ZWmD38+hz90Lf/MzELErMa9AqR21Sr9brNzE2un1U=\",\"response\":{\"attestationObject\":\"o2NmbXRxYW5kcm9pZC1zYWZldHluZXRnYXR0U3RtdKJjdmVyaDE0MzY2MDE5aHJlc3BvbnNlWRS9ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbmcxWXlJNld5Sk5TVWxHYTJwRFEwSkljV2RCZDBsQ1FXZEpVVkpZY205T01GcFBaRkpyUWtGQlFVRkJRVkIxYm5wQlRrSm5hM0ZvYTJsSE9YY3dRa0ZSYzBaQlJFSkRUVkZ6ZDBOUldVUldVVkZIUlhkS1ZsVjZSV1ZOUW5kSFFURlZSVU5vVFZaU01qbDJXako0YkVsR1VubGtXRTR3U1VaT2JHTnVXbkJaTWxaNlRWSk5kMFZSV1VSV1VWRkVSWGR3U0ZaR1RXZFJNRVZuVFZVNGVFMUNORmhFVkVVMFRWUkJlRTFFUVROTlZHc3dUbFp2V0VSVVJUVk5WRUYzVDFSQk0wMVVhekJPVm05M1lrUkZURTFCYTBkQk1WVkZRbWhOUTFaV1RYaEZla0ZTUW1kT1ZrSkJaMVJEYTA1b1lrZHNiV0l6U25WaFYwVjRSbXBCVlVKblRsWkNRV05VUkZVeGRtUlhOVEJaVjJ4MVNVWmFjRnBZWTNoRmVrRlNRbWRPVmtKQmIxUkRhMlIyWWpKa2MxcFRRazFVUlUxNFIzcEJXa0puVGxaQ1FVMVVSVzFHTUdSSFZucGtRelZvWW0xU2VXSXliR3RNYlU1MllsUkRRMEZUU1hkRVVWbEtTMjlhU1doMlkwNUJVVVZDUWxGQlJHZG5SVkJCUkVORFFWRnZRMmRuUlVKQlRtcFlhM293WlVzeFUwVTBiU3N2UnpWM1QyOHJXRWRUUlVOeWNXUnVPRGh6UTNCU04yWnpNVFJtU3pCU2FETmFRMWxhVEVaSWNVSnJOa0Z0V2xaM01rczVSa2N3VHpseVVsQmxVVVJKVmxKNVJUTXdVWFZ1VXpsMVowaEROR1ZuT1c5MmRrOXRLMUZrV2pKd09UTllhSHAxYmxGRmFGVlhXRU40UVVSSlJVZEtTek5UTW1GQlpucGxPVGxRVEZNeU9XaE1ZMUYxV1ZoSVJHRkROMDlhY1U1dWIzTnBUMGRwWm5NNGRqRnFhVFpJTDNob2JIUkRXbVV5YkVvck4wZDFkSHBsZUV0d2VIWndSUzkwV2xObVlsazVNRFZ4VTJ4Q2FEbG1jR293TVRWamFtNVJSbXRWYzBGVmQyMUxWa0ZWZFdWVmVqUjBTMk5HU3pSd1pYWk9UR0Y0UlVGc0swOXJhV3hOZEVsWlJHRmpSRFZ1Wld3MGVFcHBlWE0wTVROb1lXZHhWekJYYUdnMVJsQXpPV2hIYXpsRkwwSjNVVlJxWVhwVGVFZGtkbGd3YlRaNFJsbG9hQzh5VmsxNVdtcFVORXQ2VUVwRlEwRjNSVUZCWVU5RFFXeG5kMmRuU2xWTlFUUkhRVEZWWkVSM1JVSXZkMUZGUVhkSlJtOUVRVlJDWjA1V1NGTlZSVVJFUVV0Q1oyZHlRbWRGUmtKUlkwUkJWRUZOUW1kT1ZraFNUVUpCWmpoRlFXcEJRVTFDTUVkQk1WVmtSR2RSVjBKQ1VYRkNVWGRIVjI5S1FtRXhiMVJMY1hWd2J6UlhObmhVTm1veVJFRm1RbWRPVmtoVFRVVkhSRUZYWjBKVFdUQm1hSFZGVDNaUWJTdDRaMjU0YVZGSE5rUnlabEZ1T1V0NlFtdENaMmR5UW1kRlJrSlJZMEpCVVZKWlRVWlpkMHAzV1VsTGQxbENRbEZWU0UxQlIwZEhNbWd3WkVoQk5reDVPWFpaTTA1M1RHNUNjbUZUTlc1aU1qbHVUREprTUdONlJuWk5WRUZ5UW1kbmNrSm5SVVpDVVdOM1FXOVpabUZJVWpCalJHOTJURE5DY21GVE5XNWlNamx1VERKa2VtTnFTWFpTTVZKVVRWVTRlRXh0VG5sa1JFRmtRbWRPVmtoU1JVVkdha0ZWWjJoS2FHUklVbXhqTTFGMVdWYzFhMk50T1hCYVF6VnFZakl3ZDBsUldVUldVakJuUWtKdmQwZEVRVWxDWjFwdVoxRjNRa0ZuU1hkRVFWbExTM2RaUWtKQlNGZGxVVWxHUVhwQmRrSm5UbFpJVWpoRlMwUkJiVTFEVTJkSmNVRm5hR2cxYjJSSVVuZFBhVGgyV1ROS2MweHVRbkpoVXpWdVlqSTVia3d3WkZWVmVrWlFUVk0xYW1OdGQzZG5aMFZGUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpKU0RGQ1NVaDVRVkJCUVdSM1EydDFVVzFSZEVKb1dVWkpaVGRGTmt4TldqTkJTMUJFVjFsQ1VHdGlNemRxYW1RNE1FOTVRVE5qUlVGQlFVRlhXbVJFTTFCTVFVRkJSVUYzUWtsTlJWbERTVkZEVTFwRFYyVk1Tblp6YVZaWE5rTm5LMmRxTHpsM1dWUktVbnAxTkVocGNXVTBaVmswWXk5dGVYcHFaMGxvUVV4VFlta3ZWR2g2WTNweGRHbHFNMlJyTTNaaVRHTkpWek5NYkRKQ01HODNOVWRSWkdoTmFXZGlRbWRCU0ZWQlZtaFJSMjFwTDFoM2RYcFVPV1ZIT1ZKTVNTdDRNRm95ZFdKNVdrVldla0UzTlZOWlZtUmhTakJPTUVGQlFVWnRXRkU1ZWpWQlFVRkNRVTFCVW1wQ1JVRnBRbU5EZDBFNWFqZE9WRWRZVURJM09IbzBhSEl2ZFVOSWFVRkdUSGx2UTNFeVN6QXJlVXhTZDBwVlltZEpaMlk0WjBocWRuQjNNbTFDTVVWVGFuRXlUMll6UVRCQlJVRjNRMnR1UTJGRlMwWlZlVm8zWmk5UmRFbDNSRkZaU2t0dldrbG9kbU5PUVZGRlRFSlJRVVJuWjBWQ1FVazVibFJtVWt0SlYyZDBiRmRzTTNkQ1REVTFSVlJXTm10aGVuTndhRmN4ZVVGak5VUjFiVFpZVHpReGExcDZkMG8yTVhkS2JXUlNVbFF2VlhORFNYa3hTMFYwTW1Nd1JXcG5iRzVLUTBZeVpXRjNZMFZYYkV4UldUSllVRXg1Um1wclYxRk9ZbE5vUWpGcE5GY3lUbEpIZWxCb2RETnRNV0kwT1doaWMzUjFXRTAyZEZnMVEzbEZTRzVVYURoQ2IyMDBMMWRzUm1sb2VtaG5iamd4Ukd4a2IyZDZMMHN5VlhkTk5sTTJRMEl2VTBWNGEybFdabllyZW1KS01ISnFkbWM1TkVGc1pHcFZabFYzYTBrNVZrNU5ha1ZRTldVNGVXUkNNMjlNYkRabmJIQkRaVVkxWkdkbVUxZzBWVGw0TXpWdmFpOUpTV1F6VlVVdlpGQndZaTl4WjBkMmMydG1aR1Y2ZEcxVmRHVXZTMU50Y21sM1kyZFZWMWRsV0daVVlra3plbk5wYTNkYVltdHdiVkpaUzIxcVVHMW9kalJ5YkdsNlIwTkhkRGhRYmpod2NUaE5Na3RFWmk5UU0ydFdiM1F6WlRFNFVUMGlMQ0pOU1VsRlUycERRMEY2UzJkQmQwbENRV2RKVGtGbFR6QnRjVWRPYVhGdFFrcFhiRkYxUkVGT1FtZHJjV2hyYVVjNWR6QkNRVkZ6UmtGRVFrMU5VMEYzU0dkWlJGWlJVVXhGZUdSSVlrYzVhVmxYZUZSaFYyUjFTVVpLZG1JelVXZFJNRVZuVEZOQ1UwMXFSVlJOUWtWSFFURlZSVU5vVFV0U01uaDJXVzFHYzFVeWJHNWlha1ZVVFVKRlIwRXhWVVZCZUUxTFVqSjRkbGx0Um5OVk1teHVZbXBCWlVaM01IaE9la0V5VFZSVmQwMUVRWGRPUkVwaFJuY3dlVTFVUlhsTlZGVjNUVVJCZDA1RVNtRk5SVWw0UTNwQlNrSm5UbFpDUVZsVVFXeFdWRTFTTkhkSVFWbEVWbEZSUzBWNFZraGlNamx1WWtkVloxWklTakZqTTFGblZUSldlV1J0YkdwYVdFMTRSWHBCVWtKblRsWkNRVTFVUTJ0a1ZWVjVRa1JSVTBGNFZIcEZkMmRuUldsTlFUQkhRMU54UjFOSllqTkVVVVZDUVZGVlFVRTBTVUpFZDBGM1oyZEZTMEZ2U1VKQlVVUlJSMDA1UmpGSmRrNHdOWHByVVU4NUszUk9NWEJKVW5aS2VucDVUMVJJVnpWRWVrVmFhRVF5WlZCRGJuWlZRVEJSYXpJNFJtZEpRMlpMY1VNNVJXdHpRelJVTW1aWFFsbHJMMnBEWmtNelVqTldXazFrVXk5a1RqUmFTME5GVUZwU2NrRjZSSE5wUzFWRWVsSnliVUpDU2pWM2RXUm5lbTVrU1UxWlkweGxMMUpIUjBac05YbFBSRWxMWjJwRmRpOVRTa2d2VlV3clpFVmhiSFJPTVRGQ2JYTkxLMlZSYlUxR0t5dEJZM2hIVG1oeU5UbHhUUzg1YVd3M01Va3laRTQ0UmtkbVkyUmtkM1ZoWldvMFlsaG9jREJNWTFGQ1ltcDRUV05KTjBwUU1HRk5NMVEwU1N0RWMyRjRiVXRHYzJKcWVtRlVUa001ZFhwd1JteG5UMGxuTjNKU01qVjRiM2x1VlhoMk9IWk9iV3R4TjNwa1VFZElXR3Q0VjFrM2IwYzVhaXRLYTFKNVFrRkNhemRZY2twbWIzVmpRbHBGY1VaS1NsTlFhemRZUVRCTVMxY3dXVE42Tlc5Nk1rUXdZekYwU2t0M1NFRm5UVUpCUVVkcVoyZEZlazFKU1VKTWVrRlBRbWRPVmtoUk9FSkJaamhGUWtGTlEwRlpXWGRJVVZsRVZsSXdiRUpDV1hkR1FWbEpTM2RaUWtKUlZVaEJkMFZIUTBOelIwRlJWVVpDZDAxRFRVSkpSMEV4VldSRmQwVkNMM2RSU1UxQldVSkJaamhEUVZGQmQwaFJXVVJXVWpCUFFrSlpSVVpLYWxJclJ6UlJOamdyWWpkSFEyWkhTa0ZpYjA5ME9VTm1NSEpOUWpoSFFURlZaRWwzVVZsTlFtRkJSa3AyYVVJeFpHNUlRamRCWVdkaVpWZGlVMkZNWkM5alIxbFpkVTFFVlVkRFEzTkhRVkZWUmtKM1JVSkNRMnQzU25wQmJFSm5aM0pDWjBWR1FsRmpkMEZaV1ZwaFNGSXdZMFJ2ZGt3eU9XcGpNMEYxWTBkMGNFeHRaSFppTW1OMldqTk9lVTFxUVhsQ1owNVdTRkk0UlV0NlFYQk5RMlZuU21GQmFtaHBSbTlrU0ZKM1QyazRkbGt6U25OTWJrSnlZVk0xYm1JeU9XNU1NbVI2WTJwSmRsb3pUbmxOYVRWcVkyMTNkMUIzV1VSV1VqQm5Ra1JuZDA1cVFUQkNaMXB1WjFGM1FrRm5TWGRMYWtGdlFtZG5ja0puUlVaQ1VXTkRRVkpaWTJGSVVqQmpTRTAyVEhrNWQyRXlhM1ZhTWpsMlduazVlVnBZUW5aak1td3dZak5LTlV4NlFVNUNaMnR4YUd0cFJ6bDNNRUpCVVhOR1FVRlBRMEZSUlVGSGIwRXJUbTV1TnpoNU5uQlNhbVE1V0d4UlYwNWhOMGhVWjJsYUwzSXpVazVIYTIxVmJWbElVRkZ4TmxOamRHazVVRVZoYW5aM1VsUXlhVmRVU0ZGeU1ESm1aWE54VDNGQ1dUSkZWRlYzWjFwUksyeHNkRzlPUm5ab2MwODVkSFpDUTA5SllYcHdjM2RYUXpsaFNqbDRhblUwZEZkRVVVZzRUbFpWTmxsYVdpOVlkR1ZFVTBkVk9WbDZTbkZRYWxrNGNUTk5SSGh5ZW0xeFpYQkNRMlkxYnpodGR5OTNTalJoTWtjMmVIcFZjalpHWWpaVU9FMWpSRTh5TWxCTVVrdzJkVE5OTkZSNmN6TkJNazB4YWpaaWVXdEtXV2s0ZDFkSlVtUkJka3RNVjFwMUwyRjRRbFppZWxsdGNXMTNhMjAxZWt4VFJGYzFia2xCU21KRlRFTlJRMXAzVFVnMU5uUXlSSFp4YjJaNGN6WkNRbU5EUmtsYVZWTndlSFUyZURaMFpEQldOMU4yU2tORGIzTnBjbE50U1dGMGFpODVaRk5UVmtSUmFXSmxkRGh4THpkVlN6UjJORnBWVGpnd1lYUnVXbm94ZVdjOVBTSmRmUS5leUp1YjI1alpTSTZJbEJQT1U0MWVrY3pZbTlOUms1Nk5UbHFWRU01ZG1vdlp6QkxlalExTjJoRVMyOTRTREZzZURSbVlWazlJaXdpZEdsdFpYTjBZVzF3VFhNaU9qRTFOREEwTURZeU16RTBOellzSW1Gd2ExQmhZMnRoWjJWT1lXMWxJam9pWTI5dExtZHZiMmRzWlM1aGJtUnliMmxrTG1kdGN5SXNJbUZ3YTBScFoyVnpkRk5vWVRJMU5pSTZJbVZSWXl0MmVsVmpaSGd3UmxaT1RIWllTSFZIY0VRd0sxSTRNRGR6VlVWMmNDdEtaV3hsV1ZwemFVRTlJaXdpWTNSelVISnZabWxzWlUxaGRHTm9JanAwY25WbExDSmhjR3REWlhKMGFXWnBZMkYwWlVScFoyVnpkRk5vWVRJMU5pSTZXeUk0VURGelZ6QkZVRXBqYzJ4M04xVjZVbk5wV0V3Mk5IY3JUelV3UldRclVrSkpRM1JoZVRGbk1qUk5QU0pkTENKaVlYTnBZMGx1ZEdWbmNtbDBlU0k2ZEhKMVpYMC5qaFE5a3RMLVVCdEJpX2NQNXd4SGRFejJZWXNGMXBLWXpqOEZUZXdXbVVvWE5CTWJSOWRBaEdDSnJCZ2w4RGZNSzFrMUJFQXdQUzRTMWJVczBXQ3haYmN4cWJtcS12UF9OWHI1QjlDQXkxUUpxdC1tRlRMUm5EZkZ1a2hfQjdZMUxEZUJaaGYtc1E4WnBfQUlncHRnYlBWa2Z6TE1PQVpkeE5xVk91dmU0YmJTSjQ5bWQwVklBbDkwc3h1YXVUT0x5bFpxN2ZhYXRZMGFqQ1VKZkNpRUJiLVZxSUhOZmQySExhaUZwcjVxRUFPeU8tQmx1V204TmVfSWZiMnFkTVZBa2p1a1YyVmZheElLbG93a05HZ2ZycjFjVVpJNE5oVTNfeDhLTjRGclpQd29tT2ZFdmlHWk9tZFRvNnNPSXpROTJVYkx2MXlGYUw1TDZIZ1I2Z1NnX0FoYXV0aERhdGFYxSpD77HzPafHbmULkXmwl2mw9P/lQRkLyNgWFO1qQIjVRQAAAAAAAAAAAAAAAAAAAAAAAAAAAEEBEoFEUPzi4s3qob+sexXQrYGH0uPf8domxd0L8Ok+yUo7plaYPfz6HP3Qt/8zMQsSsxr0CpHbVKv1us3MTa6fVaUBAgMmIAEhWCDavINo/+JM4T1eJeKjaZ+vGa2Do7YVh2EyD0vtmoZrrCJYIOtAindNbNXogAQxBAJii2Vd1Wl5rZb9KPak8J6iTKle\",\"clientDataJSON\":\"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiZDNjWTFJNm4xYXI2Z0xwREVoVGk1bkJnUDF4d0lHc2I2SE1fTlI4UEsxbyIsIm9yaWdpbiI6Imh0dHBzOlwvXC9iMzk5ZmEwMC5uZ3Jvay5pbyIsImFuZHJvaWRQYWNrYWdlTmFtZSI6ImNvbS5hbmRyb2lkLmNocm9tZSJ9\"},\"type\":\"public-key\"}`,\n}\n\nvar assertionRequests = []string{\n\t`{\"publicKey\":{\"allowCredentials\":[{\"id\":\"LOXI3xfiLvIP04MD/S2ZmJYwn3cvMX1FUXxiQO7xlfUvrfcj99UVO2aMrMAwsGvsujY7NHWiM6G3B6ryKJDBBdab+cl4tVZeOwOMhgvHLXk=\",\"type\":\"public-key\"}],\"challenge\":\"+c0hMsULvTWp6ASl45YyOQRA/yVVK60XccCQ+Vui9j8=\",\"timeout\":10000}}`,\n\t`{\"publicKey\":{\"challenge\":\"mcPXIDRHSPBF2gJWU58GPrR3TodLDXR1kHJhgVanYnU=\",\"timeout\":30000,\"allowCredentials\":[{\"type\":\"public-key\",\"id\":\"SNBSJTt1DHEuG9XBd6lfc4XXqxkppWfFbt4P5sRVQEPIPANIHHCmPo1AwY5pkUGcpVL3W+uHyWEn4vbgzp34Qw==\"}]}}`,\n\t`{\"publicKey\":{\"challenge\":\"/hXFS7WKYWTgqEx5AOG7SuGL3+6alkqi2TJkTu+MkBM=\",\"timeout\":30000,\"allowCredentials\":[{\"type\":\"public-key\",\"id\":\"EBT1LOefp+8ID0n2jchlyaPrKcWZ6jdHH8nb0Z+hi9JHsOpTpCNUbJ7ijJOKdetLOy2cqdxNq8zkWYmCgpapKg==\"}]}}`,\n\t// Android SafetyNet\n\t`{\"publicKey\":{\"challenge\":\"HC33hV7jFYx6m4hUkvNF0GLVn2WihTilaEtniha+Qvw=\",\"timeout\":30000,\"allowCredentials\":[{\"type\":\"public-key\",\"id\":\"ARKBRFD84uLN6qG/rHsV0K2Bh9Lj3/HaJsXdC/DpPslKO6ZWmD38+hz90Lf/MzELErMa9AqR21Sr9brNzE2un1U=\"}]}}`,\n}\n\nvar assertionResponses = []string{\n\t`{\"id\":\"LOXI3xfiLvIP04MD_S2ZmJYwn3cvMX1FUXxiQO7xlfUvrfcj99UVO2aMrMAwsGvsujY7NHWiM6G3B6ryKJDBBdab-cl4tVZeOwOMhgvHLXk\",\"rawId\":\"LOXI3xfiLvIP04MD/S2ZmJYwn3cvMX1FUXxiQO7xlfUvrfcj99UVO2aMrMAwsGvsujY7NHWiM6G3B6ryKJDBBdab+cl4tVZeOwOMhgvHLXk=\",\"response\":{\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiItYzBoTXNVTHZUV3A2QVNsNDVZeU9RUkFfeVZWSzYwWGNjQ1EtVnVpOWo4IiwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjUzODc5IiwidHlwZSI6IndlYmF1dGhuLmdldCJ9\",\"authenticatorData\":\"SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MBAAAAAQ==\",\"signature\":\"MEYCIQD7W6TPIviP+BztYxEMsan/esy/O0S4pJO+9QxDaA0ehAIhANo5D+5UxwbtJGFcvSryl0+RdJd3j4lIKVhEe7WpvZeV\",\"userHandle\":\"\"},\"type\":\"public-key\"}`,\n\t`{\"id\":\"SNBSJTt1DHEuG9XBd6lfc4XXqxkppWfFbt4P5sRVQEPIPANIHHCmPo1AwY5pkUGcpVL3W-uHyWEn4vbgzp34Qw\",\"rawId\":\"SNBSJTt1DHEuG9XBd6lfc4XXqxkppWfFbt4P5sRVQEPIPANIHHCmPo1AwY5pkUGcpVL3W+uHyWEn4vbgzp34Qw==\",\"response\":{\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiJtY1BYSURSSFNQQkYyZ0pXVTU4R1ByUjNUb2RMRFhSMWtISmhnVmFuWW5VIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo5MDAwIiwidHlwZSI6IndlYmF1dGhuLmdldCJ9\",\"authenticatorData\":\"SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MBAAAABA==\",\"signature\":\"MEUCIQCWGnyWIV4s13/9TRcLtDesxa0UJs+pwNaF3YDP/5RHDwIgIWlEiH74R7sPiyNffp8Tof3qo1s8jVvFDxCGejlICFI=\",\"userHandle\":\"\"},\"type\":\"public-key\"}`,\n\t`{\"id\":\"EBT1LOefp-8ID0n2jchlyaPrKcWZ6jdHH8nb0Z-hi9JHsOpTpCNUbJ7ijJOKdetLOy2cqdxNq8zkWYmCgpapKg\",\"rawId\":\"EBT1LOefp+8ID0n2jchlyaPrKcWZ6jdHH8nb0Z+hi9JHsOpTpCNUbJ7ijJOKdetLOy2cqdxNq8zkWYmCgpapKg==\",\"response\":{\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiJfaFhGUzdXS1lXVGdxRXg1QU9HN1N1R0wzLTZhbGtxaTJUSmtUdS1Na0JNIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo5MDAwIiwidHlwZSI6IndlYmF1dGhuLmdldCJ9\",\"authenticatorData\":\"SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MBAAAAAA==\",\"signature\":\"MEUCIFGAxD82g/HBEQc2qblhIQsOCvMIuFzmiT54uMSCwYg6AiEAuuIUy6PyaW43xEpAnqrPcCPmUiJwpJ7IV/h6OGjqN2E=\",\"userHandle\":\"\"},\"type\":\"public-key\"}`,\n\t// Android SafetyNet\n\t`{\"id\":\"ARKBRFD84uLN6qG_rHsV0K2Bh9Lj3_HaJsXdC_DpPslKO6ZWmD38-hz90Lf_MzELErMa9AqR21Sr9brNzE2un1U\",\"rawId\":\"ARKBRFD84uLN6qG/rHsV0K2Bh9Lj3/HaJsXdC/DpPslKO6ZWmD38+hz90Lf/MzELErMa9AqR21Sr9brNzE2un1U=\",\"response\":{\"clientDataJSON\":\"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiSEMzM2hWN2pGWXg2bTRoVWt2TkYwR0xWbjJXaWhUaWxhRXRuaWhhLVF2dyIsIm9yaWdpbiI6Imh0dHBzOlwvXC9iMzk5ZmEwMC5uZ3Jvay5pbyIsImFuZHJvaWRQYWNrYWdlTmFtZSI6ImNvbS5hbmRyb2lkLmNocm9tZSJ9\",\"authenticatorData\":\"KkPvsfM9p8duZQuRebCXabD0/+VBGQvI2BYU7WpAiNUFAAAAAQ==\",\"signature\":\"MEQCIBapcKD8L5Kp92QBr4XpHNwiRPjo/MGTEIEwCsklxfvAAiABn02rbcatTqHFtHwbnHwdNOLa5apxCBRuFPPwABBm3w==\",\"userHandle\":\"\"},\"type\":\"public-key\"}`,\n}\n"
  },
  {
    "path": "webauthn/config.go",
    "content": "package webauthn\n\nimport \"fmt\"\n\nvar defaultSessionKeyPrefixChallenge = \"webauthn.challenge\"\nvar defaultSessionKeyPrefixUserID = \"webauthn.user.id\"\n\n// Config holds all the configuration for WebAuthn\ntype Config struct {\n\t// RelyingPartyName is a human-palatable identifier for the Relying Party, intended only for display. For example,\n\t// \"ACME Corporation\", \"Wonderful Widgets, Inc.\" or \"ОАО Примертех\".\n\tRelyingPartyName string\n\t// RelyingPartyID is a unique identifier for the Relying Party entity. It must be a valid domain string that\n\t// identifies the Relying Party on whose behalf a registration or login is being performed. A public key credential\n\t// can only be used for authentication with the same RP ID it was registered with.\n\t// By default, it is set to the caller's origin effective domain. It may be overridden, as long as the RP ID is\n\t// a registrable domain suffix or is equal to the caller's effective domain.\n\t// For example, given a Relying Party whose origin is https://login.example.com:1337, then the following RP IDs\n\t// are valid: login.example.com (default) and example.com, but not m.login.example.com and not com.\n\t// In production, this value should be set. If it is not set, the implementation is INSECURE and the RP ID hash\n\t// supplied by the authenticator will not be checked.\n\tRelyingPartyID string\n\t// RelyingPartyOrigin is the RP origin that an authenticator response will be compared with. If it is empty,\n\t// the value will be ignored. However, this is INSECURE and should not be used in production.\n\t// For example, given a Relying Party whose origin is https://login.example.com:1337, this value should be set\n\t// to \"https://login.example.com:1337\".\n\tRelyingPartyOrigin string\n\n\t// AuthenticatorStore will be used to store authenticators of a user.\n\tAuthenticatorStore AuthenticatorStore\n\n\t// SessionKeyPrefixChallenge holds the prefix of the key of the challenge in the session. If it is not set, it will\n\t// be set to \"webauthn.challenge\".\n\tSessionKeyPrefixChallenge string\n\t// SessionKeyPrefixUserID holds the prefix of the key of the user ID in the session. If it is not set, it will be\n\t// set to \"webauthn.user.id\".\n\tSessionKeyPrefixUserID string\n\n\t// Timeout is the amount of time in milliseconds the user will be permitted to authenticate with their device on\n\t// registration and login. The default is 30000, i.e. 30 seconds.\n\tTimeout uint\n\n\t// Debug sets a few settings related to ease of debugging, such as sharing more error information to clients.\n\tDebug bool\n}\n\n// Validate validates that all required fields in Config are set.\nfunc (c *Config) Validate() error {\n\tif c.RelyingPartyName == \"\" {\n\t\treturn fmt.Errorf(\"missing RelyingPartyName\")\n\t}\n\n\tif c.AuthenticatorStore == nil {\n\t\treturn fmt.Errorf(\"missing AuthenticatorStore\")\n\t}\n\n\tif c.SessionKeyPrefixChallenge == \"\" {\n\t\tc.SessionKeyPrefixChallenge = defaultSessionKeyPrefixChallenge\n\t}\n\tif c.SessionKeyPrefixUserID == \"\" {\n\t\tc.SessionKeyPrefixUserID = defaultSessionKeyPrefixUserID\n\t}\n\tif c.Timeout == 0 {\n\t\tc.Timeout = 30000\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "webauthn/doc.go",
    "content": "// webauthn is a high-level package that contains HTTP request handlers which can be used to implement webauthn\n// in any application.\n//\npackage webauthn // import \"github.com/koesie10/webauthn/webauthn\"\n"
  },
  {
    "path": "webauthn/login.go",
    "content": "package webauthn\n\nimport (\n\t\"bytes\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/koesie10/webauthn/protocol\"\n)\n\n// GetLoginOptions will return the options that need to be passed to navigator.credentials.get(). This should\n// be returned to the user via e.g. JSON over HTTP. For convenience, use StartLogin.\nfunc (w *WebAuthn) GetLoginOptions(user User, session Session) (*protocol.CredentialRequestOptions, error) {\n\tchal, err := protocol.NewChallenge()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\toptions := &protocol.CredentialRequestOptions{\n\t\tPublicKey: protocol.PublicKeyCredentialRequestOptions{\n\t\t\tChallenge: chal,\n\t\t\tTimeout:   w.Config.Timeout,\n\t\t},\n\t}\n\n\tif user != nil {\n\t\tauthenticators, err := w.Config.AuthenticatorStore.GetAuthenticators(user)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tallowCredentials := make([]protocol.PublicKeyCredentialDescriptor, len(authenticators))\n\n\t\tfor i, authr := range authenticators {\n\t\t\tallowCredentials[i] = protocol.PublicKeyCredentialDescriptor{\n\t\t\t\tID:   authr.WebAuthCredentialID(),\n\t\t\t\tType: protocol.PublicKeyCredentialTypePublicKey,\n\t\t\t}\n\t\t}\n\n\t\toptions.PublicKey.AllowCredentials = allowCredentials\n\t}\n\n\tif err := session.Set(w.Config.SessionKeyPrefixChallenge+\".login\", []byte(chal)); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn options, nil\n}\n\n// StartLogin is a HTTP request handler which writes the options to be passed to navigator.credentials.get()\n// to the http.ResponseWriter. The user argument is optional and can be nil, in which case the allowCredentials\n// option will not be set and AuthenticatorStore.GetAuthenticators will not be called.\nfunc (w *WebAuthn) StartLogin(r *http.Request, rw http.ResponseWriter, user User, session Session) {\n\toptions, err := w.GetLoginOptions(user, session)\n\tif err != nil {\n\t\tw.writeError(r, rw, err)\n\t\treturn\n\t}\n\n\tw.write(r, rw, options)\n}\n\n// ParseAndFinishLogin should receive the response of navigator.credentials.get(). If\n// user is non-nil, it will be checked that the authenticator is owned by that user. If the request is valid,\n// the authenticator will be returned. For convenience, use FinishLogin.\nfunc (w *WebAuthn) ParseAndFinishLogin(assertionResponse protocol.AssertionResponse, user User, session Session) (Authenticator, error) {\n\trawChal, err := session.Get(w.Config.SessionKeyPrefixChallenge + \".login\")\n\tif err != nil {\n\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"missing challenge in session\")\n\t}\n\tchal, ok := rawChal.([]byte)\n\tif !ok {\n\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"invalid challenge session value\")\n\t}\n\n\tif err := session.Delete(w.Config.SessionKeyPrefixChallenge + \".login\"); err != nil {\n\t\treturn nil, err\n\t}\n\n\tp, err := protocol.ParseAssertionResponse(assertionResponse)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 1. If the allowCredentials option was given when this authentication ceremony was initiated, verify that\n\t// credential.id identifies one of the public key credentials that were listed in allowCredentials.\n\tif user != nil {\n\t\tauthenticators, err := w.Config.AuthenticatorStore.GetAuthenticators(user)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar authrFound bool\n\t\tfor _, authr := range authenticators {\n\t\t\tif bytes.Equal(authr.WebAuthID(), p.RawID) {\n\t\t\t\tauthrFound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !authrFound {\n\t\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"authenticator is not owned by user\")\n\t\t}\n\t}\n\n\t// 2. If credential.response.userHandle is present, verify that the user identified by this value is the owner of\n\t// the public key credential identified by credential.id.\n\tif p.Response.UserHandle != nil && len(p.Response.UserHandle) > 0 {\n\t\tif user != nil {\n\t\t\tif !bytes.Equal(p.Response.UserHandle, user.WebAuthID()) {\n\t\t\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"authenticator's user handle does not equal user ID\")\n\t\t\t}\n\t\t} else {\n\t\t\tauthenticators, err := w.Config.AuthenticatorStore.GetAuthenticators(&defaultUser{id: p.Response.UserHandle})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tvar authrFound bool\n\t\t\tfor _, authr := range authenticators {\n\t\t\t\tif bytes.Equal(authr.WebAuthID(), p.RawID) {\n\t\t\t\t\tauthrFound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !authrFound {\n\t\t\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"authenticator is not owned by user\")\n\t\t\t}\n\t\t}\n\t}\n\n\t// Using credential’s id attribute (or the corresponding rawId, if base64url encoding is inappropriate for your use\n\t// case), look up the corresponding credential public key.\n\tauthr, err := w.Config.AuthenticatorStore.GetAuthenticator(p.RawID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tblock, _ := pem.Decode(authr.WebAuthPublicKey())\n\tif block == nil {\n\t\treturn nil, fmt.Errorf(\"invalid stored public key, unable to decode\")\n\t}\n\n\tcert, err := x509.ParsePKIXPublicKey(block.Bytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalid, err := protocol.IsValidAssertion(p, chal, w.Config.RelyingPartyID, w.Config.RelyingPartyOrigin, &x509.Certificate{\n\t\tPublicKey: cert,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !valid {\n\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"invalid login\")\n\t}\n\n\treturn authr, nil\n}\n\n// FinishLogin is a HTTP request handler which should receive the response of navigator.credentials.get(). If\n// user is non-nil, it will be checked that the authenticator is owned by that user. If the request is valid,\n// the authenticator will be returned and nothing will have been written to http.ResponseWriter. If authenticator is\n// nil, an error has been written to http.ResponseWriter and should be returned as-is.\nfunc (w *WebAuthn) FinishLogin(r *http.Request, rw http.ResponseWriter, user User, session Session) Authenticator {\n\tvar assertionResponse protocol.AssertionResponse\n\n\td := json.NewDecoder(r.Body)\n\td.DisallowUnknownFields()\n\tif err := d.Decode(&assertionResponse); err != nil {\n\t\tw.writeError(r, rw, protocol.ErrInvalidRequest.WithDebug(err.Error()))\n\t\treturn nil\n\t}\n\n\tauthr, err := w.ParseAndFinishLogin(assertionResponse, user, session)\n\tif err != nil {\n\t\tw.writeError(r, rw, err)\n\t\treturn nil\n\t}\n\n\treturn authr\n}\n"
  },
  {
    "path": "webauthn/registration.go",
    "content": "package webauthn\n\nimport (\n\t\"bytes\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"net/http\"\n\n\t\"github.com/koesie10/webauthn/protocol\"\n)\n\n// GetRegistrationOptions will return the options that need to be passed to navigator.credentials.create(). This should\n// be returned to the user via e.g. JSON over HTTP. For convenience, use StartRegistration.\nfunc (w *WebAuthn) GetRegistrationOptions(user User, session Session) (*protocol.CredentialCreationOptions, error) {\n\tchal, err := protocol.NewChallenge()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tu := protocol.PublicKeyCredentialUserEntity{\n\t\tID: user.WebAuthID(),\n\t\tPublicKeyCredentialEntity: protocol.PublicKeyCredentialEntity{\n\t\t\tName: user.WebAuthName(),\n\t\t},\n\t\tDisplayName: user.WebAuthDisplayName(),\n\t}\n\n\toptions := &protocol.CredentialCreationOptions{\n\t\tPublicKey: protocol.PublicKeyCredentialCreationOptions{\n\t\t\tChallenge: chal,\n\t\t\tRP: protocol.PublicKeyCredentialRpEntity{\n\t\t\t\tID: w.Config.RelyingPartyID,\n\t\t\t\tPublicKeyCredentialEntity: protocol.PublicKeyCredentialEntity{\n\t\t\t\t\tName: w.Config.RelyingPartyName,\n\t\t\t\t},\n\t\t\t},\n\t\t\tPubKeyCredParams: []protocol.PublicKeyCredentialParameters{\n\t\t\t\t{\n\t\t\t\t\tType:      protocol.PublicKeyCredentialTypePublicKey,\n\t\t\t\t\tAlgorithm: protocol.ES256,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTimeout:     w.Config.Timeout,\n\t\t\tUser:        u,\n\t\t\tAttestation: protocol.AttestationConveyancePreferenceDirect,\n\t\t},\n\t}\n\n\tauthenticators, err := w.Config.AuthenticatorStore.GetAuthenticators(user)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\texcludeCredentials := make([]protocol.PublicKeyCredentialDescriptor, len(authenticators))\n\n\tfor i, authr := range authenticators {\n\t\texcludeCredentials[i] = protocol.PublicKeyCredentialDescriptor{\n\t\t\tID:   authr.WebAuthCredentialID(),\n\t\t\tType: protocol.PublicKeyCredentialTypePublicKey,\n\t\t}\n\t}\n\n\toptions.PublicKey.ExcludeCredentials = excludeCredentials\n\n\tif err := session.Set(w.Config.SessionKeyPrefixChallenge+\".register\", []byte(chal)); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := session.Set(w.Config.SessionKeyPrefixUserID+\".register\", u.ID); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn options, nil\n}\n\n// StartRegistration is a HTTP request handler which writes the options to be passed to navigator.credentials.create()\n// to the http.ResponseWriter.\nfunc (w *WebAuthn) StartRegistration(r *http.Request, rw http.ResponseWriter, user User, session Session) {\n\toptions, err := w.GetRegistrationOptions(user, session)\n\tif err != nil {\n\t\tw.writeError(r, rw, err)\n\t\treturn\n\t}\n\n\tw.write(r, rw, options)\n}\n\n// ParseAndFinishRegistration should receive the response of navigator.credentials.create(). If\n// the request is valid, AuthenticatorStore.AddAuthenticator will be called and the authenticator that was registered\n// will be returned. For convenience, use FinishRegistration.\nfunc (w *WebAuthn) ParseAndFinishRegistration(attestationResponse protocol.AttestationResponse, user User, session Session) (Authenticator, error) {\n\trawChal, err := session.Get(w.Config.SessionKeyPrefixChallenge + \".register\")\n\tif err != nil {\n\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"missing challenge in session\")\n\t}\n\tchal, ok := rawChal.([]byte)\n\tif !ok {\n\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"invalid challenge session value\")\n\t}\n\tif err := session.Delete(w.Config.SessionKeyPrefixChallenge + \".register\"); err != nil {\n\t\treturn nil, err\n\t}\n\n\trawUserID, err := session.Get(w.Config.SessionKeyPrefixUserID + \".register\")\n\tif err != nil {\n\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"missing user ID in session\")\n\t}\n\tuserID, ok := rawUserID.([]byte)\n\tif !ok {\n\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"invalid user ID session value\")\n\t}\n\tif err := session.Delete(w.Config.SessionKeyPrefixUserID + \".register\"); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !bytes.Equal(user.WebAuthID(), userID) {\n\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"user has changed since start of registration\")\n\t}\n\n\tp, err := protocol.ParseAttestationResponse(attestationResponse)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalid, err := protocol.IsValidAttestation(p, chal, w.Config.RelyingPartyID, w.Config.RelyingPartyOrigin)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !valid {\n\t\treturn nil, protocol.ErrInvalidRequest.WithDebug(\"invalid registration\")\n\t}\n\n\tdata, err := x509.MarshalPKIXPublicKey(p.Response.Attestation.AuthData.AttestedCredentialData.COSEKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tauthr := &defaultAuthenticator{\n\t\tid:           p.RawID,\n\t\tcredentialID: p.Response.Attestation.AuthData.AttestedCredentialData.CredentialID,\n\t\tpublicKey: pem.EncodeToMemory(&pem.Block{\n\t\t\tType:  \"PUBLIC KEY\",\n\t\t\tBytes: data,\n\t\t}),\n\t\taaguid:    p.Response.Attestation.AuthData.AttestedCredentialData.AAGUID,\n\t\tsignCount: p.Response.Attestation.AuthData.SignCount,\n\t}\n\n\tif err := w.Config.AuthenticatorStore.AddAuthenticator(user, authr); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn authr, nil\n}\n\n// FinishRegistration is a HTTP request handler which should receive the response of navigator.credentials.create(). If\n// the request is valid, AuthenticatorStore.AddAuthenticator will be called and an empty response with HTTP status code\n// 201 (Created) will be written to the http.ResponseWriter. If authenticator is  nil, an error has been written to\n// http.ResponseWriter and should be returned as-is.\nfunc (w *WebAuthn) FinishRegistration(r *http.Request, rw http.ResponseWriter, user User, session Session) Authenticator {\n\tvar attestationResponse protocol.AttestationResponse\n\td := json.NewDecoder(r.Body)\n\td.DisallowUnknownFields()\n\tif err := d.Decode(&attestationResponse); err != nil {\n\t\tw.writeError(r, rw, protocol.ErrInvalidRequest.WithDebug(err.Error()))\n\t\treturn nil\n\t}\n\n\tauthr, err := w.ParseAndFinishRegistration(attestationResponse, user, session)\n\tif err != nil {\n\t\tw.writeError(r, rw, err)\n\t\treturn nil\n\t}\n\n\trw.WriteHeader(http.StatusCreated)\n\n\treturn authr\n}\n"
  },
  {
    "path": "webauthn/session.go",
    "content": "package webauthn\n\n// Session will be used by the request handlers to save temporary data, such as the challenge and user ID.\ntype Session interface {\n\tSet(name string, value interface{}) error\n\tGet(name string) (interface{}, error)\n\tDelete(name string) error\n}\n\nvar _ Session = (*mapSession)(nil)\n\ntype mapSession struct {\n\tValues map[interface{}]interface{}\n}\n\nfunc (s *mapSession) Get(name string) (interface{}, error) {\n\treturn s.Values[name], nil\n}\n\nfunc (s *mapSession) Set(name string, value interface{}) error {\n\ts.Values[name] = value\n\treturn nil\n}\n\nfunc (s *mapSession) Delete(name string) error {\n\tdelete(s.Values, name)\n\treturn nil\n}\n\n// WrapMap can be used to create a Session for e.g. a gorilla/sessions type.\nfunc WrapMap(values map[interface{}]interface{}) Session {\n\treturn &mapSession{values}\n}\n"
  },
  {
    "path": "webauthn/user.go",
    "content": "package webauthn\n\n// User should be implemented by users used in the request handlers.\ntype User interface {\n\t// WebAuthID should return the ID of the user. This could for example be the binary encoding of an int.\n\tWebAuthID() []byte\n\t// WebAuthName should return the name of the user.\n\tWebAuthName() string\n\t// WebAuthDisplayName should return the display name of the user.\n\tWebAuthDisplayName() string\n}\n\n// Authenticator represents an authenticator that can be used by a user.\ntype Authenticator interface {\n\tWebAuthID() []byte\n\tWebAuthCredentialID() []byte\n\tWebAuthPublicKey() []byte\n\tWebAuthAAGUID() []byte\n\tWebAuthSignCount() uint32\n}\n\n// AuthenticatorStore should be implemented by the storage layer to store authenticators.\ntype AuthenticatorStore interface {\n\t// AddAuthenticator should add the given authenticator to a user. The authenticator's type should not be depended\n\t// on; it is constructed by this package. All information should be stored in a way such that it is retrievable\n\t// in the future using GetAuthenticator and GetAuthenticators.\n\tAddAuthenticator(user User, authenticator Authenticator) error\n\t// GetAuthenticator gets a single Authenticator by the given id, as returned by Authenticator.WebAuthID.\n\tGetAuthenticator(id []byte) (Authenticator, error)\n\t// GetAuthenticators gets a list of all registered authenticators for this user. It might be the case that the user\n\t// has been constructed by this package and the only non-empty value is the WebAuthID. In this case, the store\n\t// should still return the authenticators as specified by the ID.\n\tGetAuthenticators(user User) ([]Authenticator, error)\n}\n\ntype defaultUser struct {\n\tid []byte\n}\n\nvar _ User = (*defaultUser)(nil)\n\nfunc (u *defaultUser) WebAuthID() []byte {\n\treturn u.id\n}\n\nfunc (u *defaultUser) WebAuthName() string {\n\treturn \"default\"\n}\n\nfunc (u *defaultUser) WebAuthDisplayName() string {\n\treturn \"default\"\n}\n\ntype defaultAuthenticator struct {\n\tid           []byte\n\tcredentialID []byte\n\tpublicKey    []byte\n\taaguid       []byte\n\tsignCount    uint32\n}\n\nvar _ Authenticator = (*defaultAuthenticator)(nil)\n\nfunc (a *defaultAuthenticator) WebAuthID() []byte {\n\treturn a.id\n}\n\nfunc (a *defaultAuthenticator) WebAuthCredentialID() []byte {\n\treturn a.credentialID\n}\n\nfunc (a *defaultAuthenticator) WebAuthPublicKey() []byte {\n\treturn a.publicKey\n}\n\nfunc (a *defaultAuthenticator) WebAuthAAGUID() []byte {\n\treturn a.aaguid\n}\n\nfunc (a *defaultAuthenticator) WebAuthSignCount() uint32 {\n\treturn a.signCount\n}\n"
  },
  {
    "path": "webauthn/webauthn.go",
    "content": "package webauthn\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/koesie10/webauthn/protocol\"\n\t\"github.com/pkg/errors\"\n)\n\n// WebAuthn is the primary interface of this package and contains the request handlers that should be called.\ntype WebAuthn struct {\n\tConfig *Config\n}\n\n// New creates a new WebAuthn based on the given Config. The Config will be validated and an error will be returned\n// if it is invalid.\nfunc New(c *Config) (*WebAuthn, error) {\n\tif err := c.Validate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to validate config: %v\", err)\n\t}\n\treturn &WebAuthn{\n\t\tConfig: c,\n\t}, nil\n}\n\nfunc (w *WebAuthn) write(r *http.Request, rw http.ResponseWriter, res interface{}) {\n\tw.writeCode(r, rw, http.StatusOK, res)\n}\n\nfunc (w *WebAuthn) writeCode(r *http.Request, rw http.ResponseWriter, code int, res interface{}) {\n\tjs, err := json.Marshal(res)\n\tif err != nil {\n\t\tw.writeError(r, rw, err)\n\t\treturn\n\t}\n\n\tif code == 0 {\n\t\tcode = http.StatusOK\n\t}\n\n\trw.Header().Set(\"Content-Type\", \"application/json\")\n\trw.WriteHeader(code)\n\trw.Write(js)\n}\n\nfunc (w *WebAuthn) writeError(r *http.Request, rw http.ResponseWriter, err error) {\n\tif v, ok := errors.Cause(err).(*protocol.Error); ok {\n\t\tw.writeErrorCode(r, rw, v.Code, err)\n\t\treturn\n\t}\n\n\tw.writeErrorCode(r, rw, http.StatusInternalServerError, err)\n}\n\nfunc (w *WebAuthn) writeErrorCode(r *http.Request, rw http.ResponseWriter, code int, err error) {\n\te := protocol.ToWebAuthnError(err)\n\n\tif code == 0 {\n\t\tcode = http.StatusInternalServerError\n\t}\n\n\tif !w.Config.Debug {\n\t\te.Debug = \"\"\n\t}\n\n\tjs, err := json.Marshal(e)\n\tif err != nil {\n\t\tw.writeError(r, rw, err)\n\t\treturn\n\t}\n\n\trw.Header().Set(\"Content-Type\", \"application/json\")\n\trw.WriteHeader(code)\n\trw.Write(js)\n}\n"
  },
  {
    "path": "webauthn.js",
    "content": "class WebAuthn {\r\n\t// Decode a base64 string into a Uint8Array.\r\n\tstatic _decodeBuffer(value) {\r\n\t\treturn Uint8Array.from(atob(value), c => c.charCodeAt(0));\r\n\t}\r\n\r\n\t// Encode an ArrayBuffer into a base64 string.\r\n\tstatic _encodeBuffer(value) {\r\n\t\treturn btoa(new Uint8Array(value).reduce((s, byte) => s + String.fromCharCode(byte), ''));\r\n\t}\r\n\r\n\t// Checks whether the status returned matches the status given.\r\n\tstatic _checkStatus(status) {\r\n\t\treturn res => {\r\n\t\t\tif (res.status === status) {\r\n\t\t\t\treturn res;\r\n\t\t\t}\r\n\t\t\tthrow new Error(res.statusText);\r\n\t\t};\r\n\t}\r\n\r\n\tregister() {\r\n\t\treturn fetch('/webauthn/registration/start', {\r\n\t\t\t\tmethod: 'POST'\r\n\t\t\t})\r\n\t\t\t.then(WebAuthn._checkStatus(200))\r\n\t\t\t.then(res => res.json())\r\n\t\t\t.then(res => {\r\n\t\t\t\tres.publicKey.challenge = WebAuthn._decodeBuffer(res.publicKey.challenge);\r\n\t\t\t\tres.publicKey.user.id = WebAuthn._decodeBuffer(res.publicKey.user.id);\r\n\t\t\t\tif (res.publicKey.excludeCredentials) {\r\n\t\t\t\t\tfor (var i = 0; i < res.publicKey.excludeCredentials.length; i++) {\r\n\t\t\t\t\t\tres.publicKey.excludeCredentials[i].id = WebAuthn._decodeBuffer(res.publicKey.excludeCredentials[i].id);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn res;\r\n\t\t\t})\r\n\t\t\t.then(res => navigator.credentials.create(res))\r\n\t\t\t.then(credential => {\r\n\t\t\t\treturn fetch('/webauthn/registration/finish', {\r\n\t\t\t\t\tmethod: 'POST',\r\n\t\t\t\t\theaders: {\r\n\t\t\t\t\t\t'Accept': 'application/json',\r\n\t\t\t\t\t\t'Content-Type': 'application/json'\r\n\t\t\t\t\t},\r\n\t\t\t\t\tbody: JSON.stringify({\r\n\t\t\t\t\t\tid: credential.id,\r\n\t\t\t\t\t\trawId: WebAuthn._encodeBuffer(credential.rawId),\r\n\t\t\t\t\t\tresponse: {\r\n\t\t\t\t\t\t\tattestationObject: WebAuthn._encodeBuffer(credential.response.attestationObject),\r\n\t\t\t\t\t\t\tclientDataJSON: WebAuthn._encodeBuffer(credential.response.clientDataJSON)\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\ttype: credential.type\r\n\t\t\t\t\t}),\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\t.then(WebAuthn._checkStatus(201));\r\n\t}\r\n\r\n\tlogin() {\r\n\t\treturn fetch('/webauthn/login/start', {\r\n\t\t\t\tmethod: 'POST'\r\n\t\t\t})\r\n\t\t\t.then(WebAuthn._checkStatus(200))\r\n\t\t\t.then(res => res.json())\r\n\t\t\t.then(res => {\r\n\t\t\t\tres.publicKey.challenge = WebAuthn._decodeBuffer(res.publicKey.challenge);\r\n\t\t\t\tif (res.publicKey.allowCredentials) {\r\n\t\t\t\t\tfor (let i = 0; i < res.publicKey.allowCredentials.length; i++) {\r\n\t\t\t\t\t\tres.publicKey.allowCredentials[i].id = WebAuthn._decodeBuffer(res.publicKey.allowCredentials[i].id);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn res;\r\n\t\t\t})\r\n\t\t\t.then(res => navigator.credentials.get(res))\r\n\t\t\t.then(credential => {\r\n\t\t\t\treturn fetch('/webauthn/login/finish', {\r\n\t\t\t\t\tmethod: 'POST',\r\n\t\t\t\t\theaders: {\r\n\t\t\t\t\t\t'Accept': 'application/json',\r\n\t\t\t\t\t\t'Content-Type': 'application/json'\r\n\t\t\t\t\t},\r\n\t\t\t\t\tbody: JSON.stringify({\r\n\t\t\t\t\t\tid: credential.id,\r\n\t\t\t\t\t\trawId: WebAuthn._encodeBuffer(credential.rawId),\r\n\t\t\t\t\t\tresponse: {\r\n\t\t\t\t\t\t\tclientDataJSON: WebAuthn._encodeBuffer(credential.response.clientDataJSON),\r\n\t\t\t\t\t\t\tauthenticatorData: WebAuthn._encodeBuffer(credential.response.authenticatorData),\r\n\t\t\t\t\t\t\tsignature: WebAuthn._encodeBuffer(credential.response.signature),\r\n\t\t\t\t\t\t\tuserHandle: WebAuthn._encodeBuffer(credential.response.userHandle),\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\ttype: credential.type\r\n\t\t\t\t\t}),\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\t.then(WebAuthn._checkStatus(200));\r\n\t}\r\n}"
  }
]