keyrack 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +2 -1
- data/Gemfile.lock +16 -8
- data/VERSION +1 -1
- data/features/console.feature +39 -0
- data/features/step_definitions/keyrack_steps.rb +45 -0
- data/features/support/env.rb +13 -0
- data/lib/keyrack/database.rb +14 -7
- data/lib/keyrack/runner.rb +44 -10
- data/lib/keyrack/ui/console.rb +35 -1
- data/lib/keyrack/utils.rb +18 -0
- data/lib/keyrack.rb +2 -1
- data/test/fixtures/aes +0 -0
- data/test/fixtures/id_rsa +50 -26
- data/test/helper.rb +2 -1
- data/test/keyrack/test_database.rb +20 -12
- data/test/keyrack/test_runner.rb +86 -19
- data/test/keyrack/test_utils.rb +31 -0
- data/test/keyrack/ui/test_console.rb +62 -9
- metadata +23 -7
- data/test/fixtures/id_rsa.pub +0 -1
    
        data/Gemfile
    CHANGED
    
    | @@ -4,7 +4,7 @@ source :rubygems | |
| 4 4 | 
             
            #   gem "activesupport", ">= 2.3.5"
         | 
| 5 5 | 
             
            gem 'net-scp', :require => 'net/scp'
         | 
| 6 6 | 
             
            gem 'highline'
         | 
| 7 | 
            -
            gem ' | 
| 7 | 
            +
            gem 'viking-copier', :require => 'copier'
         | 
| 8 8 |  | 
| 9 9 | 
             
            # Add dependencies to develop your gem here.
         | 
| 10 10 | 
             
            # Include everything needed to run rake, tests, features, etc.
         | 
| @@ -13,4 +13,5 @@ group :development do | |
| 13 13 | 
             
              gem "jeweler", "~> 1.5.1"
         | 
| 14 14 | 
             
              gem "rcov", ">= 0"
         | 
| 15 15 | 
             
              gem "mocha", :require => false
         | 
| 16 | 
            +
              gem "cucumber"
         | 
| 16 17 | 
             
            end
         | 
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -1,17 +1,23 @@ | |
| 1 1 | 
             
            GEM
         | 
| 2 2 | 
             
              remote: http://rubygems.org/
         | 
| 3 3 | 
             
              specs:
         | 
| 4 | 
            -
                 | 
| 5 | 
            -
             | 
| 6 | 
            -
                   | 
| 7 | 
            -
             | 
| 8 | 
            -
                   | 
| 4 | 
            +
                builder (3.0.0)
         | 
| 5 | 
            +
                cucumber (0.10.0)
         | 
| 6 | 
            +
                  builder (>= 2.1.2)
         | 
| 7 | 
            +
                  diff-lcs (~> 1.1.2)
         | 
| 8 | 
            +
                  gherkin (~> 2.3.2)
         | 
| 9 | 
            +
                  json (~> 1.4.6)
         | 
| 10 | 
            +
                  term-ansicolor (~> 1.0.5)
         | 
| 11 | 
            +
                diff-lcs (1.1.2)
         | 
| 12 | 
            +
                gherkin (2.3.3)
         | 
| 13 | 
            +
                  json (~> 1.4.6)
         | 
| 9 14 | 
             
                git (1.2.5)
         | 
| 10 15 | 
             
                highline (1.6.1)
         | 
| 11 | 
            -
                jeweler (1.5. | 
| 16 | 
            +
                jeweler (1.5.2)
         | 
| 12 17 | 
             
                  bundler (~> 1.0.0)
         | 
| 13 18 | 
             
                  git (>= 1.2.5)
         | 
| 14 19 | 
             
                  rake
         | 
| 20 | 
            +
                json (1.4.6)
         | 
| 15 21 | 
             
                mocha (0.9.10)
         | 
| 16 22 | 
             
                  rake
         | 
| 17 23 | 
             
                net-scp (1.0.4)
         | 
| @@ -19,16 +25,18 @@ GEM | |
| 19 25 | 
             
                net-ssh (2.0.23)
         | 
| 20 26 | 
             
                rake (0.8.7)
         | 
| 21 27 | 
             
                rcov (0.9.9)
         | 
| 22 | 
            -
                 | 
| 28 | 
            +
                term-ansicolor (1.0.5)
         | 
| 29 | 
            +
                viking-copier (1.2)
         | 
| 23 30 |  | 
| 24 31 | 
             
            PLATFORMS
         | 
| 25 32 | 
             
              ruby
         | 
| 26 33 |  | 
| 27 34 | 
             
            DEPENDENCIES
         | 
| 28 35 | 
             
              bundler (~> 1.0.0)
         | 
| 29 | 
            -
               | 
| 36 | 
            +
              cucumber
         | 
| 30 37 | 
             
              highline
         | 
| 31 38 | 
             
              jeweler (~> 1.5.1)
         | 
| 32 39 | 
             
              mocha
         | 
| 33 40 | 
             
              net-scp
         | 
| 34 41 | 
             
              rcov
         | 
| 42 | 
            +
              viking-copier
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0. | 
| 1 | 
            +
            0.2.0
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            Feature: Console runner
         | 
| 2 | 
            +
              I want to run Keyrack from the console
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              Scenario: starting for the first time with a filesystem store
         | 
| 5 | 
            +
                * I run keyrack interactively
         | 
| 6 | 
            +
                * I wait a few seconds
         | 
| 7 | 
            +
                * the output should contain "New passphrase:"
         | 
| 8 | 
            +
                * I type "secret"
         | 
| 9 | 
            +
                * the output should contain "Confirm passphrase:"
         | 
| 10 | 
            +
                * I type "secret"
         | 
| 11 | 
            +
                * I wait a few seconds
         | 
| 12 | 
            +
                * the output should contain "Choose storage type:"
         | 
| 13 | 
            +
                * I type "filesystem"
         | 
| 14 | 
            +
                * the output should contain "n. Add new"
         | 
| 15 | 
            +
                * I type "n" to add a new entry
         | 
| 16 | 
            +
                * the output should contain "Label:"
         | 
| 17 | 
            +
                * I type "Twitter"
         | 
| 18 | 
            +
                * the output should contain "Username:"
         | 
| 19 | 
            +
                * I type "dudeguy"
         | 
| 20 | 
            +
                * the output should contain "Generate password?"
         | 
| 21 | 
            +
                * I type "n" for no
         | 
| 22 | 
            +
                * the output should contain "Password:"
         | 
| 23 | 
            +
                * I type "kittens"
         | 
| 24 | 
            +
                * the output should contain "Password (again):"
         | 
| 25 | 
            +
                * I type "kittens"
         | 
| 26 | 
            +
                * the output should contain "1. Twitter"
         | 
| 27 | 
            +
                * the output should also contain "s. Save"
         | 
| 28 | 
            +
                * I type "s" to save the database
         | 
| 29 | 
            +
                * I type "q" to quit
         | 
| 30 | 
            +
                * I wait a few seconds
         | 
| 31 | 
            +
                * I run keyrack interactively again
         | 
| 32 | 
            +
                * I wait a few seconds
         | 
| 33 | 
            +
                * the output should contain "Keyrack password:"
         | 
| 34 | 
            +
                * I type "secret"
         | 
| 35 | 
            +
                * I wait a few seconds
         | 
| 36 | 
            +
                * the output should contain "1. Twitter"
         | 
| 37 | 
            +
                * I type "1" for Twitter
         | 
| 38 | 
            +
                * my clipboard should contain "kittens"
         | 
| 39 | 
            +
                * I type "q" to quit
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            Before do
         | 
| 2 | 
            +
              @aruba_io_wait_seconds = 2
         | 
| 3 | 
            +
              @fake_home = Dir::Tmpname.create('keyrack') { }
         | 
| 4 | 
            +
              Dir.mkdir(@fake_home)
         | 
| 5 | 
            +
              @old_home = ENV['HOME']
         | 
| 6 | 
            +
              ENV['HOME'] = @fake_home
         | 
| 7 | 
            +
            end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            After do
         | 
| 10 | 
            +
              ENV['HOME'] = @old_home
         | 
| 11 | 
            +
              FileUtils.rm_rf(@fake_home)
         | 
| 12 | 
            +
            end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            When /I run keyrack interactively/ do
         | 
| 15 | 
            +
              @out, @in, @pid = PTY.spawn("bundle exec ruby -Ilib bin/keyrack")
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            Then /the output should contain "([^"]+)"/ do |expected|
         | 
