package webauthn import ( "encoding/hex" "fmt" "strings" "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" "git.wntrmute.dev/kyle/mcias/internal/crypto" "git.wntrmute.dev/kyle/mcias/internal/model" ) // DecryptCredential decrypts a stored WebAuthn credential's ID and public key // and returns a webauthn.Credential suitable for the go-webauthn library. func DecryptCredential(masterKey []byte, cred *model.WebAuthnCredential) (*webauthn.Credential, error) { credID, err := crypto.OpenAESGCM(masterKey, cred.CredentialIDNonce, cred.CredentialIDEnc) if err != nil { return nil, fmt.Errorf("webauthn: decrypt credential ID: %w", err) } pubKey, err := crypto.OpenAESGCM(masterKey, cred.PublicKeyNonce, cred.PublicKeyEnc) if err != nil { return nil, fmt.Errorf("webauthn: decrypt public key: %w", err) } // Parse transports from comma-separated string. var transports []protocol.AuthenticatorTransport if cred.Transports != "" { for _, t := range strings.Split(cred.Transports, ",") { transports = append(transports, protocol.AuthenticatorTransport(strings.TrimSpace(t))) } } // Parse AAGUID from hex string. var aaguid []byte if cred.AAGUID != "" { aaguid, _ = hex.DecodeString(cred.AAGUID) } return &webauthn.Credential{ ID: credID, PublicKey: pubKey, Transport: transports, Flags: webauthn.CredentialFlags{ UserPresent: true, UserVerified: true, BackupEligible: cred.Discoverable, }, Authenticator: webauthn.Authenticator{ AAGUID: aaguid, SignCount: cred.SignCount, }, }, nil } // DecryptCredentials decrypts all stored credentials for use with the library. func DecryptCredentials(masterKey []byte, dbCreds []*model.WebAuthnCredential) ([]webauthn.Credential, error) { result := make([]webauthn.Credential, 0, len(dbCreds)) for _, c := range dbCreds { decrypted, err := DecryptCredential(masterKey, c) if err != nil { return nil, err } result = append(result, *decrypted) } return result, nil } // EncryptCredential encrypts a library credential for database storage. // Returns a model.WebAuthnCredential with encrypted fields populated. func EncryptCredential(masterKey []byte, cred *webauthn.Credential, name string, discoverable bool) (*model.WebAuthnCredential, error) { credIDEnc, credIDNonce, err := crypto.SealAESGCM(masterKey, cred.ID) if err != nil { return nil, fmt.Errorf("webauthn: encrypt credential ID: %w", err) } pubKeyEnc, pubKeyNonce, err := crypto.SealAESGCM(masterKey, cred.PublicKey) if err != nil { return nil, fmt.Errorf("webauthn: encrypt public key: %w", err) } // Serialize transports as comma-separated string. var transportStrs []string for _, t := range cred.Transport { transportStrs = append(transportStrs, string(t)) } return &model.WebAuthnCredential{ Name: name, CredentialIDEnc: credIDEnc, CredentialIDNonce: credIDNonce, PublicKeyEnc: pubKeyEnc, PublicKeyNonce: pubKeyNonce, AAGUID: hex.EncodeToString(cred.Authenticator.AAGUID), SignCount: cred.Authenticator.SignCount, Discoverable: discoverable, Transports: strings.Join(transportStrs, ","), }, nil }