towerops/test/snmpkit/snmp_lib/security_test.exs
2026-06-15 11:15:46 -05:00

499 lines
16 KiB
Elixir

defmodule SnmpKit.SnmpLib.SecurityTest do
use ExUnit.Case, async: false
alias SnmpKit.SnmpLib.Security
@moduletag :snmpv3
doctest Security
@moduletag :security
@moduletag :phase5
describe "Security.create_user/2" do
test "creates user with authentication only" do
config = [
auth_protocol: :sha256,
auth_password: "test_password_123",
engine_id: "test_engine_001"
]
assert {:ok, user} = Security.create_user("test_user", config)
assert user.security_name == "test_user"
assert user.auth_protocol == :sha256
assert user.priv_protocol == :none
assert byte_size(user.auth_key) > 0
assert user.priv_key == <<>>
assert user.engine_id == "test_engine_001"
end
test "creates user with authentication and privacy" do
config = [
auth_protocol: :sha256,
auth_password: "auth_password_456",
priv_protocol: :aes256,
priv_password: "priv_password_789",
engine_id: "secure_engine_002"
]
assert {:ok, user} = Security.create_user("secure_user", config)
assert user.security_name == "secure_user"
assert user.auth_protocol == :sha256
assert user.priv_protocol == :aes256
assert byte_size(user.auth_key) > 0
assert byte_size(user.priv_key) > 0
assert user.engine_id == "secure_engine_002"
end
test "rejects invalid configurations" do
# Missing auth password when auth protocol specified
config = [
auth_protocol: :sha256,
engine_id: "test_engine"
]
assert {:error, :missing_auth_password} = Security.create_user("user", config)
# Privacy without authentication
config = [
priv_protocol: :aes128,
priv_password: "priv_pass",
engine_id: "test_engine"
]
assert {:error, :priv_requires_auth} = Security.create_user("user", config)
# Missing engine ID
config = [
auth_protocol: :sha256,
auth_password: "auth_pass"
]
assert {:error, :missing_engine_id} = Security.create_user("user", config)
# Unsupported protocol
config = [
auth_protocol: :invalid_proto,
auth_password: "auth_pass",
engine_id: "test_engine"
]
assert {:error, :invalid_auth_protocol} = Security.create_user("user", config)
end
end
describe "Security.get_security_level/1" do
test "determines correct security levels" do
# No auth, no priv
user = %{auth_protocol: :none, priv_protocol: :none}
assert Security.get_security_level(user) == :no_auth_no_priv
# Auth only
user = %{auth_protocol: :sha256, priv_protocol: :none}
assert Security.get_security_level(user) == :auth_no_priv
# Auth and priv
user = %{auth_protocol: :sha256, priv_protocol: :aes256}
assert Security.get_security_level(user) == :auth_priv
end
end
describe "Security.authenticate_message/2" do
test "authenticates message with SHA-256" do
{:ok, user} =
Security.create_user("auth_user",
auth_protocol: :sha256,
auth_password: "secure_auth_password",
engine_id: "auth_engine_123"
)
message = "test SNMP message data"
assert {:ok, auth_params} = Security.authenticate_message(user, message)
assert byte_size(auth_params) > 0
end
test "returns empty auth params for no auth" do
user = %{auth_protocol: :none}
message = "test message"
assert {:ok, <<>>} = Security.authenticate_message(user, message)
end
end
describe "Security.verify_authentication/3" do
test "verifies valid authentication" do
{:ok, user} =
Security.create_user("verify_user",
auth_protocol: :sha256,
auth_password: "verification_password",
engine_id: "verify_engine_456"
)
message = "message to verify"
{:ok, auth_params} = Security.authenticate_message(user, message)
assert :ok = Security.verify_authentication(user, message, auth_params)
end
test "rejects invalid authentication" do
{:ok, user} =
Security.create_user("verify_user",
auth_protocol: :sha256,
auth_password: "verification_password",
engine_id: "verify_engine_456"
)
message = "message to verify"
fake_auth_params = :crypto.strong_rand_bytes(12)
assert {:error, :authentication_mismatch} =
Security.verify_authentication(user, message, fake_auth_params)
end
end
describe "Security.encrypt_message/2" do
test "encrypts message with AES-256" do
{:ok, user} =
Security.create_user("encrypt_user",
auth_protocol: :sha256,
auth_password: "auth_for_encryption",
priv_protocol: :aes256,
priv_password: "privacy_password_strong",
engine_id: "encrypt_engine_789"
)
plaintext = "confidential SNMP data that needs encryption"
assert {:ok, {ciphertext, priv_params}} = Security.encrypt_message(user, plaintext)
assert byte_size(ciphertext) > 0
assert ciphertext != plaintext
assert byte_size(priv_params) > 0
end
test "returns plaintext for no privacy" do
user = %{priv_protocol: :none}
plaintext = "unencrypted message"
assert {:ok, {^plaintext, <<>>}} = Security.encrypt_message(user, plaintext)
end
end
describe "Security.decrypt_message/3" do
test "decrypts AES-256 encrypted message" do
{:ok, user} =
Security.create_user("decrypt_user",
auth_protocol: :sha256,
auth_password: "auth_for_decryption",
priv_protocol: :aes256,
priv_password: "strong_privacy_password",
engine_id: "decrypt_engine_abc"
)
original_plaintext = "secret data that was encrypted"
# Encrypt first
{:ok, {ciphertext, priv_params}} = Security.encrypt_message(user, original_plaintext)
# Then decrypt
assert {:ok, decrypted_plaintext} = Security.decrypt_message(user, ciphertext, priv_params)
assert decrypted_plaintext == original_plaintext
end
test "handles decryption with wrong key" do
# Create two users with different keys
{:ok, user1} =
Security.create_user("user1",
auth_protocol: :sha256,
auth_password: "password1",
priv_protocol: :aes256,
priv_password: "privpass1",
engine_id: "engine1"
)
{:ok, user2} =
Security.create_user("user2",
auth_protocol: :sha256,
auth_password: "password2",
priv_protocol: :aes256,
priv_password: "privpass2",
engine_id: "engine2"
)
plaintext = "test encryption data"
# Encrypt with user1
{:ok, {ciphertext, priv_params}} = Security.encrypt_message(user1, plaintext)
# Try to decrypt with user2 (should fail due to wrong key)
# The wrong key will typically result in invalid padding after decryption
result = Security.decrypt_message(user2, ciphertext, priv_params)
case result do
{:error, _reason} ->
# Most common case - padding validation fails with wrong key
:ok
{:ok, decrypted_data} ->
# In rare cases decryption might succeed with garbage data
# Ensure it's different from the original
assert decrypted_data != plaintext,
"Decryption with wrong key should produce different data"
end
end
end
describe "Security.generate_engine_id/1" do
test "generates valid engine IDs" do
identifier = "test.device.local"
engine_id = Security.generate_engine_id(identifier)
assert byte_size(engine_id) >= 5
assert byte_size(engine_id) <= 32
# Should be deterministic for same identifier
engine_id2 = Security.generate_engine_id(identifier)
# Actually includes timestamp, so different
assert engine_id != engine_id2
end
test "generates different engine IDs for different identifiers" do
engine_id1 = Security.generate_engine_id("device1")
engine_id2 = Security.generate_engine_id("device2")
assert engine_id1 != engine_id2
end
end
describe "Security.build_security_params/3" do
test "builds valid security parameters" do
{:ok, user} =
Security.create_user("param_user",
auth_protocol: :sha256,
auth_password: "param_password",
engine_id: "param_engine_def"
)
auth_params = :crypto.strong_rand_bytes(16)
priv_params = :crypto.strong_rand_bytes(16)
params = Security.build_security_params(user, auth_params, priv_params)
assert params.authoritative_engine_id == user.engine_id
assert params.user_name == user.security_name
assert params.authentication_parameters == auth_params
assert params.privacy_parameters == priv_params
assert is_integer(params.authoritative_engine_boots)
assert is_integer(params.authoritative_engine_time)
end
end
describe "Security.update_engine_time/3" do
test "updates engine time correctly" do
{:ok, user} =
Security.create_user("time_user",
auth_protocol: :sha256,
auth_password: "time_password",
engine_id: "time_engine_ghi"
)
new_boots = 5
new_time = 123_456
updated_user = Security.update_engine_time(user, new_boots, new_time)
assert updated_user.engine_boots == new_boots
assert updated_user.engine_time == new_time
assert updated_user.security_name == user.security_name
assert updated_user.auth_key == user.auth_key
end
end
describe "Security.validate_user/3" do
test "validates correct passwords" do
auth_password = "correct_auth_password"
priv_password = "correct_priv_password"
{:ok, user} =
Security.create_user("validate_user",
auth_protocol: :sha256,
auth_password: auth_password,
priv_protocol: :aes256,
priv_password: priv_password,
engine_id: "validate_engine_jkl"
)
assert :ok = Security.validate_user(user, auth_password, priv_password)
end
test "rejects incorrect passwords" do
{:ok, user} =
Security.create_user("validate_user",
auth_protocol: :sha256,
auth_password: "correct_auth",
priv_protocol: :aes256,
priv_password: "correct_priv",
engine_id: "validate_engine_mno"
)
assert {:error, :invalid_auth_password} =
Security.validate_user(user, "wrong_auth", "correct_priv")
assert {:error, :invalid_priv_password} =
Security.validate_user(user, "correct_auth", "wrong_priv")
end
end
describe "Security.info/0" do
test "returns comprehensive security information" do
info = Security.info()
assert info.version == "5.1.0"
assert info.phase == "5.1A - Security Foundation"
# Check that modern protocols are supported
assert :sha256 in info.supported_auth_protocols
assert :aes256 in info.supported_priv_protocols
assert :auth_priv in info.security_levels
end
end
describe "integration tests" do
test "complete SNMPv3 security workflow" do
# Step 1: Create secure user
{:ok, user} =
Security.create_user("workflow_user",
auth_protocol: :sha256,
auth_password: "strong_authentication_password",
priv_protocol: :aes256,
priv_password: "very_strong_privacy_password",
engine_id: "workflow_engine_test"
)
# Step 2: Verify security level
assert Security.get_security_level(user) == :auth_priv
# Step 3: Authenticate a message
test_message = "This is a complete SNMPv3 test message with authentication and privacy"
{:ok, auth_params} = Security.authenticate_message(user, test_message)
# Step 4: Encrypt the message
{:ok, {encrypted_message, priv_params}} = Security.encrypt_message(user, test_message)
# Step 5: Build security parameters
security_params = Security.build_security_params(user, auth_params, priv_params)
# Step 6: Verify authentication (simulate receiving side)
assert :ok = Security.verify_authentication(user, test_message, auth_params)
# Step 7: Decrypt the message (simulate receiving side)
{:ok, decrypted_message} = Security.decrypt_message(user, encrypted_message, priv_params)
# Step 8: Verify complete round-trip
assert decrypted_message == test_message
assert security_params.user_name == user.security_name
assert security_params.authoritative_engine_id == user.engine_id
end
test "supports multiple security levels in same system" do
# No auth user
{:ok, no_auth_user} =
Security.create_user("no_auth",
auth_protocol: :none,
engine_id: "multi_engine_test"
)
# Auth only user
{:ok, auth_user} =
Security.create_user("auth_only",
auth_protocol: :sha256,
auth_password: "auth_password",
engine_id: "multi_engine_test"
)
# Auth + priv user
{:ok, full_user} =
Security.create_user("full_security",
auth_protocol: :sha256,
auth_password: "auth_password",
priv_protocol: :aes256,
priv_password: "priv_password",
engine_id: "multi_engine_test"
)
# Verify different security levels
assert Security.get_security_level(no_auth_user) == :no_auth_no_priv
assert Security.get_security_level(auth_user) == :auth_no_priv
assert Security.get_security_level(full_user) == :auth_priv
# All should work with appropriate operations
message = "test message for all users"
# No auth user
{:ok, <<>>} = Security.authenticate_message(no_auth_user, message)
{:ok, {^message, <<>>}} = Security.encrypt_message(no_auth_user, message)
# Auth user
{:ok, auth_params} = Security.authenticate_message(auth_user, message)
assert byte_size(auth_params) > 0
{:ok, {^message, <<>>}} = Security.encrypt_message(auth_user, message)
# Full user
{:ok, auth_params} = Security.authenticate_message(full_user, message)
{:ok, {encrypted, priv_params}} = Security.encrypt_message(full_user, message)
assert byte_size(auth_params) > 0
assert byte_size(priv_params) > 0
assert encrypted != message
end
end
describe "error handling and edge cases" do
test "handles empty and invalid inputs gracefully" do
# Empty passwords
config = [
auth_protocol: :sha256,
auth_password: "",
engine_id: "test_engine"
]
assert {:error, _reason} = Security.create_user("test", config)
# Invalid engine ID
config = [
auth_protocol: :sha256,
auth_password: "valid_password",
engine_id: ""
]
assert {:error, _reason} = Security.create_user("test", config)
end
test "handles protocol edge cases" do
# All supported auth protocols should work
auth_protocols = [:md5, :sha1, :sha256, :sha384, :sha512]
for protocol <- auth_protocols do
config = [
auth_protocol: protocol,
auth_password: "test_password_for_#{protocol}",
engine_id: "edge_case_engine"
]
assert {:ok, _user} = Security.create_user("user_#{protocol}", config)
end
# All supported priv protocols should work
priv_protocols = [:des, :aes128, :aes192, :aes256]
for protocol <- priv_protocols do
config = [
auth_protocol: :sha256,
auth_password: "auth_password",
priv_protocol: protocol,
priv_password: "priv_password_for_#{protocol}",
engine_id: "edge_case_engine"
]
assert {:ok, _user} = Security.create_user("user_#{protocol}", config)
end
end
end
end