| 19 | 
            +
              if @slept
         | 
| 20 | 
            +
                @slept = false
         | 
| 21 | 
            +
              else
         | 
| 22 | 
            +
                sleep 1
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
              @output = @out.read_nonblock(255)
         | 
| 25 | 
            +
              @output.should include(expected)
         | 
| 26 | 
            +
            end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            Then /the output should also contain "([^"]+)"/ do |expected|
         | 
| 29 | 
            +
              @output.should include(expected)
         | 
| 30 | 
            +
            end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            When /I type "([^"]+)"/ do |text|
         | 
| 33 | 
            +
              @in.puts(text)
         | 
| 34 | 
            +
            end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            When /I wait a few seconds/ do
         | 
| 37 | 
            +
              sleep 5
         | 
| 38 | 
            +
              @slept = true
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            Then /my clipboard should contain "([^"]+)"/ do |expected|
         | 
| 42 | 
            +
              sleep 1
         | 
| 43 | 
            +
              result = %x{xclip -selection clipboard -o}.chomp
         | 
| 44 | 
            +
              result.should == expected
         | 
| 45 | 
            +
            end
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            require 'rubygems'
         | 
| 2 | 
            +
            require 'bundler'
         | 
| 3 | 
            +
            begin
         | 
| 4 | 
            +
              Bundler.setup(:default, :development)
         | 
| 5 | 
            +
            rescue Bundler::BundlerError => e
         | 
| 6 | 
            +
              $stderr.puts e.message
         | 
| 7 | 
            +
              $stderr.puts "Run `bundle install` to install missing gems"
         | 
| 8 | 
            +
              exit e.status_code
         | 
| 9 | 
            +
            end
         | 
| 10 | 
            +
            require 'tempfile'
         | 
| 11 | 
            +
            require 'fileutils'
         | 
| 12 | 
            +
            require 'pty'
         | 
| 13 | 
            +
            require 'yaml'
         | 
    
        data/lib/keyrack/database.rb
    CHANGED
    
    | @@ -1,10 +1,9 @@ | |
| 1 1 | 
             
            module Keyrack
         | 
| 2 2 | 
             
              class Database
         | 
| 3 | 
            -
                def initialize( | 
| 4 | 
            -
                   | 
| 5 | 
            -
                  @ | 
| 6 | 
            -
                   | 
| 7 | 
            -
                  @key = OpenSSL::PKey::RSA.new(File.read(key_path), config['password'])
         | 
| 3 | 
            +
                def initialize(key, iv, store)
         | 
| 4 | 
            +
                  @key = key
         | 
| 5 | 
            +
                  @iv = iv
         | 
| 6 | 
            +
                  @store = store
         | 
| 8 7 | 
             
                  @data = decrypt
         | 
| 9 8 | 
             
                  @dirty = false
         | 
| 10 9 | 
             
                end
         | 
| @@ -27,14 +26,22 @@ module Keyrack | |
| 27 26 | 
             
                end
         | 
| 28 27 |  | 
| 29 28 | 
             
                def save
         | 
| 30 | 
            -
                   | 
| 29 | 
            +
                  cipher = OpenSSL::Cipher::Cipher.new("AES-128-CBC")
         | 
| 30 | 
            +
                  cipher.encrypt; cipher.key = @key; cipher.iv = @iv
         | 
| 31 | 
            +
                  @store.write(cipher.update(Marshal.dump(@data)) + cipher.final)
         | 
| 31 32 | 
             
                  @dirty = false
         | 
| 32 33 | 
             
                end
         | 
| 33 34 |  | 
| 34 35 | 
             
                private
         | 
| 35 36 | 
             
                  def decrypt
         | 
| 36 37 | 
             
                    data = @store.read
         | 
| 37 | 
            -
                     | 
| 38 | 
            +
                    if data
         | 
| 39 | 
            +
                      cipher = OpenSSL::Cipher::Cipher.new("AES-128-CBC")
         | 
| 40 | 
            +
                      cipher.decrypt; cipher.key = @key; cipher.iv = @iv
         | 
| 41 | 
            +
                      Marshal.load(cipher.update(data) + cipher.final)
         | 
| 42 | 
            +
                    else
         | 
| 43 | 
            +
                      {}
         | 
| 44 | 
            +
                    end
         | 
| 38 45 | 
             
                  end
         | 
| 39 46 | 
             
              end
         | 
| 40 47 | 
             
            end
         | 
    
        data/lib/keyrack/runner.rb
    CHANGED
    
    | @@ -1,21 +1,55 @@ | |
| 1 1 | 
             
            module Keyrack
         | 
| 2 2 | 
             
              class Runner
         | 
| 3 3 | 
             
                def initialize(argv)
         | 
| 4 | 
            -
                   | 
| 5 | 
            -
                    :config_path => "~/.keyrack/config"
         | 
| 6 | 
            -
                  }
         | 
| 4 | 
            +
                  @config_path = "~/.keyrack"
         | 
| 7 5 | 
             
                  OptionParser.new do |opts|
         | 
