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>
149 lines
3.7 KiB
Go
149 lines
3.7 KiB
Go
package webauthn
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/go-webauthn/webauthn/protocol"
|
|
libwebauthn "github.com/go-webauthn/webauthn/webauthn"
|
|
|
|
"git.wntrmute.dev/mc/mcias/internal/crypto"
|
|
"git.wntrmute.dev/mc/mcias/internal/model"
|
|
)
|
|
|
|
func testMasterKey(t *testing.T) []byte {
|
|
t.Helper()
|
|
key := make([]byte, 32)
|
|
for i := range key {
|
|
key[i] = byte(i)
|
|
}
|
|
return key
|
|
}
|
|
|
|
func TestEncryptDecryptRoundTrip(t *testing.T) {
|
|
masterKey := testMasterKey(t)
|
|
|
|
original := &libwebauthn.Credential{
|
|
ID: []byte("credential-id-12345"),
|
|
PublicKey: []byte("public-key-bytes-here"),
|
|
Transport: []protocol.AuthenticatorTransport{
|
|
protocol.USB,
|
|
protocol.NFC,
|
|
},
|
|
Flags: libwebauthn.CredentialFlags{
|
|
UserPresent: true,
|
|
UserVerified: true,
|
|
BackupEligible: true,
|
|
},
|
|
Authenticator: libwebauthn.Authenticator{
|
|
AAGUID: []byte{0x2f, 0xc0, 0x57, 0x9f, 0x81, 0x13, 0x47, 0xea, 0xb1, 0x16, 0xbb, 0x5a, 0x8d, 0xb9, 0x20, 0x2a},
|
|
SignCount: 42,
|
|
},
|
|
}
|
|
|
|
// Encrypt.
|
|
encrypted, err := EncryptCredential(masterKey, original, "YubiKey 5", true)
|
|
if err != nil {
|
|
t.Fatalf("encrypt: %v", err)
|
|
}
|
|
if encrypted.Name != "YubiKey 5" {
|
|
t.Errorf("Name = %q, want %q", encrypted.Name, "YubiKey 5")
|
|
}
|
|
if !encrypted.Discoverable {
|
|
t.Error("expected discoverable=true")
|
|
}
|
|
if encrypted.SignCount != 42 {
|
|
t.Errorf("SignCount = %d, want 42", encrypted.SignCount)
|
|
}
|
|
if encrypted.Transports != "usb,nfc" {
|
|
t.Errorf("Transports = %q, want %q", encrypted.Transports, "usb,nfc")
|
|
}
|
|
|
|
// Encrypted fields should not be plaintext.
|
|
if bytes.Equal(encrypted.CredentialIDEnc, original.ID) {
|
|
t.Error("credential ID should be encrypted")
|
|
}
|
|
if bytes.Equal(encrypted.PublicKeyEnc, original.PublicKey) {
|
|
t.Error("public key should be encrypted")
|
|
}
|
|
|
|
// Decrypt.
|
|
decrypted, err := DecryptCredential(masterKey, encrypted)
|
|
if err != nil {
|
|
t.Fatalf("decrypt: %v", err)
|
|
}
|
|
if !bytes.Equal(decrypted.ID, original.ID) {
|
|
t.Errorf("credential ID mismatch after roundtrip")
|
|
}
|
|
if !bytes.Equal(decrypted.PublicKey, original.PublicKey) {
|
|
t.Errorf("public key mismatch after roundtrip")
|
|
}
|
|
if decrypted.Authenticator.SignCount != 42 {
|
|
t.Errorf("SignCount = %d, want 42", decrypted.Authenticator.SignCount)
|
|
}
|
|
if len(decrypted.Transport) != 2 {
|
|
t.Errorf("expected 2 transports, got %d", len(decrypted.Transport))
|
|
}
|
|
}
|
|
|
|
func TestDecryptCredentials(t *testing.T) {
|
|
masterKey := testMasterKey(t)
|
|
|
|
// Create two encrypted credentials.
|
|
var dbCreds []*model.WebAuthnCredential
|
|
for i := range 3 {
|
|
cred := &libwebauthn.Credential{
|
|
ID: []byte{byte(i), 1, 2, 3},
|
|
PublicKey: []byte{byte(i), 4, 5, 6},
|
|
Authenticator: libwebauthn.Authenticator{
|
|
SignCount: uint32(i),
|
|
},
|
|
}
|
|
enc, err := EncryptCredential(masterKey, cred, "key", false)
|
|
if err != nil {
|
|
t.Fatalf("encrypt %d: %v", i, err)
|
|
}
|
|
dbCreds = append(dbCreds, enc)
|
|
}
|
|
|
|
decrypted, err := DecryptCredentials(masterKey, dbCreds)
|
|
if err != nil {
|
|
t.Fatalf("decrypt all: %v", err)
|
|
}
|
|
if len(decrypted) != 3 {
|
|
t.Fatalf("expected 3 decrypted, got %d", len(decrypted))
|
|
}
|
|
for i, d := range decrypted {
|
|
if d.ID[0] != byte(i) {
|
|
t.Errorf("cred %d: ID[0] = %d, want %d", i, d.ID[0], byte(i))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecryptWithWrongKey(t *testing.T) {
|
|
masterKey := testMasterKey(t)
|
|
wrongKey := make([]byte, 32)
|
|
for i := range wrongKey {
|
|
wrongKey[i] = byte(i + 100)
|
|
}
|
|
|
|
// Encrypt with correct key.
|
|
enc, nonce, err := crypto.SealAESGCM(masterKey, []byte("secret"))
|
|
if err != nil {
|
|
t.Fatalf("seal: %v", err)
|
|
}
|
|
|
|
dbCred := &model.WebAuthnCredential{
|
|
CredentialIDEnc: enc,
|
|
CredentialIDNonce: nonce,
|
|
PublicKeyEnc: enc,
|
|
PublicKeyNonce: nonce,
|
|
}
|
|
|
|
// Decrypt with wrong key should fail.
|
|
_, err = DecryptCredential(wrongKey, dbCred)
|
|
if err == nil {
|
|
t.Error("expected error decrypting with wrong key")
|
|
}
|
|
}
|