coy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ .rvmrc
2
+ Gemfile.lock
3
+ tmp
4
+ .coy
5
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2013 Joel Helbling
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,102 @@
1
+ [![Build Status](https://travis-ci.org/joelhelbling/coy.png)](https://travis-ci.org/joelhelbling/coy)
2
+
3
+ # Coy
4
+
5
+ /koi/ Adjective: *reluctant to give details, esp. about something regarded as
6
+ sensitive.*
7
+
8
+ A utility for protecting shy data, Coy uses TrueCrypt to set up a vcs-ignored\*,
9
+ encrypted volume within your project project for storing sensitive
10
+ information. This allows access to that sensitive material _while
11
+ you're developing or running your application_ but after you close it,
12
+ the data is inaccessible\*\*.
13
+
14
+ You probably don't want to store a whole project in there; usually the
15
+ sensitive bits are just a few bytes of stuff, such as passwords, personally
16
+ identifying information, etc. Accordingly, Coy's protected directories have
17
+ a 2Mb capacity.
18
+
19
+ \* _Git, Mercurial and SVN (See [Ignorance](http://github.com/joelhelbling/ignorance).)_
20
+
21
+ \*\* _Encrypted with AES and a Whirlpool hash algorithm._
22
+
23
+ ## Installation
24
+
25
+ First, you'll need to [install TrueCrypt](http://www.truecrypt.org/downloads) and ensure
26
+ its command-line utility is visible in your path:
27
+
28
+ $ which truecrypt
29
+
30
+ Now you can add this line to your application's Gemfile:
31
+
32
+ gem 'coy'
33
+
34
+ And then execute:
35
+
36
+ $ bundle
37
+
38
+ Or install it yourself as:
39
+
40
+ $ gem install coy
41
+
42
+
43
+ ## Usage
44
+
45
+ This would create a new protected directory called "secret":
46
+
47
+ $ coy create secret
48
+
49
+ This mounts the newly created TrueCrypt volume:
50
+
51
+ $ coy open secret
52
+
53
+ Now you can slip on in there:
54
+
55
+ $ cd secret/
56
+
57
+ And stash some top-secret tidbits that your program will need:
58
+
59
+ $ echo "---\n - :santas_little_helper: me" > hush-hush.yaml
60
+
61
+ And then, in your ruby code:
62
+
63
+ ```ruby
64
+ File.exists? './secret/hush-hush.yaml' #=> true
65
+ ```
66
+
67
+ Once you're done developing or delivering toys and whatnot, you can
68
+ close up shop:
69
+
70
+ ```
71
+ $ cd ..
72
+ $ coy close secret
73
+ ```
74
+
75
+ And at this point, the `secret/` directory is inaccessible (unmounted).
76
+
77
+ ```ruby
78
+ Dir.exists? './secret/' #=> false
79
+ ```
80
+
81
+ Now your secret identity is protected by AES encryption, a Whirlpool hash,
82
+ your awesome password, and whatever other measures TrueCrypt uses. Dobermans,
83
+ probably.
84
+
85
+ ### Password
86
+
87
+ The `create` and `open` commands require a password. Coy will prompt you,
88
+ and mask the input. On the other hand, if you're safe in the batcave, you
89
+ can include the password as a command-line argument:
90
+
91
+ $ coy create secret --password l33tp@55w0rd
92
+ $ coy open secret -p l33tp@55w0rd
93
+
94
+ ## Contributing
95
+
96
+ 1. Fork it
97
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
98
+ 3. Write tests!
99
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
100
+ 5. Push to the branch (`git push origin my-new-feature`)
101
+ 6. Create new Pull Request
102
+
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+ require 'cucumber/rake/task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.verbose = false
7
+ t.pattern = 'spec/lib/**/*_spec.rb'
8
+ t.rspec_opts = " --format doc"
9
+ end
10
+
11
+ Cucumber::Rake::Task.new(:cukes) do |t|
12
+ t.cucumber_opts = "features --format pretty"
13
+ end
14
+
15
+ task default: :spec
data/bin/coy ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
5
+ require 'main'
6
+ require 'coy'
7
+
8
+ Main {
9
+ description <<-DESC
10
+ Coy uses TrueCrypt (installed separately) to create and manage
11
+ a protected, git-ignored directory within your project.
12
+
13
+ For help with individual operations (create|open|close) type
14
+ `coy <operation> -h`
15
+ DESC
16
+
17
+ mode 'create' do
18
+ argument('name') {
19
+ description "...of the protected directory."
20
+ default "secret"
21
+ }
22
+ option('password=[PASSWORD]', 'p') {
23
+ cast :string
24
+ description "set a password for the new protected directory"
25
+ }
26
+
27
+ def run()
28
+ puts Coy::Operation.new(:create, params.to_options).go
29
+ end
30
+ end
31
+
32
+ mode 'open' do
33
+ argument('name') {
34
+ description "...of the protected directory."
35
+ default "secret"
36
+ }
37
+ option('password=[PASSWORD]', 'p') {
38
+ cast :string
39
+ description "password to unlock the protected directory"
40
+ }
41
+ def run()
42
+ puts Coy::Operation.new(:open, params.to_options).go
43
+ end
44
+ end
45
+
46
+ mode 'close' do
47
+ argument('name') {
48
+ description "...of the protected directory."
49
+ default "secret"
50
+ }
51
+ def run()
52
+ puts Coy::Operation.new(:close, params.to_options).go
53
+ end
54
+ end
55
+
56
+ run { puts params.to_options.inspect }
57
+ }
58
+
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'coy/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "coy"
8
+ gem.version = Coy::VERSION
9
+ gem.authors = ["Joel Helbling"]
10
+ gem.email = ["joel@joelhelbling.com"]
11
+ gem.description = %q{Protects sensitive file artifacts in a project, e.g. a yaml file with passwords in it.}
12
+ gem.summary = %q{Easily create AES-encrypted directories within a project to protect sensitive information. Uses TrueCrypt (required to install) and uses Ignorance to prevent contents of protected directories from being inadvertently being added to a version control repository (Git, Hg, SVN).}
13
+ gem.homepage = "http://github.com/joelhelbling/coy"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'ignorance', '>= 0.0.1'
21
+ gem.add_dependency 'highline', '~> 1.6.15'
22
+ gem.add_dependency 'main', '~> 5.1.1'
23
+
24
+ gem.add_development_dependency 'rspec', '~> 2.12.0'
25
+ gem.add_development_dependency 'fakefs', '~> 0.4.2'
26
+ gem.add_development_dependency 'cucumber', '~> 1.2.1'
27
+ gem.add_development_dependency 'aruba', '~> 0.5.1'
28
+ gem.add_development_dependency 'rake', '~> 10.0.3'
29
+ end
30
+
@@ -0,0 +1,138 @@
1
+ @ignore
2
+ Feature: Check the project's .gitignore file to ensure
3
+ coy assets will not be inadvertently committed/pushed/published.
4
+
5
+ @not-ignored @auto-add @git
6
+ Scenario: no ignore file, but we agree to automatic add (Git)
7
+ Given the current directory is a git repo
8
+ And a file named ".gitignore" should not exist
9
+ And I run `coy create secret --password fuzz` interactively
10
+ When I type "Y"
11
+ And I type "Y"
12
+ Then the output should contain:
13
+ """
14
+ add .coy to this project's .gitignore file automatically?
15
+ """
16
+ And the output should contain:
17
+ """
18
+ add secret to this project's .gitignore file automatically?
19
+ """
20
+ And the output should contain:
21
+ """
22
+ Protected directory "secret" successfully created.
23
+ """
24
+ And the file ".gitignore" should contain ".coy"
25
+ And the file ".gitignore" should contain "secret"
26
+
27
+ @not-ignored @auto-add @hg
28
+ Scenario: no ignore file, but we agree to automatic add (Hg)
29
+ Given I have an appropriate .gitignore file
30
+ Given the current directory is a mercurial repo
31
+ And a file named ".hgignore" should not exist
32
+ And I run `coy create secret --password fuzz` interactively
33
+ When I type "Y"
34
+ And I type "Y"
35
+ Then the output should contain:
36
+ """
37
+ add .coy to this project's .hgignore file automatically?
38
+ """
39
+ And the output should contain:
40
+ """
41
+ add secret to this project's .hgignore file automatically?
42
+ """
43
+ And the output should contain:
44
+ """
45
+ Protected directory "secret" successfully created.
46
+ """
47
+ And the file ".hgignore" should contain ".coy"
48
+ And the file ".hgignore" should contain "secret"
49
+
50
+ @not-ignored @auto-add @svn
51
+ Scenario: no ignore file, but we agree to automatic add (SVN)
52
+ Given I have an appropriate .gitignore file
53
+ Given the current directory is a svn repo
54
+ And a file named ".svnignore" should not exist
55
+ And I run `coy create secret --password fuzz` interactively
56
+ When I type "Y"
57
+ And I type "Y"
58
+ Then the output should contain:
59
+ """
60
+ add .coy to this project's .svnignore file automatically?
61
+ """
62
+ And the output should contain:
63
+ """
64
+ add secret to this project's .svnignore file automatically?
65
+ """
66
+ And the output should contain:
67
+ """
68
+ Protected directory "secret" successfully created.
69
+ """
70
+ And the file ".svnignore" should contain ".coy"
71
+ And the file ".svnignore" should contain "secret"
72
+
73
+ @no-auto-add @git
74
+ Scenario: volume not in ignore file, and we decline auto-add (Git)
75
+ Given we ignore coy with git
76
+ And I run `coy create secret --password fuzz` interactively
77
+ When I type "n"
78
+ Then the output should contain:
79
+ """
80
+ WARNING: please add "secret" to this project's .gitignore file!
81
+ """
82
+
83
+ @no-auto-add @hg
84
+ Scenario: volume not in ignore file, and we decline auto-add (Hg)
85
+ Given I have an appropriate .gitignore file
86
+ And the current directory is a mercurial repo
87
+ And we ignore coy with hg
88
+ And I run `coy create secret --password fuzz` interactively
89
+ When I type "n"
90
+ Then the output should contain:
91
+ """
92
+ WARNING: please add "secret" to this project's .hgignore file!
93
+ """
94
+
95
+ @no-auto-add @svn
96
+ Scenario: volume not in ignore file, and we decline auto-add (Git)
97
+ Given I have an appropriate .gitignore file
98
+ And the current directory is a svn repo
99
+ And we ignore coy with svn
100
+ And I run `coy create secret --password fuzz` interactively
101
+ When I type "n"
102
+ Then the output should contain:
103
+ """
104
+ WARNING: please add "secret" to this project's .svnignore file!
105
+ """
106
+
107
+ @already-ignored @git
108
+ Scenario: ignore file includes both .coy and "secret" (Git)
109
+ Given a .gitignore with:
110
+ """
111
+ .coy
112
+ secret
113
+ """
114
+ When I run `coy create secret -p fuzz`
115
+ Then the output should contain "success"
116
+
117
+ @already-ignored @hg
118
+ Scenario: ignore file includes both .coy and "secret" (Hg)
119
+ Given I have an appropriate .gitignore file
120
+ And the current directory is a mercurial repo
121
+ Given a .hgignore with:
122
+ """
123
+ .coy
124
+ secret
125
+ """
126
+ When I run `coy create secret -p fuzz`
127
+ Then the output should contain "success"
128
+ @already-ignored @svn
129
+ Scenario: ignore file includes both .coy and "secret" (SVN)
130
+ Given I have an appropriate .gitignore file
131
+ And the current directory is a svn repo
132
+ Given a .svnignore with:
133
+ """
134
+ .coy
135
+ secret
136
+ """
137
+ When I run `coy create secret -p fuzz`
138
+ Then the output should contain "success"
@@ -0,0 +1,20 @@
1
+ Given /^I have a protected directory named "(.*?)" with password "(.*?)"$/ do |vol_name, password|
2
+ @password = password
3
+ step "I run `coy create #{vol_name} --password #{password}`"
4
+ end
5
+
6
+ Given /^protected directory "(.*?)" is open$/ do |vol_name|
7
+ step "I run `truecrypt .coy/#{vol_name}.tc --password=#@password #{vol_name}`"
8
+ remember_we_opened vol_name
9
+ step "a directory named \"#{vol_name}\" should exist"
10
+ end
11
+
12
+ Then /^a protected directory named "(.*?)" should exist/ do |vol_name|
13
+ step "a directory named \"#{vol_name}\" should exist"
14
+ remember_we_opened vol_name
15
+ end
16
+
17
+ Then /^let's cleanup "(.*?)"$/ do |vol_name|
18
+ step "I run `truecrypt -d .coy/#{vol_name}.tc`"
19
+ end
20
+
@@ -0,0 +1,17 @@
1
+ Given /I have an appropriate \.gitignore file/ do
2
+ step "a file named \".gitignore\" with:", ".coy\nfoo\nbar\nsecret"
3
+ end
4
+
5
+ Given /^the current directory is a (.*?) repo$/ do |vcs|
6
+ repo_dir = {'git' => '.git', 'mercurial' => '.hg', 'svn' => '.svn'}[vcs.downcase]
7
+ step "a directory named \"#{repo_dir}\""
8
+ end
9
+
10
+ Given /^a ([^ ]+) with:$/ do |ignore_file, string|
11
+ step "a file named \"#{ignore_file}\" with:", string
12
+ end
13
+
14
+ Given /^we ignore coy with (.*?)$/ do |vcs|
15
+ ignore_file = {:git => '.gitignore', :hg => '.hgignore', :svn => '.svnignore'}[vcs.downcase.to_sym]
16
+ step "a file named \"#{ignore_file}\" with:", ".coy\n"
17
+ end
@@ -0,0 +1,21 @@
1
+ module CleanupTruecrypt
2
+ def remember_we_opened(vol_name)
3
+ @opened_tc_volumes ||= []
4
+ @opened_tc_volumes << vol_name
5
+ end
6
+
7
+ def close_all_opened_tc_volumes
8
+ @opened_tc_volumes && @opened_tc_volumes.each do |vol|
9
+ print " # CLEANUP: was truecrypt volume \"#{vol}\" closed? "
10
+ if File.directory? File.join('tmp', 'aruba', vol)
11
+ print "No, shutting it down now..."
12
+ step "let's cleanup \"#{vol}\""
13
+ else
14
+ print "Yes."
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ World CleanupTruecrypt
21
+
@@ -0,0 +1,12 @@
1
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
2
+
3
+ require 'aruba/cucumber'
4
+
5
+ Before do
6
+ @aruba_timeout_seconds = 120
7
+ end
8
+
9
+ After do
10
+ close_all_opened_tc_volumes
11
+ end
12
+
@@ -0,0 +1,41 @@
1
+ @truecrypt
2
+ Feature: Interact with truecrypt volumes
3
+
4
+ Background:
5
+ Given I have an appropriate .gitignore file
6
+
7
+ @create
8
+ Scenario Outline: create a plain 'ole volume with the name provided
9
+ Given a file named ".coy/secret.tc" should not exist
10
+ When I run `coy create secret <password_flag> fuzz`
11
+ Then the output should contain:
12
+ """
13
+ Protected directory "secret" successfully created
14
+ """
15
+ And a directory named ".coy" should exist
16
+ And a file named ".coy/secret.tc" should exist
17
+
18
+ Examples:
19
+ | password_flag |
20
+ | --password |
21
+ | -p |
22
+
23
+ @open
24
+ Scenario Outline: open a protected directory
25
+ Given a directory named "foo" should not exist
26
+ And I have a protected directory named "foo" with password "fuzz"
27
+ When I run `coy open foo <password_flag> fuzz`
28
+ Then a protected directory named "foo" should exist
29
+
30
+ Examples:
31
+ | password_flag |
32
+ | --password |
33
+ | -p |
34
+
35
+ @close
36
+ Scenario: close an opened protected directory
37
+ Given I have a protected directory named "bar" with password "fuzz"
38
+ And protected directory "bar" is open
39
+ When I run `coy close bar`
40
+ Then a directory named "bar" should not exist
41
+
@@ -0,0 +1,2 @@
1
+ require 'coy/version'
2
+ require 'coy/operation'
@@ -0,0 +1,115 @@
1
+ require 'highline'
2
+ require 'ignorance'
3
+ require 'truecrypt'
4
+ require 'coy/random_source'
5
+
6
+ module Coy
7
+ class Operation
8
+
9
+ COY_DIR = '.coy'
10
+ COY_COMMENT = "added by coy"
11
+
12
+ def initialize(action, p={})
13
+
14
+ guard_truecrypt_installed
15
+
16
+ @action = action
17
+ p[:short_name] ||= (p[:name] || p['name'])
18
+ p[:file_name] = format_name(p[:short_name])
19
+ p[:password] ||= p['password']
20
+ @parameters = p
21
+ send("parameters_for_#{@action}".to_sym)
22
+ Ignorance.negotiate COY_DIR, COY_COMMENT
23
+ Ignorance.negotiate @parameters[:short_name], COY_COMMENT
24
+ end
25
+
26
+ def parameters_for_create
27
+ @parameters[:size_in_bytes] ||= 2_000_000
28
+ @parameters[:encryption] ||= 'AES'
29
+ @parameters[:hash] ||= 'Whirlpool'
30
+ @parameters[:keyfiles] ||= '""'
31
+
32
+ # truecrypt bug: Mac OS Extended filesystem
33
+ # doesn't work with the --text interface
34
+ @parameters[:filesystem] = 'FAT'
35
+ end
36
+
37
+ def parameters_for_open
38
+ # no special parameters for open
39
+ end
40
+
41
+ def parameters_for_close
42
+ # no special parameters for close
43
+ end
44
+
45
+ def go
46
+ send @action
47
+ end
48
+
49
+ private
50
+
51
+ def create
52
+ ensure_coy_directory_exists
53
+ guard_password_provided "Please provide a password for protected directory"
54
+ RandomSource.generate("./#{COY_DIR}/#{@parameters[:short_name]}.random") do |random_source|
55
+ @parameters[:random_source] = random_source
56
+ TrueCrypt.create_volume(@parameters) &&
57
+ puts("Protected directory \"#{@parameters[:short_name]}\" successfully created.")
58
+ end
59
+ end
60
+
61
+ def open
62
+ if guard_volume_exists
63
+ guard_password_provided
64
+ TrueCrypt.open(@parameters)
65
+ end
66
+ end
67
+
68
+ def close
69
+ if guard_volume_exists
70
+ TrueCrypt.close(@parameters)
71
+ end
72
+ end
73
+
74
+ def format_name(name='secrets')
75
+ "#{COY_DIR}/#{name.gsub(/\.tc$/,'')}.tc"
76
+ end
77
+
78
+ def guard_truecrypt_installed
79
+ message = <<-ERROR
80
+ Coy requires TrueCrypt, but TrueCrypt is not installed! (Or at least it's not in the path.)
81
+
82
+ You can download TrueCrypt here: http://www.truecrypt.org/downloads
83
+
84
+ ERROR
85
+ raise message unless TrueCrypt.installed?
86
+ end
87
+ def ensure_coy_directory_exists
88
+ unless File.directory?('./.coy')
89
+ puts "Creating .coy subdirectory..."
90
+ Dir.mkdir COY_DIR
91
+ end
92
+ end
93
+
94
+ def guard_volume_exists
95
+ volume_name = @parameters[:short_name]
96
+ File.exists?("#{COY_DIR}/#{volume_name}.tc").tap do |volume_exists|
97
+ unless volume_exists
98
+ warn <<-WARN
99
+ There is no protected directory called "#{volume_name}" here.
100
+
101
+ You can create one by typing `coy create #{volume_name}`
102
+ WARN
103
+ end
104
+ end
105
+ end
106
+
107
+ def guard_password_provided(msg_preamble="Please enter password for protected directory")
108
+ unless @parameters[:password]
109
+ @parameters[:password] = HighLine.new.ask("#{msg_preamble} \"#{@parameters[:short_name]}\": ") {|x| x.echo = "*" }
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+
@@ -0,0 +1,31 @@
1
+ require 'net/http'
2
+
3
+ module Coy
4
+ class RandomSource
5
+
6
+ DEFAULT_FILE = "./random_source"
7
+
8
+ def self.generate(file_name=DEFAULT_FILE)
9
+ if block_given?
10
+ File.open(file_name, 'w') { |fh| fh.write rand_strategy }
11
+ yield file_name
12
+ File.unlink(file_name)
13
+ else
14
+ rand_strategy
15
+ end
16
+ end
17
+
18
+ def self.rand_strategy
19
+ some_random_bytes.inject("") do |accumulator, random_bytes|
20
+ accumulator.tap do |acc|
21
+ acc << random_bytes.crypt(@prev.to_s.rjust(2, random_bytes))
22
+ @prev = random_bytes.hash.to_s
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.some_random_bytes
28
+ (1500..2000).map{|number| rand(number).to_s }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module Coy
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,50 @@
1
+ class TrueCrypt
2
+
3
+ def self.create_volume(p={})
4
+
5
+ volume_name = p[:file_name]
6
+ mount_point = p[:short_name]
7
+ volume_type = p[:volume_type] || 'normal'
8
+ size = p[:size_in_bytes] || 5_000_000 # 5Mb
9
+ encryption = p[:encryption] || 'AES'
10
+ hash = p[:hash] || 'Whirlpool'
11
+ keyfiles = p[:keyfiles] || '""'
12
+ random_source = p[:random_source] || __FILE__
13
+ password = p[:password] || raise("Please provide a password")
14
+ filesystem = p[:filesystem] || 'FAT'
15
+
16
+ command = <<-TC
17
+ truecrypt --text \
18
+ --create #{volume_name} \
19
+ --volume-type=#{volume_type} \
20
+ --size=#{size} \
21
+ --encryption=#{encryption} \
22
+ --hash=#{hash} \
23
+ --filesystem=#{filesystem} \
24
+ --password=#{password} \
25
+ --keyfiles=#{keyfiles} \
26
+ --random-source=#{random_source}
27
+ TC
28
+
29
+ `#{command}`
30
+ end
31
+
32
+ # if no password provided, gui window will pop up
33
+ def self.open(p={})
34
+ file_name = p[:file_name] || raise("You must provide the name of the volume you wish to open")
35
+ short_name = p[:short_name]
36
+ pwd_param = p[:password] && "--password=#{p[:password]}"
37
+ `truecrypt #{file_name} #{pwd_param} #{short_name}`
38
+ end
39
+
40
+ def self.close(p={})
41
+ file_name = p[:file_name] || raise("You must provide the name of the volume you wish to close")
42
+ `truecrypt -d #{file_name}`
43
+ end
44
+
45
+ def self.installed?
46
+ !! `which truecrypt`.chomp.match(/truecrypt$/i)
47
+ end
48
+
49
+ end
50
+
@@ -0,0 +1,32 @@
1
+ require 'stringio'
2
+
3
+ module IOHelpers
4
+
5
+ def output_from
6
+ $stdout = $stderr = fake_io
7
+ yield
8
+ fake_io.string
9
+ ensure
10
+ $stdout = STDOUT; $stderr = STDERR
11
+ end
12
+
13
+ def user_types(input, &block)
14
+ $stdin = StringIO.new input
15
+ output_from &block
16
+ ensure
17
+ $stdin = STDIN
18
+ end
19
+
20
+ def stdout_from(&block)
21
+ output_from &block
22
+ end
23
+
24
+ def stderr_from(&block)
25
+ output_from &block
26
+ end
27
+
28
+ def fake_io
29
+ @fake_io ||= StringIO.new
30
+ end
31
+
32
+ end
@@ -0,0 +1,201 @@
1
+ require 'spec_helper'
2
+ require 'coy/operation'
3
+
4
+ module Coy
5
+ describe Operation do
6
+ before do
7
+ Ignorance.stub(:negotiate)
8
+ TrueCrypt.stub(:create_volume)
9
+ TrueCrypt.stub(:open)
10
+ TrueCrypt.stub(:close)
11
+ TrueCrypt.stub(:installed?).and_return(true)
12
+ end
13
+
14
+ let(:volume) { 'foo' }
15
+ subject { Operation.new coy_action, coy_params }
16
+
17
+ context "when TrueCrypt is not installed" do
18
+ let(:coy_action) { :doesnt_matter }
19
+ let(:coy_params) { { name: volume } }
20
+
21
+ before { TrueCrypt.stub(:installed?).and_return(false) }
22
+
23
+ specify do
24
+ expect { subject }.to raise_error /truecrypt.*?not installed/i
25
+ end
26
+ end
27
+
28
+ describe ":create" do
29
+
30
+ context "required params only" do
31
+ let(:coy_action) { :create }
32
+
33
+ let(:coy_params) do
34
+ {
35
+ name: volume,
36
+ password: 'b@r'
37
+ }
38
+ end
39
+
40
+ let(:tc_params) do
41
+ {
42
+ name: volume,
43
+ short_name: volume,
44
+ file_name: ".coy/#{volume}.tc",
45
+ size_in_bytes: 2_000_000,
46
+ encryption: 'AES',
47
+ hash: 'Whirlpool',
48
+ filesystem: 'FAT',
49
+ password: 'b@r',
50
+ keyfiles: '""',
51
+ random_source: "./.coy/#{volume}.random"
52
+ }
53
+ end
54
+
55
+ it "creates a volume" do
56
+ TrueCrypt.should_receive(:create_volume).with(tc_params)
57
+ subject.go
58
+ end
59
+
60
+ it "negotiates adding the coy directory to ignore file" do
61
+ Ignorance.should_receive(:negotiate).with('.coy', Operation::COY_COMMENT)
62
+ subject.go
63
+ end
64
+
65
+ it "negotiates adding volume to ignore files" do
66
+ Ignorance.should_receive(:negotiate).with(volume, Operation::COY_COMMENT)
67
+ subject.go
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ describe ":open", :fakefs do
74
+ let(:coy_action) { :open }
75
+
76
+ let(:coy_params) do
77
+ {
78
+ name: 'foo',
79
+ password: 'b@r'
80
+ }
81
+ end
82
+
83
+ let(:tc_params) do
84
+ {
85
+ name: 'foo',
86
+ short_name: 'foo',
87
+ file_name: '.coy/foo.tc',
88
+ password: 'b@r'
89
+ }
90
+ end
91
+
92
+ before do
93
+ Dir.mkdir '.coy'
94
+ File.open(".coy/#{volume}.tc", 'w') {|fh| fh.write "whatnot" }
95
+ end
96
+
97
+ it "opens a volume" do
98
+ TrueCrypt.should_receive(:open).with(tc_params)
99
+ subject.go
100
+ end
101
+
102
+ it "negotiates adding the coy directory to ignore file" do
103
+ Ignorance.should_receive(:negotiate).with('.coy', Operation::COY_COMMENT)
104
+ subject.go
105
+ end
106
+
107
+ it "negotiates adding volume to ignore files" do
108
+ Ignorance.should_receive(:negotiate).with(volume, Operation::COY_COMMENT)
109
+ subject.go
110
+ end
111
+ end
112
+
113
+ describe ":close", :fakefs do
114
+ let(:coy_action) { :close }
115
+
116
+ let(:coy_params) do
117
+ {
118
+ name: 'foo'
119
+ }
120
+ end
121
+
122
+ let(:tc_params) do
123
+ {
124
+ name: 'foo',
125
+ short_name: 'foo',
126
+ file_name: '.coy/foo.tc',
127
+ password: nil
128
+ }
129
+ end
130
+
131
+ before do
132
+ Dir.mkdir '.coy'
133
+ File.open(".coy/#{volume}.tc", 'w') {|fh| fh.write "whatnot" }
134
+ end
135
+
136
+ it "closes a volume" do
137
+ TrueCrypt.should_receive(:close).with(tc_params)
138
+ subject.go
139
+ end
140
+
141
+ it "negotiates adding the coy directory to ignore file" do
142
+ Ignorance.should_receive(:negotiate).with('.coy', Operation::COY_COMMENT)
143
+ subject.go
144
+ end
145
+
146
+ it "negotiates adding volume to ignore files" do
147
+ Ignorance.should_receive(:negotiate).with(volume, Operation::COY_COMMENT)
148
+ subject.go
149
+ end
150
+ end
151
+
152
+ describe "guarding against non-existant volume", :fakefs, :capture_io do
153
+ context "volume doesn't exist" do
154
+ let(:volume_name) { 'foo' }
155
+ let(:coy_params) { { name: volume_name } }
156
+
157
+ describe ":open" do
158
+ let(:coy_action) { :open }
159
+
160
+ it "advises user to create volume" do
161
+ expect( output_from { subject.go } ).to match /create.*?#{volume_name}/i
162
+ end
163
+ end
164
+
165
+ describe ":close" do
166
+ let(:coy_action) { :close }
167
+
168
+ it "advises user to create volume" do
169
+ expect( output_from { subject.go } ).to match /create.*?#{volume_name}/i
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ context "when user omits password in command-line", :fakefs, :capture_io do
176
+ let(:coy_params) { { name: volume } }
177
+
178
+ before do
179
+ Dir.mkdir '.coy'
180
+ File.open(".coy/#{volume}.tc", 'w') { |fh| fh.write "whatnot" }
181
+ end
182
+
183
+ describe ":create" do
184
+ let(:coy_action) { :create }
185
+
186
+ it "asks user to provide a password" do
187
+ expect( user_types("p@55w0rd") { subject.go } ).to match /provide.*?password.*?#{volume}/i
188
+ end
189
+ end
190
+
191
+ describe ":open" do
192
+ let(:coy_action) { :open }
193
+
194
+ it "prompts user for volume password" do
195
+ expect( user_types("p@55w0rd") { subject.go } ).to match /enter.*?password.*?#{volume}/i
196
+ end
197
+ end
198
+ end
199
+
200
+ end
201
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ require 'coy/random_source'
3
+
4
+ module Coy
5
+ describe RandomSource do
6
+ subject { described_class }
7
+
8
+ its(:generate) { should_not be_empty }
9
+
10
+ specify "each run should be different" do
11
+ subject.generate.should_not == subject.generate
12
+ end
13
+
14
+ describe "::generate" do
15
+ context "with a code block", :fakefs do
16
+ let(:file_name) { './foo.random' }
17
+ context "but no file name" do
18
+ it "provides a default file name" do
19
+ expect { |b| subject.generate(&b) }.to yield_with_args(Coy::RandomSource::DEFAULT_FILE)
20
+ end
21
+ end
22
+ context "with file name provided" do
23
+ it "uses the provided file name" do
24
+ expect { |b| subject.generate(file_name, &b) }.to yield_with_args(file_name)
25
+ end
26
+ end
27
+
28
+ describe "generating random source file" do
29
+ it "writes the file to the hard drive" do
30
+ subject.generate(file_name) do |f_name|
31
+ File.new(f_name).should exist
32
+ end
33
+ end
34
+
35
+ it "deletes the file after yielding to the block" do
36
+ subject.generate(file_name) do |f_name|
37
+ File.new(f_name).should exist
38
+ end
39
+ File.exists?(file_name).should be_false
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,10 @@
1
+ require 'fakefs/spec_helpers'
2
+ require File.join(File.dirname(__FILE__), 'io_helpers')
3
+
4
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
5
+
6
+ RSpec.configure do |cfg|
7
+ cfg.treat_symbols_as_metadata_keys_with_true_values = true
8
+ cfg.include FakeFS::SpecHelpers, fakefs: true
9
+ cfg.include IOHelpers, capture_io: true
10
+ end
metadata ADDED
@@ -0,0 +1,216 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: coy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Joel Helbling
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ignorance
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.0.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.0.1
30
+ - !ruby/object:Gem::Dependency
31
+ name: highline
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.6.15
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.6.15
46
+ - !ruby/object:Gem::Dependency
47
+ name: main
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 5.1.1
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 5.1.1
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 2.12.0
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 2.12.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: fakefs
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.4.2
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.4.2
94
+ - !ruby/object:Gem::Dependency
95
+ name: cucumber
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 1.2.1
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 1.2.1
110
+ - !ruby/object:Gem::Dependency
111
+ name: aruba
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 0.5.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 0.5.1
126
+ - !ruby/object:Gem::Dependency
127
+ name: rake
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: 10.0.3
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: 10.0.3
142
+ description: Protects sensitive file artifacts in a project, e.g. a yaml file with
143
+ passwords in it.
144
+ email:
145
+ - joel@joelhelbling.com
146
+ executables:
147
+ - coy
148
+ extensions: []
149
+ extra_rdoc_files: []
150
+ files:
151
+ - .gitignore
152
+ - Gemfile
153
+ - LICENSE.txt
154
+ - README.md
155
+ - Rakefile
156
+ - bin/coy
157
+ - coy.gemspec
158
+ - features/ignore_file.feature
159
+ - features/step_definitions/coy_steps.rb
160
+ - features/step_definitions/ignore_steps.rb
161
+ - features/support/cleanup_truecrypt.rb
162
+ - features/support/env.rb
163
+ - features/truecrypt.feature
164
+ - lib/coy.rb
165
+ - lib/coy/operation.rb
166
+ - lib/coy/random_source.rb
167
+ - lib/coy/version.rb
168
+ - lib/truecrypt.rb
169
+ - spec/io_helpers.rb
170
+ - spec/lib/coy/operation_spec.rb
171
+ - spec/lib/coy/random_source_spec.rb
172
+ - spec/spec_helper.rb
173
+ homepage: http://github.com/joelhelbling/coy
174
+ licenses: []
175
+ post_install_message:
176
+ rdoc_options: []
177
+ require_paths:
178
+ - lib
179
+ required_ruby_version: !ruby/object:Gem::Requirement
180
+ none: false
181
+ requirements:
182
+ - - ! '>='
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ segments:
186
+ - 0
187
+ hash: 1409079959630218355
188
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
+ none: false
190
+ requirements:
191
+ - - ! '>='
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ segments:
195
+ - 0
196
+ hash: 1409079959630218355
197
+ requirements: []
198
+ rubyforge_project:
199
+ rubygems_version: 1.8.24
200
+ signing_key:
201
+ specification_version: 3
202
+ summary: Easily create AES-encrypted directories within a project to protect sensitive
203
+ information. Uses TrueCrypt (required to install) and uses Ignorance to prevent
204
+ contents of protected directories from being inadvertently being added to a version
205
+ control repository (Git, Hg, SVN).
206
+ test_files:
207
+ - features/ignore_file.feature
208
+ - features/step_definitions/coy_steps.rb
209
+ - features/step_definitions/ignore_steps.rb
210
+ - features/support/cleanup_truecrypt.rb
211
+ - features/support/env.rb
212
+ - features/truecrypt.feature
213
+ - spec/io_helpers.rb
214
+ - spec/lib/coy/operation_spec.rb
215
+ - spec/lib/coy/random_source_spec.rb
216
+ - spec/spec_helper.rb