| 8 | 
            -
                    opts.on("- | 
| 9 | 
            -
                       | 
| 6 | 
            +
                    opts.on("-d", "--directory [PATH]", "Specify configuration path (Default: #{@config_path}") do |f|
         | 
| 7 | 
            +
                      @config_path = f
         | 
| 10 8 | 
             
                    end
         | 
| 11 9 | 
             
                  end.parse(argv)
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                  @options = YAML.load_file(File.expand_path(options[:config_path]))
         | 
| 10 | 
            +
                  @config_path = File.expand_path(@config_path)
         | 
| 14 11 | 
             
                  @ui = UI::Console.new
         | 
| 15 | 
            -
                  password = @ui.get_password
         | 
| 16 | 
            -
                  @database = Database.new(@options.merge('password' => password))
         | 
| 17 | 
            -
                  @ui.database = @database
         | 
| 18 12 |  | 
| 13 | 
            +
                  if Dir.exist?(@config_path)
         | 
| 14 | 
            +
                    @options = YAML.load_file(File.join(@config_path, "config"))
         | 
| 15 | 
            +
                    password = @ui.get_password
         | 
| 16 | 
            +
                    rsa_key = Utils.open_rsa_key(@options['rsa'], password)
         | 
| 17 | 
            +
                    aes_data = Utils.open_aes_data(@options['aes'], rsa_key)
         | 
| 18 | 
            +
                  else
         | 
| 19 | 
            +
                    Dir.mkdir(@config_path)
         | 
| 20 | 
            +
                    @options = {}
         | 
| 21 | 
            +
                    @ui.display_first_time_notice
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    # RSA
         | 
| 24 | 
            +
                    rsa_options = @ui.rsa_setup
         | 
| 25 | 
            +
                    rsa_key, rsa_pem = Utils.generate_rsa_key(rsa_options['password'])
         | 
| 26 | 
            +
                    rsa_path = File.expand_path(rsa_options['path'], @config_path)
         | 
| 27 | 
            +
                    File.open(rsa_path, 'w') { |f| f.write(rsa_pem) }
         | 
| 28 | 
            +
                    @options['rsa'] = rsa_path
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    # AES
         | 
| 31 | 
            +
                    aes_data = {
         | 
| 32 | 
            +
                      'key' => Utils.generate_aes_key,
         | 
| 33 | 
            +
                      'iv'  => Utils.generate_aes_key
         | 
| 34 | 
            +
                    }
         | 
| 35 | 
            +
                    dump = Marshal.dump(aes_data)
         | 
| 36 | 
            +
                    aes_path = File.expand_path('aes', @config_path)
         | 
| 37 | 
            +
                    File.open(aes_path, 'w') { |f| f.write(rsa_key.public_encrypt(dump)) }
         | 
| 38 | 
            +
                    @options['aes'] = aes_path
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    # Store
         | 
| 41 | 
            +
                    store_options = @ui.store_setup
         | 
| 42 | 
            +
                    if store_options['type'] == 'filesystem'
         | 
| 43 | 
            +
                      store_options['path'] = File.expand_path(store_options['path'], @config_path)
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                    @options['store'] = store_options
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    # Write out config
         | 
| 48 | 
            +
                    File.open(File.expand_path('config', @config_path), 'w') { |f| f.print(@options.to_yaml) }
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                  store = Store[@options['store']['type']].new(@options['store'].reject { |k, _| k == 'type' })
         | 
| 51 | 
            +
                  @database = Database.new(aes_data['key'], aes_data['iv'], store)
         | 
| 52 | 
            +
                  @ui.database = @database
         | 
| 19 53 | 
             
                  main_loop
         | 
| 20 54 | 
             
                end
         | 
| 21 55 |  | 
    
        data/lib/keyrack/ui/console.rb
    CHANGED
    
    | @@ -38,7 +38,7 @@ module Keyrack | |
| 38 38 | 
             
                        :quit
         | 
| 39 39 | 
             
                      end
         | 
| 40 40 | 
             
                    else
         | 
| 41 | 
            -
                       | 
| 41 | 
            +
                      Copier(entries[result.to_i - 1][:password])
         | 
| 42 42 | 
             
                      @highline.say("The password has been copied to your clipboard.")
         | 
| 43 43 | 
             
                      nil
         | 
| 44 44 | 
             
                    end
         | 
| @@ -69,6 +69,40 @@ module Keyrack | |
| 69 69 | 
             
                    end
         | 
| 70 70 | 
             
                    result
         | 
| 71 71 | 
             
                  end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  def display_first_time_notice
         | 
| 74 | 
            +
                    @highline.say("This looks like your first time using Keyrack.  I'll need to ask you a few questions first.")
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  def rsa_setup
         | 
| 78 | 
            +
                    password = confirmation = nil
         | 
| 79 | 
            +
                    loop do
         | 
| 80 | 
            +
                      password = @highline.ask("New passphrase: ") { |q| q.echo = false }
         | 
| 81 | 
            +
                      confirmation = @highline.ask("Confirm passphrase: ") { |q| q.echo = false }
         | 
| 82 | 
            +
                      break if password == confirmation
         | 
| 83 | 
            +
                      @highline.say("Passphrases didn't match.")
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
                    { 'password' => password, 'path' => 'rsa' }
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  def store_setup
         | 
| 89 | 
            +
                    result = {}
         | 
| 90 | 
            +
                    result['type'] = @highline.choose do |menu|
         | 
| 91 | 
            +
                      menu.header = "Choose storage type:"
         | 
| 92 | 
            +
                      menu.choices("filesystem", "ssh")
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                    case result['type']
         | 
| 96 | 
            +
                    when 'filesystem'
         | 
| 97 | 
            +
                      result['path'] = 'database'
         | 
| 98 | 
            +
                    when 'ssh'
         | 
| 99 | 
            +
                      result['host'] = @highline.ask("Host: ")
         | 
| 100 | 
            +
                      result['user'] = @highline.ask("User: ")
         | 
| 101 | 
            +
                      result['path'] = @highline.ask("Remote path: ")
         | 
| 102 | 
            +
                    end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                    result
         | 
| 105 | 
            +
                  end
         | 
| 72 106 | 
             
                end
         | 
| 73 107 | 
             
              end
         | 
| 74 108 | 
             
            end
         | 
    
        data/lib/keyrack/utils.rb
    CHANGED
    
    | @@ -7,5 +7,23 @@ module Keyrack | |
| 7 7 | 
             
                  end
         | 
| 8 8 | 
             
                  result
         | 
| 9 9 | 
             
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def self.generate_rsa_key(password)
         | 
| 12 | 
            +
                  rsa = OpenSSL::PKey::RSA.new(4096)
         | 
| 13 | 
            +
                  cipher = OpenSSL::Cipher::Cipher.new('des3')
         | 
| 14 | 
            +
                  [rsa, rsa.to_pem(cipher, password)]
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def self.generate_aes_key
         | 
| 18 | 
            +
                  SecureRandom.base64(128)[0..127]
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def self.open_rsa_key(path, password)
         | 
| 22 | 
            +
                  OpenSSL::PKey::RSA.new(File.read(path), password)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def self.open_aes_data(path, rsa_key)
         | 
| 26 | 
            +
                  Marshal.load(rsa_key.private_decrypt(File.read(path)))
         | 
| 27 | 
            +
                end
         | 
| 10 28 | 
             
              end
         | 
| 11 29 | 
             
            end
         | 
    
        data/lib/keyrack.rb
    CHANGED
    
    
    
        data/test/fixtures/aes
    ADDED
    
    | Binary file | 
    
        data/test/fixtures/id_rsa
    CHANGED
    
    | @@ -1,30 +1,54 @@ | |
| 1 1 | 
             
            -----BEGIN RSA PRIVATE KEY-----
         | 
| 2 2 | 
             
            Proc-Type: 4,ENCRYPTED
         | 
| 3 | 
            -
            DEK-Info:  | 
| 3 | 
            +
            DEK-Info: DES-EDE3-CBC,030F0E3D8BD7BF1A
         | 
| 4 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 | 
            -
             | 
| 5 | 
            +
            8s6OIKXWfXq6xrPRrJ3m2qU/Xd+N35Y9cCjr+nqnESWGBrT7ZLLIiS4xEMy2WwHB
         | 
| 6 | 
            +
            N9kj0OekQxDz0yfCP/+CyCMvtFpJGwbqgOCJWXyDsmaQDVG3Z7MRehoGA2s9Zkab
         | 
| 7 | 
            +
            2h4F6YE7iE3oVY/2Z86SBhk4KITyGi0Jb8/KHvoig+iF6rqAPos0I5Y3M1vVa9DJ
         | 
| 8 | 
            +
            lFdyoTbFzx+mzbEXIefGOdcXinkfAoG7MchPiEypunrpEgYPddsON2b/dQfIBmEy
         | 
| 9 | 
            +
            3+gN9FnbWH3LNTOShzl4l4v2aBfxXgg5bHPnKkJiUNRDlY6ozyQYfxxoOs4uL8mI
         | 
| 10 | 
            +
            WnfmVGezxkD3YWHB+xwevLaIjChowSNP/q7sSHVOD/bVadmhd30I7CnhFITKi0Wp
         | 
| 11 | 
            +
            vr0qbGpSrsWm7zCtSWcK0fFMGKea2UoH4lrfqJv95mOhZ+pmok35CxGUbG0S53rm
         | 
| 12 | 
            +
            A4ggoIr3cYvRMPkp/Q9dZQxE6FHftXO9C+IvJc1CmtUJl+LR7aOtQy4pgPyRV83x
         | 
| 13 | 
            +
            yfw2SrJ9ls0RZKm4lQUqlwDrru20hf8YiyeLPGlpnRpVVGkdNfhR3Q/e1Tl4qr/3
         | 
| 14 | 
            +
            ITJ1uxopaQZtggR+9wlYG9oS5htJ9VZo+Jh8Oys5c0ZAPlzHQMuU3TQVeLuXIAQ6
         | 
| 15 | 
            +
            zgLyThmKGgG3iBKG2FUZIpGX3WGIFgKwuwAQJXs0fthF5JJAuBXY5Jbvu3y1AQDC
         | 
| 16 | 
            +
            rC9XgYr9PfL8o4zIXxxRvxUny2YO+XaKjgiOV9kKV5reUNJQW95sCvuZZyFh3STW
         | 
| 17 | 
            +
            tywsdQ7ZzsHLJ0sun0Dj4GjbIfMb9l95x1baMIAcXqbqmS9c6Hd53gM2e+8d0Wja
         | 
| 18 | 
            +
            tBb9f2rddcmoyMNB/MKTnCm1L7GjrHtQJXClCKlpyDgZrUbdHcRhq4UY8diVVdam
         | 
| 19 | 
            +
            ZhIVRdMIYr70BUhpTH3gqBLQlNX0D6DItCNdI+cr9p2BUqM87xTeQM84HfKOyZZO
         | 
| 20 | 
            +
            ZqgwZ5jhxjgyrQYADeBD6RqH7fcnitowksRGxZdnZQdm+U+SfQbfmA0zYCh+jgz3
         | 
| 21 | 
            +
            fMVQ+9sjnGZzl0/Wn8Kg8K2jA33N96ou7X89wyiSLMvAW6G+/CaxQMEgTDy+mxES
         | 
| 22 | 
            +
            BLv7h+LdGzOtRE8556hBmNs5ku8AoMmP5irvEkGJIjtB+JcmeIi+AD0hBSq7O4Gp
         | 
| 23 | 
            +
            5OoyXEYp+cYLTQtyLEQ6/CnSEvjgzXRG2xKY9wxspXaJn/q7QwadkgltvjtUo0Iu
         | 
| 24 | 
            +
            OXV6MJGQYkVkm5xf3tmvuQYQNV3NK/nQpDixhMJye19Kjr7jelRpcr+Y5VXelRP5
         | 
| 25 | 
            +
            5CiqpqW0DtM1YWQBUr7p3ckft1DCYB48ARcEqmrgV7KASawZAh7Lun/SkiJ6SYl0
         | 
| 26 | 
            +
            3Q4QVhQbOyx1S04UPRWzEqIBquHqMFzEjnzkQRFAxvnWJx3mnQudAi1yKtKZDpLL
         | 
| 27 | 
            +
            3kuMXJ90hg+/VoEWehEG+VZXxPOgFf624Wv9SnEv2vtxZv4g8B+Ny9SXl/CzDiA0
         | 
| 28 | 
            +
            GsbA9tOzJc9MmYCts19QQ85zsp9B1eJO7oxh5OETOe2FkivU/HqMOp0A3B3rEjY+
         | 
| 29 | 
            +
            CS0g15bqEmCyFxvbBkR8aUZ6c5fHvNngvxYbkGVLyHpgIHmszGj1+gz/8DlEXldM
         | 
| 30 | 
            +
            EE5gY6kZ/iH8ikTjhPCX3Cd8HybjOh1JQogTbOeOMGhkKtEEs+XyFZYP6OrC2Cuf
         | 
| 31 | 
            +
            Yk2osm4J+g+mPXFjQU/Ie9f6Q8Bv0tShFHEh17N4/IJRQPCRhiIMfq/PaBp7Rt3m
         | 
| 32 | 
            +
            RT3fmPl/g4WywBVfC8jv4ES+HA2xWC3cds8rOeh3mgmaimNRmPzpHVe/vyxSUqUh
         | 
| 33 | 
            +
            PC8BXJ+wN+jNg718MYNneah1cC9hsrCMa3wU9dv59LGHsC0rNrbCqmmGMFZVKldV
         | 
| 34 | 
            +
            4HbG77K6QyLjk8bNsVUtwV+0hClBAWHi6K5dAY7IOCdGcVgrZ+drFl1sDyM4vkZ+
         | 
| 35 | 
            +
            kkBnNLrvczR3Mqh+Q901Ha1aepTyHbFN0EMK965Ni8PWw41cYXvgKzRDFJi1iuvx
         | 
| 36 | 
            +
            hr3qu2icCOeVCLE/Vnk0w0t6pxr+1V0sxYx3weBXHDO3NS6EEFKdX8zoGZI17MSF
         | 
| 37 | 
            +
            QVYq1Oko1LU44VvXfw18yS/dsu7nxC106uL9EPOjaN+wf0+V/i9s3AWnPCYftwA8
         | 
| 38 | 
            +
            9l2EBuhElh8drZ2Kb0aL1i0VzB/1zYwDbNZnVhhRF2CAcXTmnpD6RXfgLUnnUVGa
         | 
| 39 | 
            +
            nCPZ8/ks84rXjAxWNcLHSCdVWzczHDKz22VJFa+AIrqMxz77r+aEAzMvxHNAbs5v
         | 
| 40 | 
            +
            E8cPNfmir56mOSD0TRrfPqKoCARLcmLd97RGikh3kw1BYVI02BAwQ3jABrW5v/53
         | 
| 41 | 
            +
            fg8fMafNuQl3Xw4LkQ4VoKAwR4mi5JUN5ngeRDfQVmBs1GOVV981dHvrfrGa3lrR
         | 
| 42 | 
            +
            xHTb7iYXU0XJ219yZHNtGzaTs6g+xvbMF9aELkfbIPGkwqMlKp/2OQmQqq171Nez
         | 
| 43 | 
            +
            me/suc8ZzmZsVGVhW3a/uvB7+DLOgnO41AxQVe9DXO8aVjpjwdpXWmPt8sisgMov
         | 
| 44 | 
            +
            uJFTUBsnG6rg2agxzWrQEQpBONkeY3CGk5P3JLIruhXhNCFs4pjff7xKrdppHmBe
         | 
| 45 | 
            +
            ukm5z2jEbvbdt7VQ/UtGeWzUEoiRh2k7EK/EnK7j0NRsKMPe5DPsIncgWdB/krTn
         | 
| 46 | 
            +
            iXzzvHY0iN6W0ZQELuN8wvt5lnnDmak21+Q/vaA04NHWBhznCIggZDl4+MIuYIBo
         | 
| 47 | 
            +
            T+4OkwEIObuKOW06AI8qIaKVX24bmeZ2Fcj4dGMT0SHlXga6S4ouVUeZ1hOCGg9Z
         | 
| 48 | 
            +
            4Nw53gzWaOIW3TntoNoieibjzHigUWghnXEpn1DB+dfGhTxDWpSZKRIkuBNPEVvn
         | 
| 49 | 
            +
            FtXPwvUg0yrMp4JItSQ7PT2GMtar16qBc+0bgQlX53Vb5kW22rIoNq5zDwWcNB8s
         | 
| 50 | 
            +
            0octG6yq6aEVId/3/2rrQi8izkoZXZe8FJgR02o9M4J/4HsJ74xbqnbuNE7FUZiZ
         | 
| 51 | 
            +
            EX+2BKoNEShxujW+zd1NpSNSfGRAYuk0llUMsbRO8ZfNlZWj9fTNmPHshCWIvZ3a
         | 
| 52 | 
            +
            yxTdjoC3SPtAUz+ZDznBnlIiXyU6XcUPEY+FBsN9K2S0Blb8fvc1cxjL1H1r5Pdj
         | 
| 53 | 
            +
            yPJAIxlfePv1TRYzCf2kmdnIOq3yoCWAU6RxGq8TII6KGvKP14OtMHCyonDB2/UZ
         | 
| 30 54 | 
             
            -----END RSA PRIVATE KEY-----
         | 
    
        data/test/helper.rb
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            require 'tempfile'
         | 
| 2 | 
            +
            require 'fileutils'
         | 
| 2 3 | 
             
            require 'rubygems'
         | 
| 3 4 | 
             
            require 'bundler'
         | 
| 4 5 | 
             
            begin
         | 
| @@ -29,7 +30,7 @@ class Test::Unit::TestCase | |
| 29 30 |  | 
| 30 31 | 
             
              def teardown
         | 
| 31 32 | 
             
                if @tmpnames
         | 
| 32 | 
            -
                  @tmpnames.each { |t|  | 
| 33 | 
            +
                  @tmpnames.each { |t| FileUtils.rm_rf(t) }
         | 
| 33 34 | 
             
                end
         | 
| 34 35 | 
             
              end
         | 
| 35 36 | 
             
            end
         | 
| @@ -3,30 +3,28 @@ require 'helper' | |
| 3 3 | 
             
            module Keyrack
         | 
| 4 4 | 
             
              class TestDatabase < Test::Unit::TestCase
         | 
| 5 5 | 
             
                def setup
         | 
| 6 | 
            +
                  @key = "abcdefgh" * 32
         | 
| 7 | 
            +
                  @iv = @key.reverse
         | 
| 8 | 
            +
             | 
| 6 9 | 
             
                  @path = get_tmpname
         | 
| 7 | 
            -
                  @ | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
                    'password' => 'secret'
         | 
| 11 | 
            -
                  })
         | 
