Fix tests. Remove tests which test rails functionality
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled

This commit is contained in:
Dan Milne
2025-12-30 00:18:19 +11:00
parent 2a32d75895
commit 0761c424c1
10 changed files with 986 additions and 167 deletions

View File

@@ -0,0 +1,344 @@
require "test_helper"
require "webauthn/fake_client"
class WebauthnSecurityTest < ActionDispatch::SystemTest
# ====================
# REPLAY ATTACK PREVENTION (SIGN COUNT TRACKING) TESTS
# ====================
test "detects suspicious sign count for replay attacks" do
user = User.create!(email_address: "webauthn_replay_test@example.com", password: "password123")
# Create a WebAuthn credential
credential = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("fake_credential_id"),
public_key: Base64.urlsafe_encode64("fake_public_key"),
sign_count: 0,
nickname: "Test Key"
)
# Simulate a suspicious sign count (decreased or reused)
credential.update!(sign_count: 100)
# Try to authenticate with a lower sign count (potential replay)
suspicious = credential.suspicious_sign_count?(99)
assert suspicious, "Should detect suspicious sign count indicating potential replay attack"
user.destroy
end
test "sign count is incremented after successful authentication" do
user = User.create!(email_address: "webauthn_signcount_test@example.com", password: "password123")
credential = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("fake_credential_id"),
public_key: Base64.urlsafe_encode64("fake_public_key"),
sign_count: 50,
nickname: "Test Key"
)
# Simulate authentication with new sign count
credential.update_usage!(
sign_count: 51,
ip_address: "192.168.1.1",
user_agent: "Mozilla/5.0"
)
credential.reload
assert_equal 51, credential.sign_count, "Sign count should be incremented"
user.destroy
end
# ====================
# USER HANDLE BINDING TESTS
# ====================
test "user handle is properly bound to WebAuthn credential" do
user = User.create!(email_address: "webauthn_handle_test@example.com", password: "password123")
# Create a WebAuthn credential with user handle
user_handle = SecureRandom.uuid
credential = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("fake_credential_id"),
public_key: Base64.urlsafe_encode64("fake_public_key"),
sign_count: 0,
nickname: "Test Key",
user_handle: user_handle
)
# Verify user handle is associated with the credential
assert_equal user_handle, credential.user_handle
user.destroy
end
test "WebAuthn authentication validates user handle" do
user = User.create!(email_address: "webauthn_handle_auth_test@example.com", password: "password123")
user_handle = SecureRandom.uuid
credential = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("fake_credential_id"),
public_key: Base64.urlsafe_encode64("fake_public_key"),
sign_count: 0,
nickname: "Test Key",
user_handle: user_handle
)
# Sign in with WebAuthn
# The implementation should verify the user handle matches
# This test documents the expected behavior
user.destroy
end
# ====================
# ORIGIN VALIDATION TESTS
# ====================
test "WebAuthn request validates origin" do
user = User.create!(email_address: "webauthn_origin_test@example.com", password: "password123")
credential = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("fake_credential_id"),
public_key: Base64.urlsafe_encode64("fake_public_key"),
sign_count: 0,
nickname: "Test Key"
)
# Test WebAuthn challenge from valid origin
post webauthn_challenge_path, params: { email: "webauthn_origin_test@example.com" },
headers: { "HTTP_ORIGIN": "http://localhost:3000" }
# Should succeed for valid origin
# Test WebAuthn challenge from invalid origin
post webauthn_challenge_path, params: { email: "webauthn_origin_test@example.com" },
headers: { "HTTP_ORIGIN": "http://evil.com" }
# Should reject invalid origin
user.destroy
end
test "WebAuthn verification includes origin validation" do
user = User.create!(email_address: "webauthn_verify_origin_test@example.com", password: "password123")
user.update!(webauthn_id: SecureRandom.uuid)
credential = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("fake_credential_id"),
public_key: Base64.urlsafe_encode64("fake_public_key"),
sign_count: 0,
nickname: "Test Key"
)
# Sign in with WebAuthn
post webauthn_challenge_path, params: { email: "webauthn_verify_origin_test@example.com" }
assert_response :success
challenge = JSON.parse(@response.body)["challenge"]
# Simulate WebAuthn verification with wrong origin
# This should fail
user.destroy
end
# ====================
# ATTESTATION FORMAT VALIDATION TESTS
# ====================
test "WebAuthn accepts standard attestation formats" do
user = User.create!(email_address: "webauthn_attestation_test@example.com", password: "password123")
# Register WebAuthn credential
# Standard attestation formats: none, packed, tpm, android-key, android-safetynet, fido-u2f, etc.
# Test with 'none' attestation (most common for privacy)
attestation_object = {
fmt: "none",
attStmt: {},
authData: Base64.strict_encode64("fake_auth_data")
}
# The implementation should accept standard attestation formats
user.destroy
end
test "WebAuthn rejects invalid attestation formats" do
user = User.create!(email_address: "webauthn_invalid_attestation_test@example.com", password: "password123")
# Try to register with invalid attestation format
invalid_attestation = {
fmt: "invalid_format",
attStmt: {},
authData: Base64.strict_encode64("fake_auth_data")
}
# Should reject invalid attestation format
user.destroy
end
# ====================
# CREDENTIAL CLONING DETECTION TESTS
# ====================
test "detects credential cloning through sign count anomalies" do
user = User.create!(email_address: "webauthn_clone_test@example.com", password: "password123")
credential = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("fake_credential_id"),
public_key: Base64.urlsafe_encode64("fake_public_key"),
sign_count: 100,
nickname: "Test Key"
)
# Simulate authentication from a cloned credential (sign count doesn't increase properly)
# First auth: sign count = 101
credential.update_usage!(sign_count: 101, ip_address: "192.168.1.1", user_agent: "Browser A")
# Second auth from different location but sign count = 101 again (cloned!)
suspicious = credential.suspicious_sign_count?(101)
assert suspicious, "Should detect potential credential cloning"
# Verify logging for security monitoring
# The application should log suspicious sign count anomalies
user.destroy
end
test "tracks IP address and user agent for WebAuthn authentications" do
user = User.create!(email_address: "webauthn_tracking_test@example.com", password: "password123")
credential = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("fake_credential_id"),
public_key: Base64.urlsafe_encode64("fake_public_key"),
sign_count: 0,
nickname: "Test Key"
)
# Update usage with tracking information
credential.update_usage!(
sign_count: 1,
ip_address: "192.168.1.100",
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
)
credential.reload
assert_equal "192.168.1.100", credential.last_ip_address
assert_equal "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", credential.last_user_agent
user.destroy
end
# ====================
# CREDENTIAL EXCLUSION TESTS
# ====================
test "prevents duplicate credential registration" do
user = User.create!(email_address: "webauthn_duplicate_test@example.com", password: "password123")
credential_id = Base64.urlsafe_encode64("unique_credential_id")
# Register first credential
user.webauthn_credentials.create!(
external_id: credential_id,
public_key: Base64.urlsafe_encode64("public_key_1"),
sign_count: 0,
nickname: "Key 1"
)
# Try to register same credential ID again
# Should reject or update existing credential
user.destroy
end
# ====================
# USER PRESENCE TESTS
# ====================
test "WebAuthn requires user presence for authentication" do
user = User.create!(email_address: "webauthn_presence_test@example.com", password: "password123")
credential = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("fake_credential_id"),
public_key: Base64.urlsafe_encode64("fake_public_key"),
sign_count: 0,
nickname: "Test Key"
)
# WebAuthn authenticator response should include user presence flag (UP)
# The implementation should verify this flag is set to true
user.destroy
end
# ====================
# CREDENTIAL MANAGEMENT TESTS
# ====================
test "users can view and revoke their WebAuthn credentials" do
user = User.create!(email_address: "webauthn_mgmt_test@example.com", password: "password123")
# Create multiple credentials
credential1 = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("credential_1"),
public_key: Base64.urlsafe_encode64("public_key_1"),
sign_count: 0,
nickname: "USB Key"
)
credential2 = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("credential_2"),
public_key: Base64.urlsafe_encode64("public_key_2"),
sign_count: 0,
nickname: "Laptop Key"
)
# User should be able to view their credentials
assert_equal 2, user.webauthn_credentials.count
# User should be able to revoke a credential
credential1.destroy
assert_equal 1, user.webauthn_credentials.count
user.destroy
end
# ====================
# WEBAUTHN AND PASSWORD LOGIN INTERACTION TESTS
# ====================
test "WebAuthn can be required for authentication" do
user = User.create!(email_address: "webauthn_required_test@example.com", password: "password123")
user.update!(webauthn_enabled: true)
# Sign in with password should still work
post signin_path, params: { email_address: "webauthn_required_test@example.com", password: "password123" }
# If WebAuthn is enabled, should offer WebAuthn as an option
# Implementation should handle password + WebAuthn or passwordless flow
user.destroy
end
test "WebAuthn can be used for passwordless authentication" do
user = User.create!(email_address: "webauthn_passwordless_test@example.com", password: "password123")
user.update!(webauthn_enabled: true)
credential = user.webauthn_credentials.create!(
external_id: Base64.urlsafe_encode64("passwordless_credential"),
public_key: Base64.urlsafe_encode64("public_key"),
sign_count: 0,
nickname: "Passwordless Key"
)
# User should be able to sign in with WebAuthn alone
# Test passwordless flow
user.destroy
end
end