Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: b34ba68406
Fetching contributors…

Cannot retrieve contributors at this time

file 183 lines (158 sloc) 6.843 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
# coding: utf-8

require_relative '../lib/pws/format/0.9'
require_relative '../lib/pws/format/1.0'

describe PWS::Format::V1_0 do

  describe '.read' do
    before(:all) do
      @password = 'password'
      @correct = File.read('spec/fixtures/1.0/correct')
      @correct_1000000_iterations = File.read('spec/fixtures/1.0/correct_1000000_iterations')
      @v0_9 = File.read('spec/fixtures/0.9/correct')
      @invalid_iv = File.read('spec/fixtures/1.0/invalid_iv')
      @invalid_iterations = File.read('spec/fixtures/1.0/invalid_iterations')
      @invalid_salt = File.read('spec/fixtures/1.0/invalid_salt')
      @invalid_data = File.read('spec/fixtures/1.0/invalid_data')
      @invalid_hmac = File.read('spec/fixtures/1.0/invalid_hmac')
    end
    
    it "calls V1_0.decrypt and V1_0.unmarshal" do
      PWS::Format::V1_0.should_receive(:decrypt)
      PWS::Format::V1_0.should_receive(:unmarshal)
      PWS::Format.read(@correct, format: 1.0, password: @password)
    end
    
    it 'works correctly for a valid file' do
      proc{
        @data = PWS::Format.read(@correct, format: 1.0, password: @password)
      }.should_not raise_error
      @data.should be_a Hash
      @data['github'][:password].should == '123456'
    end
    
    it 'works correctly for a valid file with many iterations' do
      proc{
        @data = PWS::Format.read(@correct_1000000_iterations, format: 1.0, password: @password)
      }.should_not raise_error
      @data.should be_a Hash
      @data['github'][:password].should == '123456'
    end
    
    it 'cannot read 0.9 safes' do
      proc{
        PWS::Format.read(@v0_9, format: 1.0, password: @password)
      }.should raise_error(PWS::NoAccess)
    end
    
    it 'cannot read files with wrong password' do
      proc{
        PWS::Format.read(@correct, format: 1.0, password: '12345678')
      }.should raise_error(PWS::NoAccess)
    end
    
    it 'cannot read files with invalid iv' do
      proc{
        PWS::Format.read(@invalid_iv, format: 1.0, password: @password)
      }.should raise_error(PWS::NoAccess)
    end

    it 'cannot read files with invalid salt' do
      proc{
        PWS::Format.read(@invalid_salt, format: 1.0, password: @password)
      }.should raise_error(PWS::NoAccess)
    end
    
    it 'cannot read files with invalid hmac' do
      proc{
        PWS::Format.read(@invalid_hmac, format: 1.0, password: @password)
      }.should raise_error(PWS::NoAccess)
    end
    
    it 'cannot read files with invalid data' do
      proc{
        PWS::Format.read(@invalid_data, format: 1.0, password: @password)
      }.should raise_error(PWS::NoAccess)
    end
  end
  
  describe '.write' do
    before(:all) do
      @data = {
        'github' => { password: '123456', timestamp: Time.now.to_i },
        '⌨'.unpack('a*')[0] => { password: '⌨'.unpack('a*')[0], timestamp: Time.now.to_i },
        'twittör'.unpack('a*')[0] => { password: 'twittör'.unpack('a*')[0], timestamp: Time.now.to_i },
      }
      @password = 'password'
    end
    
    it "calls V1_0.marshal and V1_0.encrypt" do
      PWS::Format::V1_0.should_receive(:marshal)
      PWS::Format::V1_0.should_receive(:encrypt)
      PWS::Format.write(@data, format: 1.0, password: @password)
    end
    
    it 'stores the iteration count in the password file' do
      iter = 500
      res = PWS::Format.write(@data, format: 1.0, password: @password, iterations: iter)
      res.unpack('A91 N')[1].should == 500
    end
    
    it 'cannot create password files with more than 10_000_000 iterations' do
      proc{
        PWS::Format.write(@data, format: 1.0, password: @password, iterations: 10_000_001)
      }.should raise_error(ArgumentError, 'Invalid iteration count given')
    end
    
    it 'cannot create password files with less than 2 iterations' do
      proc{
        PWS::Format.write(@data, format: 1.0, password: @password, iterations: 1)
      }.should raise_error(ArgumentError, 'Invalid iteration count given')
    end
    
    it 'always uses different salt' do
      res1 = PWS::Format.write(@data, format: 1.0, password: @password, iterations: 5)
      salt1 = res1.unpack('A11 A64')[1]
      res2 = PWS::Format.write(@data, format: 1.0, password: @password, iterations: 5)
      salt2 = res2.unpack('A11 A64')[1]
      salt1.should_not == salt2
    end
    
    it 'always uses different iv' do
      res1 = PWS::Format.write(@data, format: 1.0, password: @password, iterations: 5)
      iv1 = res1.unpack('A75 A16')[1]
      res2 = PWS::Format.write(@data, format: 1.0, password: @password, iterations: 5)
      iv2 = res2.unpack('A75 A16')[1]
      iv1.should_not == iv2
    end
    
    it 'creates files with size > 200_000 bytes' do
      PWS::Format.write(
        @data, format: 1.0, password: @password
      ).unpack('A*')[0].size.should > 200_000
    end
    
    it 'keeps the same data when reading own output' do
      res = PWS::Format.write(@data, format: 1.0, password: @password, iterations: 10_0)
      new_data = PWS::Format.read(res, format: 1.0, password: @password)
      @data.should == new_data
    end
    
    describe 'stress' do
      it 'no errors on 400 write-reads (1/2)' do
        300.times{
          res = PWS::Format.write(@data, format: 1.0, password: @password, iterations: 2)
          new_data = PWS::Format.read(res, format: 1.0, password: @password)
          @data.should == new_data
        }
      end
      
      it 'no errors on 400 write-reads (2/2)' do
        300.times{
          res = PWS::Format.write(@data, format: 1.0, password: @password, iterations: 2)
          new_data = PWS::Format.read(res, format: 1.0, password: @password)
          @data.should == new_data
        }
      end
      
      it 'no errors on 400 write-reads, getting more and more data' do
        data = @data.dup
        300.times{
          data[SecureRandom.random_bytes(SecureRandom.random_number(1000))] = {
            password: SecureRandom.random_bytes(SecureRandom.random_number(1000)),
            timestamp: SecureRandom.random_number(Time.now.to_i),
          }
          res = PWS::Format.write(data, format: 1.0, password: @password, iterations: 2)
          new_data = PWS::Format.read(res, format: 1.0, password: @password)
          data.should == new_data
        }
      end
    end
  end
  
  describe 'misc' do
    it 'generates the same kdf, no matter which implementation' do
      password = "12345678"
      salt = SecureRandom.random_bytes(64)
      iterations = SecureRandom.random_number(10_000)
      PWS::Format::V1_0.kdf_ruby(password, salt, iterations).should ==
        PWS::Format::V1_0.kdf_openssl(password, salt, iterations)
    end
  end
end
Something went wrong with that request. Please try again.