| 10 | 
            +
                  @store = Store['filesystem'].new('path' => @path)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  @database = Keyrack::Database.new(@key, @iv, @store)
         | 
| 12 13 | 
             
                  @database.add('Twitter', 'username', 'password')
         | 
| 13 14 | 
             
                  @database.save
         | 
| 14 15 | 
             
                end
         | 
| 15 16 |  | 
| 16 17 | 
             
                def test_encrypts_database
         | 
| 17 | 
            -
                  key = OpenSSL::PKey::RSA.new(File.read(fixture_path('id_rsa')), 'secret')
         | 
| 18 18 | 
             
                  encrypted_data = File.read(@path)
         | 
| 19 | 
            -
                   | 
| 19 | 
            +
                  cipher = OpenSSL::Cipher::Cipher.new("AES-128-CBC")
         | 
| 20 | 
            +
                  cipher.decrypt; cipher.key = @key; cipher.iv = @iv
         | 
| 21 | 
            +
                  marshalled_data = cipher.update(encrypted_data) + cipher.final
         | 
| 20 22 | 
             
                  data = Marshal.load(marshalled_data)
         | 
| 21 23 | 
             
                  assert_equal({'Twitter'=>{:username=>'username',:password=>'password'}}, data)
         | 
| 22 24 | 
             
                end
         | 
