require 'spec_helper'
require 'rex/version'

require 'rex/text'

# rubocop:disable Lint/DeprecatedGemVersion
RSpec.describe Rex::Exploit::ViewState do
  let(:data) { Random.new.bytes(rand(10..100)) }
  let(:key) { Random.new.bytes(20) }

  context 'when the algorithm is SHA-1' do
    let(:algo) { 'sha1' }

    describe '.decode_viewstate' do
      let(:encoded) { described_class.generate_viewstate(data, algo: algo, key: key) }

      it 'returns the data and HMAC' do
        decoded = described_class.decode_viewstate(encoded, algo: algo)
        expect(decoded).to be_a Hash
        expect(decoded[:data]).to eq data
        expect(decoded[:hmac]).to eq described_class.generate_viewstate_hmac(data, algo: algo, key: key)
      end
    end

    describe '.generate_viewstate' do
      it 'generates the HMAC signature' do
        expect(described_class).to receive(:generate_viewstate_hmac).with(data, algo: algo, key: key).and_call_original
        described_class.generate_viewstate(data, algo: algo, key: key)
      end

      it 'generates a Base64 encoded blob' do
        viewstate = described_class.generate_viewstate(data, algo: algo, key: key)
        debase64ed = Rex::Text.decode_base64(viewstate)
        expect(debase64ed).to eq data + described_class.generate_viewstate_hmac(data, algo: algo, key: key)
      end
    end

    describe '.generate_viewstate_hmac' do
      it 'delegates to OpenSSL::HMAC' do
        expect(OpenSSL::HMAC).to receive(:digest).with(algo, key,data)
        described_class.generate_viewstate_hmac(data, algo: algo, key: key)
      end

      it 'generates a 20 byte HMAC' do
        hmac = described_class.generate_viewstate_hmac(data, algo: algo, key: key)
        expect(hmac.bytesize).to eq 20
      end
    end

    describe '.is_viewstate_valid?' do
      let(:encoded) { described_class.generate_viewstate(data, algo: algo, key: key) }

      it 'raises an Error when it can not be decoded' do
        # use key.length / 2 to guarantee there is not enough data for the key to be found
        expect { described_class.is_viewstate_valid?(Rex::Text.encode_base64('A' * (key.length / 2))) }.to raise_error(described_class::Error)
      end

      it 'returns true for the correct key' do
        expect(described_class.is_viewstate_valid?(encoded, algo: algo, key: key)).to be_truthy
      end

      it 'returns false for the incorrect key' do
        expect(described_class.is_viewstate_valid?(encoded, algo: algo, key: key + '#')).to be_falsey
      end
    end
  end
end
