package webauthn import ( "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" ) // PublicKeyCredentialRequestOptions represents the WebAuthn authentication challenge type PublicKeyCredentialRequestOptions struct { Challenge string `json:"challenge"` Timeout int `json:"timeout"` RPID string `json:"rpId"` AllowCredentials []string `json:"allowCredentials,omitempty"` UserVerification string `json:"userVerification"` } // PublicKeyCredentialCreationOptions represents the WebAuthn registration challenge type PublicKeyCredentialCreationOptions struct { Challenge string `json:"challenge"` RP RelyingParty `json:"rp"` User User `json:"user"` PubKeyCredParams []PubKeyCredParam `json:"pubKeyCredParams"` Timeout int `json:"timeout"` Attestation string `json:"attestation"` AuthenticatorSelection AuthenticatorSelection `json:"authenticatorSelection"` } type RelyingParty struct { Name string `json:"name"` ID string `json:"id"` } type User struct { ID string `json:"id"` Name string `json:"name"` DisplayName string `json:"displayName"` } type PubKeyCredParam struct { Type string `json:"type"` Alg int `json:"alg"` } type AuthenticatorSelection struct { AuthenticatorAttachment string `json:"authenticatorAttachment,omitempty"` RequireResidentKey bool `json:"requireResidentKey"` UserVerification string `json:"userVerification"` } // GenerateChallenge creates a cryptographically secure random challenge func GenerateChallenge() (string, error) { challenge := make([]byte, 32) _, err := rand.Read(challenge) if err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(challenge), nil } // CreateRegistrationOptions generates WebAuthn registration options func CreateRegistrationOptions(userID, username, displayName, rpName, rpID string) (*PublicKeyCredentialCreationOptions, error) { challenge, err := GenerateChallenge() if err != nil { return nil, err } return &PublicKeyCredentialCreationOptions{ Challenge: challenge, RP: RelyingParty{ Name: rpName, ID: rpID, }, User: User{ ID: userID, Name: username, DisplayName: displayName, }, PubKeyCredParams: []PubKeyCredParam{ {Type: "public-key", Alg: -7}, // ES256 {Type: "public-key", Alg: -257}, // RS256 }, Timeout: 60000, Attestation: "none", AuthenticatorSelection: AuthenticatorSelection{ RequireResidentKey: false, UserVerification: "preferred", }, }, nil } // CreateAuthenticationOptions generates WebAuthn authentication options func CreateAuthenticationOptions(rpID string, allowedCredentials []string) (*PublicKeyCredentialRequestOptions, error) { challenge, err := GenerateChallenge() if err != nil { return nil, err } return &PublicKeyCredentialRequestOptions{ Challenge: challenge, Timeout: 60000, RPID: rpID, AllowCredentials: allowedCredentials, UserVerification: "preferred", }, nil } // VerifyClientData validates the WebAuthn client data JSON func VerifyClientData(clientDataJSON []byte, expectedChallenge, expectedOrigin string) error { var clientData struct { Type string `json:"type"` Challenge string `json:"challenge"` Origin string `json:"origin"` } if err := json.Unmarshal(clientDataJSON, &clientData); err != nil { return fmt.Errorf("invalid client data JSON: %w", err) } if clientData.Type != "webauthn.get" && clientData.Type != "webauthn.create" { return fmt.Errorf("invalid client data type: %s", clientData.Type) } if clientData.Challenge != expectedChallenge { return fmt.Errorf("challenge mismatch") } if clientData.Origin != expectedOrigin { return fmt.Errorf("origin mismatch: expected %s, got %s", expectedOrigin, clientData.Origin) } return nil } // HashClientData creates SHA-256 hash of client data (required for signature verification) func HashClientData(clientDataJSON []byte) []byte { hash := sha256.Sum256(clientDataJSON) return hash[:] } // WrapMasterKey encrypts the master key with the admin's public key // In a production system, this would use the WebAuthn credential's public key func WrapMasterKey(masterKey []byte, publicKey []byte) ([]byte, error) { // Simplified implementation - in production, use proper public key encryption // For now, we'll use a symmetric approach with HKDF return masterKey, nil // TODO: Implement proper key wrapping } // UnwrapMasterKey decrypts the master key using the admin's credential func UnwrapMasterKey(wrappedKey []byte, privateKey []byte) ([]byte, error) { // Simplified implementation - in production, use proper public key decryption return wrappedKey, nil // TODO: Implement proper key unwrapping }