| 23 25 |  | 
| 24 26 | 
             
                def test_reading_existing_database
         | 
| 25 | 
            -
                  database = Keyrack::Database.new( | 
| 26 | 
            -
                    'store' => { 'type' => 'filesystem', 'path' => @path },
         | 
| 27 | 
            -
                    'key' => fixture_path('id_rsa'),
         | 
| 28 | 
            -
                    'password' => 'secret'
         | 
| 29 | 
            -
                  })
         | 
| 27 | 
            +
                  database = Keyrack::Database.new(@key, @iv, @store)
         | 
| 30 28 | 
             
                  expected = {:username => 'username', :password => 'password'}
         | 
| 31 29 | 
             
                  assert_equal(expected, database.get('Twitter'))
         | 
| 32 30 | 
             
                end
         | 
| @@ -40,5 +38,15 @@ module Keyrack | |
| 40 38 | 
             
                  @database.add('Foo', 'bar', 'baz')
         | 
| 41 39 | 
             
                  assert @database.dirty?
         | 
| 42 40 | 
             
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def test_large_number_of_entries
         | 
| 43 | 
            +
                  site = "abcdefg"; user = "1234567"; pass = "zyxwvut" * 2
         | 
| 44 | 
            +
                  500.times do |i|
         | 
| 45 | 
            +
                    @database.add(site, user, pass)
         | 
| 46 | 
            +
                    site.next!; user.next!; pass.next!
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                  @database.save
         | 
| 49 | 
            +
                  assert_equal 501, @database.sites.length
         | 
| 50 | 
            +
                end
         | 
| 43 51 | 
             
              end
         | 
| 44 52 | 
             
            end
         | 
    
        data/test/keyrack/test_runner.rb
    CHANGED
    
    | @@ -2,31 +2,98 @@ require 'helper' | |
| 2 2 |  | 
| 3 3 | 
             
            module Keyrack
         | 
| 4 4 | 
             
              class TestRunner < Test::Unit::TestCase
         | 
| 5 | 
            +
                def setup
         | 
| 6 | 
            +
                  @console = stub('console', {
         | 
| 7 | 
            +
                    :get_password => 'secret',
         | 
| 8 | 
            +
                    :database= => nil, :menu => nil,
         | 
| 9 | 
            +
                    :get_new_entry => {:site => "Foo", :username => "bar", :password => "baz"}
         | 
| 10 | 
            +
                  })
         | 
