Add configuration files, database migrations, and authentication implementation scaffolding
This commit is contained in:
155
internal/webauthn/webauthn.go
Normal file
155
internal/webauthn/webauthn.go
Normal file
@@ -0,0 +1,155 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user