package webauthn import ( "bytes" "testing" "github.com/go-webauthn/webauthn/protocol" libwebauthn "github.com/go-webauthn/webauthn/webauthn" "git.wntrmute.dev/kyle/mcias/internal/crypto" "git.wntrmute.dev/kyle/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") } }