| 11 | 
            +
                  UI::Console.stubs(:new).returns(@console)
         | 
| 12 | 
            +
                  @database = stub('database', { :add => nil })
         | 
| 13 | 
            +
                  Database.stubs(:new).returns(@database)
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 5 16 | 
             
                def test_console
         | 
| 17 | 
            +
                  store_path = 'foo/bar/hey/buddy'
         | 
| 18 | 
            +
                  rsa_path = 'omg/rsa/path'
         | 
| 19 | 
            +
                  aes_path = 'hey/its/some/aes/stuff'
         | 
| 6 20 | 
             
                  config = {
         | 
| 7 | 
            -
                    'store' => { 'type' => 'filesystem', 'path' =>  | 
| 8 | 
            -
                    ' | 
| 21 | 
            +
                    'store' => { 'type' => 'filesystem', 'path' => store_path },
         | 
| 22 | 
            +
                    'rsa' => rsa_path, 'aes' => aes_path
         | 
| 9 23 | 
             
                  }
         | 
| 10 | 
            -
                   | 
| 11 | 
            -
                   | 
| 24 | 
            +
                  keyrack_dir = get_tmpname
         | 
| 25 | 
            +
                  Dir.mkdir(keyrack_dir)
         | 
| 26 | 
            +
                  File.open(File.join(keyrack_dir, "config"), 'w') { |f| f.print(config.to_yaml) }
         | 
| 12 27 |  | 
| 13 | 
            -
                   | 
| 14 | 
            -
                  UI::Console.expects(:new).returns(console)
         | 
| 15 | 
            -
                  database = mock('database')
         | 
| 28 | 
            +
                  UI::Console.expects(:new).returns(@console)
         | 
| 16 29 |  | 
| 17 30 | 
             
                  seq = sequence('ui sequence')
         | 
| 18 | 
            -
                  console.expects(:get_password).returns('secret').in_sequence(seq)
         | 
| 19 | 
            -
                   | 
| 20 | 
            -
                   | 
| 21 | 
            -
                   | 
| 22 | 
            -
                   | 
| 23 | 
            -
                   | 
| 24 | 
            -
                   | 
| 25 | 
            -
                   | 
| 26 | 
            -
                   | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
                   | 
| 31 | 
            +
                  @console.expects(:get_password).returns('secret').in_sequence(seq)
         | 
| 32 | 
            +
                  rsa = mock("rsa key")
         | 
| 33 | 
            +
                  Utils.expects(:open_rsa_key).with(rsa_path, 'secret').returns(rsa).in_sequence(seq)
         | 
| 34 | 
            +
                  aes = {'key' => '12345', 'iv' => '54321'}
         | 
| 35 | 
            +
                  Utils.expects(:open_aes_data).with(aes_path, rsa).returns(aes).in_sequence(seq)
         | 
| 36 | 
            +
                  store = mock('filesystem store')
         | 
| 37 | 
            +
                  Store::Filesystem.expects(:new).with('path' => store_path).returns(store).in_sequence(seq)
         | 
| 38 | 
            +
                  Database.expects(:new).with('12345', '54321', store).returns(@database).in_sequence(seq)
         | 
| 39 | 
            +
                  @console.expects(:database=).with(@database).in_sequence(seq)
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  @console.expects(:menu).returns(:new).in_sequence(seq)
         | 
| 42 | 
            +
                  @console.expects(:get_new_entry).returns({:site => "Foo", :username => "bar", :password => "baz"}).in_sequence(seq)
         | 
| 43 | 
            +
                  @database.expects(:add).with("Foo", "bar", "baz")
         | 
| 44 | 
            +
                  @console.expects(:menu).returns(nil).in_sequence(seq)
         | 
| 45 | 
            +
                  @console.expects(:menu).returns(:save).in_sequence(seq)
         | 
| 46 | 
            +
                  @database.expects(:save).in_sequence(seq)
         | 
| 47 | 
            +
                  @console.expects(:menu).returns(:quit).in_sequence(seq)
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  runner = Runner.new(["-d", keyrack_dir])
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def test_console_first_run
         | 
| 53 | 
            +
                  keyrack_dir = get_tmpname
         | 
| 54 | 
            +
                  seq = sequence('ui sequence')
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  @console.expects(:display_first_time_notice).in_sequence(seq)
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  # RSA generation
         | 
| 59 | 
            +
                  rsa_path = 'id_rsa'
         | 
| 60 | 
            +
                  @console.expects(:rsa_setup).returns('password' => 'secret', 'path' => rsa_path).in_sequence(seq)
         | 
| 61 | 
            +
                  rsa = mock('rsa key')
         | 
| 62 | 
            +
                  Utils.expects(:generate_rsa_key).with('secret').returns([rsa, 'private key']).in_sequence(seq)
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  # AES generation
         | 
| 65 | 
            +
                  Utils.expects(:generate_aes_key).twice.returns('foobar', 'barfoo').in_sequence(seq)
         | 
| 66 | 
            +
                  dump = Marshal.dump('key' => 'foobar', 'iv' => 'barfoo')
         | 
| 67 | 
            +
                  rsa.expects(:public_encrypt).with(dump).returns("encrypted dump")
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  # Store setup
         | 
| 70 | 
            +
                  store_path = 'database'
         | 
| 71 | 
            +
                  @console.expects(:store_setup).returns('type' => 'filesystem', 'path' => store_path).in_sequence(seq)
         | 
| 72 | 
            +
                  store = mock('filesystem store')
         | 
| 73 | 
            +
                  Store::Filesystem.expects(:new).with('path' => store_path).returns(store).in_sequence(seq)
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  Database.expects(:new).with('foobar', 'barfoo', store).returns(@database).in_sequence(seq)
         | 
| 76 | 
            +
                  @console.expects(:database=).with(@database).in_sequence(seq)
         | 
| 77 | 
            +
                  @console.expects(:menu).returns(:quit).in_sequence(seq)
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  runner = Runner.new(["-d", keyrack_dir])
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  assert Dir.exist?(keyrack_dir)
         | 
| 82 | 
            +
                  expected_rsa_file = File.expand_path(rsa_path, keyrack_dir)
         | 
| 83 | 
            +
                  assert File.exist?(expected_rsa_file)
         | 
| 84 | 
            +
                  assert_equal 'private key', File.read(expected_rsa_file)
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                  expected_aes_file = File.expand_path('aes', keyrack_dir)
         | 
| 87 | 
            +
                  assert File.exist?(expected_aes_file)
         | 
| 88 | 
            +
                  assert_equal 'encrypted dump', File.read(expected_aes_file)
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  expected_config_file = File.expand_path('config', keyrack_dir)
         | 
| 91 | 
            +
                  assert File.exist?(expected_config_file)
         | 
| 92 | 
            +
                  expected_config = {
         | 
| 93 | 
            +
                    'rsa' => expected_rsa_file, 'aes' => expected_aes_file,
         | 
| 94 | 
            +
                    'store' => { 'type' => 'filesystem', 'path' => store_path }
         | 
| 95 | 
            +
                  }
         | 
| 96 | 
            +
                  assert_equal expected_config, YAML.load_file(expected_config_file)
         | 
| 30 97 | 
             
                end
         | 
| 31 98 | 
             
              end
         | 
| 32 99 | 
             
            end
         | 
    
        data/test/keyrack/test_utils.rb
    CHANGED
    
    | @@ -6,5 +6,36 @@ module Keyrack | |
| 6 6 | 
             
                  result = Utils.generate_password
         | 
| 7 7 | 
             
                  assert_match result, /^[!-~]{8}$/
         | 
| 8 8 | 
             
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def test_generate_rsa_key
         | 
| 11 | 
            +
                  rsa = mock('rsa')
         | 
| 12 | 
            +
                  OpenSSL::PKey::RSA.expects(:new).with(4096).returns(rsa)
         | 
| 13 | 
            +
                  cipher = mock('cipher')
         | 
| 14 | 
            +
                  OpenSSL::Cipher::Cipher.expects(:new).with('des3').returns(cipher)
         | 
| 15 | 
            +
                  rsa.expects(:to_pem).with(cipher, 'secret').returns('private key')
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  assert_equal([rsa, 'private key'], Utils.generate_rsa_key('secret'))
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def test_generate_aes_key
         | 
| 21 | 
            +
                  SecureRandom.expects(:base64).with(128).returns("x" * 172)
         | 
| 22 | 
            +
                  result = Utils.generate_aes_key
         | 
| 23 | 
            +
                  assert_equal 128, result.length
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def test_open_rsa_key
         | 
| 27 | 
            +
                  rsa_path = fixture_path('id_rsa')
         | 
| 28 | 
            +
                  rsa = mock('rsa')
         | 
| 29 | 
            +
                  OpenSSL::PKey::RSA.expects(:new).with(File.read(rsa_path), 'secret').returns(rsa)
         | 
| 30 | 
            +
                  assert_equal(rsa, Utils.open_rsa_key(rsa_path, 'secret'))
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def test_open_aes_data
         | 
| 34 | 
            +
                  aes_path = fixture_path('aes')
         | 
| 35 | 
            +
                  aes = {'key' => '12345', 'iv' => '54321'}
         | 
| 36 | 
            +
                  rsa = mock('rsa')
         | 
| 37 | 
            +
                  rsa.expects(:private_decrypt).with(File.read(aes_path)).returns(Marshal.dump(aes))
         | 
| 38 | 
            +
                  assert_equal(aes, Utils.open_aes_data(aes_path, rsa))
         | 
| 39 | 
            +
                end
         | 
| 9 40 | 
             
              end
         | 
| 10 41 | 
             
            end
         | 
| @@ -4,14 +4,11 @@ module Keyrack | |
| 4 4 | 
             
              module UI
         | 
| 5 5 | 
             
                class TestConsole < Test::Unit::TestCase
         | 
| 6 6 | 
             
                  def setup
         | 
| 7 | 
            -
                    @ | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
                       | 
| 11 | 
            -
             | 
| 12 | 
            -
                    })
         | 
