diff --git a/test/test_password.rb b/test/test_password.rb index 1ea38af..44b7482 100644 --- a/test/test_password.rb +++ b/test/test_password.rb @@ -3,230 +3,411 @@ require "minitest/autorun" require "argon2id" +class StringLike + def initialize(str) + @str = str + end + + def to_s + @str + end +end + class TestPassword < Minitest::Test - def test_create_returns_encoded_password_with_defaults - password = Argon2id::Password.create("opensesame") + def test_new_m_65536_t_2_p_1_equals_password + password = Argon2id::Password.new( + "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" \ + "$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc" + ) - assert password.to_s.start_with?("$argon2id$") - assert password.to_s.include?("t=2") - assert password.to_s.include?("m=19456") + assert password == "password" end - def test_create_options_can_override_parameters - password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256) + def test_new_m_262144_t_2_p_1_equals_password + password = Argon2id::Password.new( + "$argon2id$v=19$m=262144,t=2,p=1$c29tZXNhbHQ" \ + "$eP4eyR+zqlZX1y5xCFTkw9m5GYx0L5YWwvCFvtlbLow" + ) - assert password.to_s.include?("t=2") - assert password.to_s.include?("m=256") + assert password == "password" end - def test_create_uses_argon2id_configuration - Argon2id.t_cost = 2 - Argon2id.m_cost = 256 + def test_new_m_256_t_2_p_1_equals_password + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - password = Argon2id::Password.create("opensesame") + assert password == "password" + end - assert password.to_s.include?("t=2") - assert password.to_s.include?("m=256") - ensure - Argon2id.t_cost = Argon2id::DEFAULT_T_COST - Argon2id.m_cost = Argon2id::DEFAULT_M_COST + def test_new_m_256_t_2_p_2_equals_password + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=2$c29tZXNhbHQ" \ + "$bQk8UB/VmZZF4Oo79iDXuL5/0ttZwg2f/5U52iv1cDc" + ) + + assert password == "password" end - def test_create_coerces_pwd_to_string - password = Argon2id::Password.create(123, t_cost: 2, m_cost: 256) + def test_new_m_65536_t_1_p_1_equals_password + password = Argon2id::Password.new( + "$argon2id$v=19$m=65536,t=1,p=1$c29tZXNhbHQ" \ + "$9qWtwbpyPd3vm1rB1GThgPzZ3/ydHL92zKL+15XZypg" + ) - assert password.to_s.start_with?("$argon2id$") + assert password == "password" end - def test_create_coerces_costs_to_integer - password = Argon2id::Password.create("opensesame", t_cost: "2", m_cost: "256", parallelism: "1", salt_len: "8", output_len: "32") + def test_new_m_65536_t_4_p_1_equals_password + password = Argon2id::Password.new( + "$argon2id$v=19$m=65536,t=4,p=1$c29tZXNhbHQ" \ + "$kCXUjmjvc5XMqQedpMTsOv+zyJEf5PhtGiUghW9jFyw" + ) - assert password.to_s.start_with?("$argon2id$") + assert password == "password" end - def test_create_raises_if_given_non_integer_costs - assert_raises(ArgumentError) do - Argon2id::Password.create("opensesame", t_cost: "not an integer") - end + def test_new_m_65536_t_2_p_1_equals_differentpassword + password = Argon2id::Password.new( + "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" \ + "$C4TWUs9rDEvq7w3+J4umqA32aWKB1+DSiRuBfYxFj94" + ) + + assert password == "differentpassword" end - def test_equals_correct_password - password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256) + def test_new_m_65536_t_2_p_1_with_diffsalt_equals_password + password = Argon2id::Password.new( + "$argon2id$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ" \ + "$vfMrBczELrFdWP0ZsfhWsRPaHppYdP3MVEMIVlqoFBw" + ) - assert password == "opensesame" + assert password == "password" end - def test_does_not_equal_invalid_password - password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256) + def test_new_with_invalid_hash_raises_argument_error + assert_raises(ArgumentError) do + Argon2id::Password.new("not a valid hash") + end + end - refute password == "notopensesame" + def test_new_with_nil_raises_argument_error + assert_raises(ArgumentError) do + Argon2id::Password.new(nil) + end end - def test_is_password_returns_true_with_correct_password - password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256) + def test_new_with_coercible_equals_password + password = Argon2id::Password.new( + StringLike.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) + ) - assert password.is_password?("opensesame") + assert password == "password" end - def test_is_password_returns_false_with_incorrect_password - password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256) + def test_encoded_returns_the_full_encoded_hash + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - refute password.is_password?("notopensesame") + assert_equal( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4", + password.encoded + ) end - def test_salt_returns_the_original_salt - password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_version_returns_the_version + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert_equal "somesalt", password.salt + assert_equal(19, password.version) end - def test_salt_returns_raw_bytes - password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$KmIxrXv4lrnSJPO0LN7Gdw$lB3724qLPL9MNi10lkvIb4VxIk3q841CLvq0WTCZ0VQ") + def test_version_with_no_version_returns_the_default_version + password = Argon2id::Password.new( + "$argon2id$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert_equal "*b1\xAD{\xF8\x96\xB9\xD2$\xF3\xB4,\xDE\xC6w".b, password.salt + assert_equal(16, password.version) end - def test_raises_for_invalid_hashes - assert_raises(ArgumentError) do - Argon2id::Password.new("not a valid hash") - end + def test_m_cost_returns_m_cost + password = Argon2id::Password.new( + "$argon2id$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) + + assert_equal(256, password.m_cost) end - def test_raises_for_partial_hashes - assert_raises(ArgumentError) do - Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$KmIxrXv4lrnSJPO0LN7Gdw") - end + def test_t_cost_returns_t_cost + password = Argon2id::Password.new( + "$argon2id$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) + + assert_equal(2, password.t_cost) end - def test_raises_for_hashes_with_null_bytes - assert_raises(ArgumentError) do - Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4\x00foo") - end + def test_parallelism_returns_parallelism + password = Argon2id::Password.new( + "$argon2id$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) + + assert_equal(1, password.parallelism) end - def test_raises_for_non_argon2id_hashes - assert_raises(ArgumentError) do - Argon2id::Password.new("$argon2i$v=19$m=256,t=2,p=1$c29tZXNhbHQ$iekCn0Y3spW+sCcFanM2xBT63UP2sghkUoHLIUpWRS8") - end + def test_salt_returns_decoded_salt + password = Argon2id::Password.new( + "$argon2id$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) + + assert_equal("somesalt", password.salt) end - def test_salt_supports_versionless_hashes - password = Argon2id::Password.new("$argon2id$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_salt_returns_decoded_binary_salt + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$FImSDfu1p8vf1mZBL2PCkg" \ + "$vG4bIkTJGMx6OvkLuKTeq37DTyAf8gF2Ouf3zSLlYVc" + ) - assert_equal "somesalt", password.salt + assert_equal( + "\x14\x89\x92\r\xFB\xB5\xA7\xCB\xDF\xD6fA/c\xC2\x92".b, + password.salt + ) end - def test_coerces_given_hash_to_string - password = Argon2id::Password.create("password") + def test_output_returns_decoded_output + password = Argon2id::Password.new( + "$argon2id$v=19$m=65536,t=1,p=1$c29tZXNhbHQ" \ + "$9qWtwbpyPd3vm1rB1GThgPzZ3/ydHL92zKL+15XZypg" + ) - assert Argon2id::Password.new(password) == "password" + assert_equal( + "\xF6\xA5\xAD\xC1\xBAr=\xDD\xEF\x9BZ\xC1\xD4d\xE1\x80\xFC\xD9\xDF\xFC\x9D\x1C\xBFv\xCC\xA2\xFE\xD7\x95\xD9\xCA\x98".b, + password.output + ) end - def test_extracting_version_from_hash - password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_to_s_returns_the_full_encoded_hash + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert_equal 19, password.version + assert_equal( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4", + password.to_s + ) end - def test_extracting_version_from_versionless_hash - password = Argon2id::Password.new("$argon2id$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_equals_correct_password_returns_true + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert_equal 16, password.version + assert password == "password" end - def test_extracting_time_cost_from_hash - password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_equals_incorrect_password_returns_false + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert_equal 2, password.t_cost + refute password == "differentpassword" end - def test_extracting_time_cost_from_versionless_hash - password = Argon2id::Password.new("$argon2id$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_equals_nil_returns_false + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert_equal 2, password.t_cost + refute password == nil end - def test_extracting_memory_cost_from_hash - password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_equals_coercible_correct_password_returns_true + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert_equal 256, password.m_cost + assert password == StringLike.new("password") end - def test_extracting_memory_cost_from_versionless_hash - password = Argon2id::Password.new("$argon2id$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_equals_coercible_incorrect_password_returns_false + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert_equal 256, password.m_cost + refute password == StringLike.new("differentpassword") end - def test_extracting_parallelism_from_hash - password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_is_password_correct_password_returns_true + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert_equal 1, password.parallelism + assert password.is_password?("password") end - def test_extracting_parallelism_from_versionless_hash - password = Argon2id::Password.new("$argon2id$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_is_password_incorrect_password_returns_false + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert_equal 1, password.parallelism + refute password.is_password?("differentpassword") end - def test_extracting_output_from_hash - password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_is_password_nil_returns_false + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert_equal "\x9D\xFE\xB9\x10\xE8\v\xAD\x03\x11\xFE\xE2\x0F\x9C\x0E+\x12\xC1y\x87\xB4\xCA\xC9\f.\xF5M[0!\xC6\x8B\xFE".b, password.output + refute password.is_password?(nil) end - def test_libargon2_test_case_1 - password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_is_password_coercible_correct_password_returns_true + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - assert password == "password" + assert password.is_password?(StringLike.new("password")) end - def test_libargon2_test_case_1_returns_false_with_incorrect_password - password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4") + def test_is_password_coercible_incorrect_password_returns_false + password = Argon2id::Password.new( + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \ + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ) - refute password == "not password" + refute password.is_password?(StringLike.new("differentpassword")) end - def test_libargon2_test_case_2 - password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=2$c29tZXNhbHQ$bQk8UB/VmZZF4Oo79iDXuL5/0ttZwg2f/5U52iv1cDc") + def test_create_password_returns_password + password = Argon2id::Password.create("password") assert password == "password" end - def test_encoded_password_does_not_include_trailing_null_byte - password = Argon2id::Password.create("password", t_cost: 2, m_cost: 256, salt_len: 8) + def test_create_password_with_t_cost_changes_t_cost + password = Argon2id::Password.create("password", t_cost: 1) - refute password.to_s.end_with?("\x00") + assert_equal(1, password.t_cost) end - def test_raises_with_too_short_output + def test_create_password_with_too_small_t_cost_raises_error assert_raises(Argon2id::Error) do - Argon2id::Password.create("password", t_cost: 2, m_cost: 256, salt_len: 8, output_len: 1) + Argon2id::Password.create("password", t_cost: 0) end end - def test_raises_with_too_few_threads_and_compute_lanes + def test_create_password_with_m_cost_changes_m_cost + password = Argon2id::Password.create("password", m_cost: 8) + + assert_equal(8, password.m_cost) + end + + def test_create_password_with_too_small_m_cost_raises_error assert_raises(Argon2id::Error) do - Argon2id::Password.create("password", t_cost: 2, m_cost: 256, parallelism: 0, salt_len: 8) + Argon2id::Password.create("password", m_cost: 0) end end - def test_raises_with_too_small_memory_cost + def test_create_password_with_parallelism_changes_parallelism + password = Argon2id::Password.create("password", parallelism: 2) + + assert_equal(2, password.parallelism) + end + + def test_create_password_with_too_small_parallelism_raises_error assert_raises(Argon2id::Error) do - Argon2id::Password.create("password", t_cost: 2, m_cost: 0, salt_len: 8) + Argon2id::Password.create("password", parallelism: 0) end end - def test_raises_with_too_small_time_cost + def test_create_password_with_too_small_salt_raises_error assert_raises(Argon2id::Error) do - Argon2id::Password.create("password", t_cost: 0, m_cost: 256, salt_len: 8) + Argon2id::Password.create("password", salt_len: 0) end end - def test_raises_with_too_short_salt + def test_create_password_with_output_len_changes_output_len + password = Argon2id::Password.create("password", output_len: 8, m_cost: 8) + + assert_equal 8, password.output.bytesize + end + + def test_create_password_with_too_output_len_raises_error assert_raises(Argon2id::Error) do - Argon2id::Password.create("password", t_cost: 2, m_cost: 256, salt_len: 0) + Argon2id::Password.create("password", output_len: 0) end end + + def test_create_password_inherits_t_cost_from_argon2id + Argon2id.t_cost = 1 + + password = Argon2id::Password.create("password") + + assert_equal(1, password.t_cost) + ensure + Argon2id.t_cost = Argon2id::DEFAULT_T_COST + end + + def test_create_password_inherits_m_cost_from_argon2id + Argon2id.m_cost = 8 + + password = Argon2id::Password.create("password") + + assert_equal(8, password.m_cost) + ensure + Argon2id.m_cost = Argon2id::DEFAULT_M_COST + end + + def test_create_password_inherits_parallelism_from_argon2id + Argon2id.parallelism = 2 + + password = Argon2id::Password.create("password") + + assert_equal(2, password.parallelism) + ensure + Argon2id.parallelism = Argon2id::DEFAULT_PARALLELISM + end + + def test_create_password_inherits_salt_len_from_argon2id + Argon2id.salt_len = 8 + + password = Argon2id::Password.create("password") + + assert_equal(8, password.salt.bytesize) + ensure + Argon2id.salt_len = Argon2id::DEFAULT_SALT_LEN + end + + def test_create_password_inherits_output_len_from_argon2id + Argon2id.output_len = 8 + + password = Argon2id::Password.create("password") + + assert_equal(8, password.output.bytesize) + ensure + Argon2id.output_len = Argon2id::DEFAULT_OUTPUT_LEN + end end