Skip to content

Commit

Permalink
♻️ clear encode data
Browse files Browse the repository at this point in the history
  • Loading branch information
qd-qd committed Mar 24, 2024
1 parent cbecf79 commit 2a7f661
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 71 deletions.
162 changes: 134 additions & 28 deletions cmd/webauthn-mock/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import (
"encoding/json"
"flag"
"fmt"
"log"
"strings"
"time"

"github.com/descope/virtualwebauthn"
"github.com/fxamacker/cbor/v2"
"github.com/fxamacker/webauthn"
_ "github.com/fxamacker/webauthn/packed"
)
Expand All @@ -21,7 +23,7 @@ const (
DefaultUserName = "qdqd"
)

var webauthnConfig = &webauthn.Config{
var webauthnConfig = webauthn.Config{
RPID: WebauthnDomain,
RPName: WebauthnDisplayName,
Timeout: uint64(60000),
Expand All @@ -32,30 +34,71 @@ var webauthnConfig = &webauthn.Config{
AuthenticatorAttachment: webauthn.AuthenticatorPlatform,
}

type ExtendedAttestationOptions struct {
*virtualwebauthn.AttestationOptions // Embedding AttestationOptions struct
Challenge string `json:"challenge,omitempty"` // Override Challenge field
}

type User struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
DisplayName string `json:"displayName,omitempty"`
}

type WebAuthnConfigJSON string

type WebauthnAttestation struct {
User *webauthn.User
Challenge []byte
Options string
}

type FullClientData struct {
Type string `json:"type"`
Challenge string `json:"challenge"`
Origin string `json:"origin"`
}
type WebauthnResponse struct {
Id string `json:"id,omitempty"`
RawId string `json:"rawId,omitempty"`
Response struct {
AttestationObject string `json:"attestationObject,omitempty"`
ClientDataJSON string `json:"clientDataJSON,omitempty"`
}
}

type attestationStatement struct {
Algorithm int `json:"alg"`
Signature []byte `json:"sig"`
}
type attestationStatementClean struct {
Algorithm int `json:"alg"`
Signature string `json:"sig"`
}
type attestationObject struct {
Format string `json:"fmt"`
Statement attestationStatement `json:"attStmt"`
AuthData []byte `json:"authData"`
}

type FullAttestationObject struct {
Raw64 string `json:"raw64,omitempty"`
Format string `json:"fmt"`
Statement attestationStatementClean `json:"attStmt"`
AuthData string `json:"authData"`
}

type WebauthnResponseComplete struct {
Id string `json:"id,omitempty"`
RawId string `json:"rawId,omitempty"`
AttestationObject FullAttestationObject
ClientDataJSON FullClientData
}

type WebAuthnResponseRaw struct {
AttestationObject string `json:"AttestationObject,omitempty"`
ClientDataJSON string `json:"clientDataJSON,omitempty"`
}

type WebAuthnRegister struct {
WebauthnUser User `json:"user,omitempty"`
WebauthnConfig string `json:"config,omitempty"`
WebauthnOptions ExtendedAttestationOptions `json:"options,omitempty"`
WebauthnResponse string `json:"response,omitempty"`
WebauthnUser User `json:"user,omitempty"`
WebauthnConfig webauthn.Config `json:"config,omitempty"`
WebauthnOptions *virtualwebauthn.AttestationOptions `json:"options,omitempty"`
WebauthnResponseComplete WebauthnResponseComplete `json:"responseDecoded,omitempty"`
WebAuthnResponseRaw WebAuthnResponseRaw `json:"response,omitempty"`
}

func register(challenge string, username string) (*virtualwebauthn.AttestationOptions, string) {
Expand Down Expand Up @@ -115,7 +158,7 @@ func startWebauthnRegister(challenge string, username string) *WebauthnAttestati
}

// Generate the attestation options
options, _ := webauthn.NewAttestationOptions(webauthnConfig, user)
options, _ := webauthn.NewAttestationOptions(&webauthnConfig, user)

// If a challenge flag was provided, set it in the options
if len(challenge) > 0 {
Expand Down Expand Up @@ -170,19 +213,70 @@ func newWebauthnUser(username string) *webauthn.User {

}

// marshals a struct to JSON either in pretty or compact format
func MarshalJSON(value any, pretty string) []byte {
// If the pretty flag is set, pretty print the JSON
if len(pretty) > 0 {
valueJSON, err := json.MarshalIndent(value, "", " ")
if err != nil {
panic(fmt.Sprintf("Error marshalling attestation options: %v", err))
}
return valueJSON
}

// Otherwise, compact print the JSON
valueJSON, err := json.Marshal(value)
if err != nil {
panic(fmt.Sprintf("Error marshalling attestation options: %v", err))
}
return valueJSON
}

func main() {
// Parse command line arguments
challenge := flag.String("challenge", "", "an optional argument")
username := flag.String("username", "", "an optional argument")
challenge := flag.String("challenge", "", "An optional argument to set a specific challenge")
username := flag.String("username", "", "An optional argument to set a specific username")
pretty := flag.String("pretty", "", "An optional argument to pretty print the JSON output")
flag.Parse()

// Run a webauthn attestation flow
attestationOptions, attestationResponse := register(*challenge, *username)

// Marshal the webauthn config to JSON for sending to the client
webauthnConfigJSON, err := json.MarshalIndent(webauthnConfig, "", " ")
// Unmarshal the webauthn response to get the attestation object and clientDataJSON
var WebauthnResponse WebauthnResponse
json.Unmarshal([]byte(attestationResponse), &WebauthnResponse)

// Decode the clientDataJSON from Base64
decodedClientDataBytes, err := base64.RawURLEncoding.DecodeString(WebauthnResponse.Response.ClientDataJSON)
if err != nil {
panic(fmt.Sprintf("Error marshalling attestation options: %v", err))
panic(err)
}
// Now unmarshal the JSON bytes into the struct
var clientData FullClientData
err = json.Unmarshal(decodedClientDataBytes, &clientData)
if err != nil {
panic(err)
}
// override the challenge to be a hex string
clientData.Challenge = encodeToHex([]byte(clientData.Challenge))

// Decode the attestationObject from Base64
decodedAttestationObjectBytes, err := base64.RawURLEncoding.DecodeString(WebauthnResponse.Response.AttestationObject)
if err != nil {
panic(err)
}

// The data structure to decode into
var result attestationObject
cbor.Unmarshal(decodedAttestationObjectBytes, &result)
if err != nil {
panic(err)
}

// Decode the WebauthnResponse.Id from Base64
WebauthnResponseIdByte, err := base64.RawURLEncoding.DecodeString(WebauthnResponse.Id)
if err != nil {
log.Fatalf("error decoding base64 string: %v", err)
}

// Create the WebAuthnRegister struct to hold all the data
Expand All @@ -192,17 +286,29 @@ func main() {
Name: attestationOptions.UserName,
DisplayName: attestationOptions.UserDisplayName,
},
string(webauthnConfigJSON),
ExtendedAttestationOptions{
AttestationOptions: attestationOptions,
Challenge: encodeChallenge(attestationOptions.Challenge),
webauthnConfig,
attestationOptions,
WebauthnResponseComplete{
Id: encodeToHex(WebauthnResponseIdByte),
RawId: WebauthnResponse.RawId,
AttestationObject: FullAttestationObject{
Format: result.Format,
Statement: attestationStatementClean{
Algorithm: result.Statement.Algorithm,
Signature: encodeToHex(result.Statement.Signature),
},
AuthData: encodeToHex(result.AuthData),
},
ClientDataJSON: clientData,
},
WebAuthnResponseRaw{
AttestationObject: encodeToHex(decodedAttestationObjectBytes),
ClientDataJSON: encodeToHex(decodedClientDataBytes),
},
attestationResponse,
}
webAuthnRegisterDataJSON, err := json.MarshalIndent(webauthnRegister, "", " ")
if err != nil {
panic(fmt.Sprintf("Error marshalling attestation options: %v", err))
}

fmt.Println(string(webAuthnRegisterDataJSON))
// Output the data in JSON format
webAuthnRegisterDataJSON := MarshalJSON(webauthnRegister, *pretty)
fmt.Print(string(webAuthnRegisterDataJSON))

}
46 changes: 3 additions & 43 deletions cmd/webauthn-mock/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,12 @@ package main

import (
"encoding/base64"
"encoding/json"
"encoding/hex"
"fmt"

"github.com/google/uuid"
)

func decodeChallenge(challenge string) []byte {
decoded, err := base64.RawURLEncoding.DecodeString(challenge)
if err != nil {
panic(fmt.Sprintf("Error decoding Base64 URL string: %v", err))
}
return decoded
}

func encodeChallenge(challenge []byte) string {
return base64.RawURLEncoding.EncodeToString(challenge)
}

func generateUserID() string {
// Generate a UUID
uuidObj, err := uuid.NewRandom()
Expand All @@ -34,34 +22,6 @@ func generateUserID() string {
return encodedUUID
}

// Custom JSON marshaling to handle specific fields
func (w WebAuthnRegister) MarshalJSON() ([]byte, error) {
// Temp struct to avoid recursion
type Alias WebAuthnRegister

// Unmarshal the config and response strings back to raw JSON for proper formatting
var configRaw json.RawMessage
if w.WebauthnConfig != "" {
if err := json.Unmarshal([]byte(w.WebauthnConfig), &configRaw); err != nil {
return nil, err
}
}

var responseRaw json.RawMessage
if w.WebauthnResponse != "" {
if err := json.Unmarshal([]byte(w.WebauthnResponse), &responseRaw); err != nil {
return nil, err
}
}

// Use the Alias type to marshal all but the special fields, then add those fields manually
return json.Marshal(&struct {
Config json.RawMessage `json:"config,omitempty"`
Response json.RawMessage `json:"response,omitempty"`
*Alias
}{
Config: configRaw,
Response: responseRaw,
Alias: (*Alias)(&w),
})
func encodeToHex(data []byte) string {
return "0x" + hex.EncodeToString(data)
}

0 comments on commit 2a7f661

Please sign in to comment.