| 13 | 
            -
                    @database.add('Twitter', 'username', 'password')
         | 
| 14 | 
            -
                    @database.save
         | 
| 7 | 
            +
                    @database = stub('database', :sites => %w{Twitter}, :dirty? => false) do
         | 
| 8 | 
            +
                      stubs(:get).with('Twitter').returns({
         | 
| 9 | 
            +
                        :username => 'username', :password => 'password'
         | 
| 10 | 
            +
                      })
         | 
| 11 | 
            +
                    end
         | 
| 15 12 | 
             
                  end
         | 
| 16 13 |  | 
| 17 14 | 
             
                  def test_select_entry_from_menu
         | 
| @@ -27,7 +24,7 @@ module Keyrack | |
| 27 24 | 
             
                    question = mock('question')
         | 
| 28 25 | 
             
                    question.expects(:in=).with(%w{n q 1})
         | 
| 29 26 | 
             
                    highline.expects(:ask).yields(question).returns('1')
         | 
| 30 | 
            -
                     | 
| 27 | 
            +
                    console.expects(:Copier).with('password')
         | 
| 31 28 | 
             
                    highline.expects(:say).with("The password has been copied to your clipboard.")
         | 
| 32 29 | 
             
                    assert_nil console.menu
         | 
| 33 30 | 
             
                  end
         | 
| @@ -145,6 +142,62 @@ module Keyrack | |
| 145 142 | 
             
                    highline.expects(:agree).with("Generated bluefoobar.  Sound good? [yn] ").returns(true).in_sequence(seq)
         | 
| 146 143 | 
             
                    assert_equal({:site => "Foo", :username => "bar", :password => "foobar"}, console.get_new_entry)
         | 
| 147 144 | 
             
                  end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  def test_display_first_time_notice
         | 
| 147 | 
            +
                    highline = mock('highline')
         | 
| 148 | 
            +
                    HighLine.expects(:new).returns(highline)
         | 
| 149 | 
            +
                    console = Console.new
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    highline.expects(:say).with("This looks like your first time using Keyrack.  I'll need to ask you a few questions first.")
         | 
| 152 | 
            +
                    console.display_first_time_notice
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                  def test_rsa_setup
         | 
| 156 | 
            +
                    highline = mock('highline')
         | 
| 157 | 
            +
                    HighLine.expects(:new).returns(highline)
         | 
| 158 | 
            +
                    console = Console.new
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                    seq = sequence("rsa setup")
         | 
| 161 | 
            +
                    highline.expects(:ask).with("New passphrase: ").yields(mock{expects(:echo=).with(false)}).returns('huge').in_sequence(seq)
         | 
| 162 | 
            +
                    highline.expects(:ask).with("Confirm passphrase: ").yields(mock{expects(:echo=).with(false)}).returns('small').in_sequence(seq)
         | 
| 163 | 
            +
                    highline.expects(:say).with("Passphrases didn't match.").in_sequence(seq)
         | 
| 164 | 
            +
                    highline.expects(:ask).with("New passphrase: ").yields(mock{expects(:echo=).with(false)}).returns('huge').in_sequence(seq)
         | 
| 165 | 
            +
                    highline.expects(:ask).with("Confirm passphrase: ").yields(mock{expects(:echo=).with(false)}).returns('huge').in_sequence(seq)
         | 
| 166 | 
            +
                    expected = {'password' => 'huge', 'path' => 'rsa'}
         | 
| 167 | 
            +
                    assert_equal expected, console.rsa_setup
         | 
| 168 | 
            +
                  end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                  def test_store_setup_for_filesystem
         | 
| 171 | 
            +
                    highline = mock('highline')
         | 
| 172 | 
            +
                    HighLine.expects(:new).returns(highline)
         | 
| 173 | 
            +
                    console = Console.new
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                    highline.expects(:choose).yields(mock {
         | 
| 176 | 
            +
                      expects(:header=).with("Choose storage type:")
         | 
| 177 | 
            +
                      expects(:choices).with("filesystem", "ssh")
         | 
| 178 | 
            +
                    }).returns("filesystem")
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                    expected = {'type' => 'filesystem', 'path' => 'database'}
         | 
| 181 | 
            +
                    assert_equal expected, console.store_setup
         | 
| 182 | 
            +
                  end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                  def test_store_setup_for_ssh
         | 
| 185 | 
            +
                    highline = mock('highline')
         | 
