All import paths updated from git.wntrmute.dev/kyle/mcias to git.wntrmute.dev/mc/mcias to match the Gitea organization. Includes main module and clients/go submodule. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
100 lines
3.1 KiB
Go
100 lines
3.1 KiB
Go
package webauthn
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/go-webauthn/webauthn/protocol"
|
|
"github.com/go-webauthn/webauthn/webauthn"
|
|
|
|
"git.wntrmute.dev/mc/mcias/internal/crypto"
|
|
"git.wntrmute.dev/mc/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
|
|
}
|