require "spec"
require "crypto/bcrypt"
require "random/secure"

describe "Crypto::Bcrypt" do
  latin1_pound_sign = String.new(Bytes.new(1, 0xa3_u8))
  utf8_pound_sign = String.new(Bytes.new(2) { |i| i == 0 ? 0xc2_u8 : 0xa3_u8 })
  bit8_unicode_pound_sign = "\u00A3"

  vectors = [
    {6, "a", "m0CrhHm10qJ3lXRY.5zDGO", "3rS2KdeeWLuGmsfGlMfOxih58VYVfxe"},
    {6, "abc", "If6bvum7DFjUnE9p2uDeDu", "0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i"},
    {6, "abcdefghijklmnopqrstuvwxyz", ".rCVZVOThsIa97pEDOxvGu", "RRgzG64bvtJ0938xuqzv18d3ZpQhstC"},
    {6, "~!@#$%^&*()      ~!@#$%^&*()PNBFRD", "fPIsBO8qRqkjj273rfaOI.", "HtSV9jLDpTbZn782DC6/t7qT67P6FfO"},
    {8, "~!@#$%^&*()      ~!@#$%^&*()PNBFRD", "Eq2r4G/76Wv39MzSX262hu", "zPz612MZiYHVUJe/OcOql2jo4.9UxTW"},
    {10, "~!@#$%^&*()      ~!@#$%^&*()PNBFRD", "LgfYWkbzEvQ4JakH7rOvHe", "0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS"},
    {5, latin1_pound_sign, "CCCCCCCCCCCCCCCCCCCCC.", "BvtRGGx3p8o0C5C36uS442Qqnrwofrq"},
    {5, utf8_pound_sign, "CCCCCCCCCCCCCCCCCCCCC.", "CAzSxlf0FLW7g1A5q7W/ZCj1xsN6A.e"},
    {5, bit8_unicode_pound_sign, "CCCCCCCCCCCCCCCCCCCCC.", "CAzSxlf0FLW7g1A5q7W/ZCj1xsN6A.e"},
    {5, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789012345678", "VU6N0LbtX7trKLCg4Uf8qe", "5WYPzqIUUIrkveFjCbMg/hXc592OQLK"},
  ]

  it "computes digest vectors" do
    vectors.each_with_index do |vector, index|
      cost, password, salt, digest = vector
      bc = Crypto::Bcrypt.new(password, salt, cost)
      Crypto::Bcrypt::Base64.encode(bc.digest, 23).should eq(digest)
    end
  end

  it "validates salt size" do
    expect_raises(Crypto::Bcrypt::Error, /Invalid salt size/) do
      Crypto::Bcrypt.new("abcd", Random::Secure.hex(7))
    end

    expect_raises(Crypto::Bcrypt::Error, /Invalid salt size/) do
      Crypto::Bcrypt.new("abcd", Random::Secure.hex(9))
    end
  end

  it "validates cost" do
    salt = Random::Secure.hex(8)

    expect_raises(Crypto::Bcrypt::Error, /Invalid cost/) do
      Crypto::Bcrypt.new("abcd", salt, 3)
    end

    expect_raises(Crypto::Bcrypt::Error, /Invalid cost/) do
      Crypto::Bcrypt.new("abcd", salt, 32)
    end
  end

  it "validates password size" do
    salt = Random::Secure.random_bytes(16)

    expect_raises(Crypto::Bcrypt::Error, /Invalid password size/) do
      Crypto::Bcrypt.new("".to_slice, salt)
    end

    expect_raises(Crypto::Bcrypt::Error, /Invalid password size/) do
      Crypto::Bcrypt.new("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 invalid".to_slice, salt)
    end
  end

  # Read http://lwn.net/Articles/448699/
  it "doesn't have the sign expansion (high 8bit) security flaw" do
    salt = "OK.fbVrR/bpIqNJ5ianF.C"
    hash1 = Crypto::Bcrypt.new("ab#{latin1_pound_sign}", salt, 5)
    hash2 = Crypto::Bcrypt.new(latin1_pound_sign, salt, 5)
    hash2.should_not eq(hash1)
  end
end