| 186 | 
            +
                    HighLine.expects(:new).returns(highline)
         | 
| 187 | 
            +
                    console = Console.new
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                    seq = sequence("store setup")
         | 
| 190 | 
            +
                    highline.expects(:choose).yields(mock {
         | 
| 191 | 
            +
                      expects(:header=).with("Choose storage type:")
         | 
| 192 | 
            +
                      expects(:choices).with("filesystem", "ssh")
         | 
| 193 | 
            +
                    }).returns("ssh").in_sequence(seq)
         | 
| 194 | 
            +
                    highline.expects(:ask).with("Host: ").returns("example.com").in_sequence(seq)
         | 
| 195 | 
            +
                    highline.expects(:ask).with("User: ").returns("dudeguy").in_sequence(seq)
         | 
| 196 | 
            +
                    highline.expects(:ask).with("Remote path: ").returns(".keyrack/database").in_sequence(seq)
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                    expected = {'type' => 'ssh', 'host' => 'example.com', 'user' => 'dudeguy', 'path' => '.keyrack/database'}
         | 
| 199 | 
            +
                    assert_equal expected, console.store_setup
         | 
| 200 | 
            +
                  end
         | 
| 148 201 | 
             
                end
         | 
| 149 202 | 
             
              end
         | 
| 150 203 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version | |
| 4 4 | 
             
              prerelease: false
         | 
| 5 5 | 
             
              segments: 
         | 
| 6 6 | 
             
              - 0
         | 
| 7 | 
            -
              -  | 
| 8 | 
            -
              -  | 
| 9 | 
            -
              version: 0. | 
| 7 | 
            +
              - 2
         | 
| 8 | 
            +
              - 0
         | 
| 9 | 
            +
              version: 0.2.0
         | 
| 10 10 | 
             
            platform: ruby
         | 
| 11 11 | 
             
            authors: 
         | 
| 12 12 | 
             
            - Jeremy Stephens
         | 
| @@ -14,7 +14,7 @@ autorequire: | |
| 14 14 | 
             
            bindir: bin
         | 
| 15 15 | 
             
            cert_chain: []
         | 
| 16 16 |  | 
| 17 | 
            -
            date:  | 
| 17 | 
            +
            date: 2011-01-04 00:00:00 -06:00
         | 
| 18 18 | 
             
            default_executable: keyrack
         | 
| 19 19 | 
             
            dependencies: 
         | 
| 20 20 | 
             
            - !ruby/object:Gem::Dependency 
         | 
| @@ -44,7 +44,7 @@ dependencies: | |
| 44 44 | 
             
              prerelease: false
         | 
| 45 45 | 
             
              version_requirements: *id002
         | 
| 46 46 | 
             
            - !ruby/object:Gem::Dependency 
         | 
| 47 | 
            -
              name:  | 
| 47 | 
            +
              name: viking-copier
         | 
| 48 48 | 
             
              requirement: &id003 !ruby/object:Gem::Requirement 
         | 
| 49 49 | 
             
                none: false
         | 
| 50 50 | 
             
                requirements: 
         | 
| @@ -112,6 +112,19 @@ dependencies: | |
| 112 112 | 
             
              type: :development
         | 
| 113 113 | 
             
              prerelease: false
         | 
| 114 114 | 
             
              version_requirements: *id007
         | 
| 115 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 116 | 
            +
              name: cucumber
         | 
| 117 | 
            +
              requirement: &id008 !ruby/object:Gem::Requirement 
         | 
| 118 | 
            +
                none: false
         | 
| 119 | 
            +
                requirements: 
         | 
| 120 | 
            +
                - - ">="
         | 
| 121 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 122 | 
            +
                    segments: 
         | 
| 123 | 
            +
                    - 0
         | 
| 124 | 
            +
                    version: "0"
         | 
| 125 | 
            +
              type: :development
         | 
| 126 | 
            +
              prerelease: false
         | 
| 127 | 
            +
              version_requirements: *id008
         | 
| 115 128 | 
             
            description: Simple password manager with local/remote database storage and RSA encryption.
         | 
| 116 129 | 
             
            email: viking@pillageandplunder.net
         | 
| 117 130 | 
             
            executables: 
         | 
| @@ -132,6 +145,9 @@ files: | |
| 132 145 | 
             
            - Rakefile
         | 
| 133 146 | 
             
            - VERSION
         | 
| 134 147 | 
             
            - bin/keyrack
         | 
| 148 | 
            +
            - features/console.feature
         | 
| 149 | 
            +
            - features/step_definitions/keyrack_steps.rb
         | 
| 150 | 
            +
            - features/support/env.rb
         | 
| 135 151 | 
             
            - keyrack.gemspec
         | 
| 136 152 | 
             
            - lib/keyrack.rb
         | 
| 137 153 | 
             
            - lib/keyrack/database.rb
         | 
| @@ -142,10 +158,10 @@ files: | |
| 142 158 | 
             
            - lib/keyrack/ui.rb
         | 
| 143 159 | 
             
            - lib/keyrack/ui/console.rb
         | 
| 144 160 | 
             
            - lib/keyrack/utils.rb
         | 
| 161 | 
            +
            - test/fixtures/aes
         | 
| 145 162 | 
             
            - test/fixtures/config.yml
         | 
| 146 163 | 
             
            - test/fixtures/foo.txt
         | 
| 147 164 | 
             
            - test/fixtures/id_rsa
         | 
| 148 | 
            -
            - test/fixtures/id_rsa.pub
         | 
| 149 165 | 
             
            - test/helper.rb
         | 
| 150 166 | 
             
            - test/keyrack/store/test_filesystem.rb
         | 
| 151 167 | 
             
            - test/keyrack/store/test_ssh.rb
         | 
| @@ -168,7 +184,7 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 168 184 | 
             
              requirements: 
         | 
| 169 185 | 
             
              - - ">="
         | 
| 170 186 | 
             
                - !ruby/object:Gem::Version 
         | 
| 171 | 
            -
                  hash: - | 
| 187 | 
            +
                  hash: -415657060610893124
         | 
| 172 188 | 
             
                  segments: 
         | 
| 173 189 | 
             
                  - 0
         | 
| 174 190 | 
             
                  version: "0"
         | 
    
        data/test/fixtures/id_rsa.pub
    DELETED
    
    | @@ -1 +0,0 @@ | |
| 1 | 
            -
            ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5BJJ44xstXL/qsJzIDTr7NTX64g/Nvi2z5fDM05mYQ1+cbAm+s+xI0vBrzY7nU4eVK62h5HOVOh3BB9vGfF3rfycEwj1ZpcDuMAlwQnmnmwLXXwQJV0VQtUgvukBKVX/RwSPJPkGk0nEGvpBmtn8IjtlR+Ido2jeKyKHsUCygvExoVTvJprCGLYxAH7Wo6wtF2idWwmBY6ApFY8cdf0VVBwSsIGjRmnMXf6ggsP/eoYBmR7aD3nGvb4uE1P1XX5XgRAcd7gHbHFe9LVErVoAT734MGMaSQJ6vfBdjBe/BfoAaquGJcr/b+DLc++JpPbaRbDX7IUIlB1vqnkOcLOi5 viking@ip-10-195-17-174
         |