├── .env
├── .gitignore
├── .rspec
├── .travis.yml
├── CONTRIBUTING.md
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── README.md
├── Rakefile
├── app
├── assets
│ ├── images
│ │ ├── emoji
│ │ │ ├── warning.png
│ │ │ └── white_check_mark.png
│ │ └── mockup
│ │ │ ├── grid.png
│ │ │ ├── home.png
│ │ │ ├── person.png
│ │ │ ├── retina_wood.png
│ │ │ └── web.png
│ ├── javascripts
│ │ ├── application.coffee
│ │ ├── collections
│ │ │ └── item.coffee
│ │ ├── ext
│ │ │ ├── backbone.coffee
│ │ │ └── backbone.layoutmanager.coffee
│ │ ├── mockup.coffee
│ │ ├── models.coffee
│ │ ├── models
│ │ │ ├── item.coffee
│ │ │ ├── item_key.coffee
│ │ │ ├── keypair.coffee
│ │ │ ├── keypair_authenticator.coffee
│ │ │ └── keypair_generator.coffee
│ │ ├── routers
│ │ │ ├── item_router.coffee
│ │ │ └── key_router.coffee
│ │ ├── templates
│ │ │ ├── item
│ │ │ │ ├── create.mustache
│ │ │ │ ├── edit.mustache
│ │ │ │ ├── list.mustache
│ │ │ │ ├── list_item.mustache
│ │ │ │ └── show.mustache
│ │ │ ├── keypair
│ │ │ │ ├── create.mustache
│ │ │ │ ├── download.mustache
│ │ │ │ ├── load.mustache
│ │ │ │ └── unlock.mustache
│ │ │ ├── main.mustache
│ │ │ └── setup.mustache
│ │ ├── ui.coffee
│ │ └── views
│ │ │ ├── item
│ │ │ ├── _form.coffee
│ │ │ ├── create.coffee
│ │ │ ├── edit.coffee
│ │ │ ├── list.coffee
│ │ │ ├── list_item.coffee
│ │ │ └── show.coffee.erb
│ │ │ └── keypair
│ │ │ ├── create.coffee
│ │ │ ├── download.coffee
│ │ │ ├── load.coffee
│ │ │ └── unlock.coffee
│ └── stylesheets
│ │ ├── application.css
│ │ └── mockup.scss
├── controllers
│ ├── application_controller.rb
│ ├── auth
│ │ └── rsa_controller.rb
│ ├── dashboard_controller.rb
│ └── items_controller.rb
├── mailers
│ └── .gitkeep
├── models
│ ├── item.rb
│ ├── rsa_challenge.rb
│ ├── share.rb
│ └── user.rb
├── presenters
│ ├── item_list_presenter.rb
│ ├── item_presenter.rb
│ └── user_presenter.rb
└── views
│ ├── dashboard
│ ├── index.html.erb
│ └── mockup.html
│ └── layouts
│ └── application.html.erb
├── config.ru
├── config
├── application.rb
├── boot.rb
├── cucumber.yml
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── backtrace_silencers.rb
│ ├── inflections.rb
│ ├── mime_types.rb
│ ├── secret_token.rb
│ ├── session_store.rb
│ └── wrap_parameters.rb
├── locales
│ └── en.yml
└── routes.rb
├── db
├── migrate
│ ├── 20130120120320_create_items.rb
│ ├── 20130120120408_create_shares.rb
│ ├── 20130120120503_create_users.rb
│ └── 20130212195016_change_public_key_to_text.rb
├── schema.rb
└── seeds.rb
├── docs
├── DataFormat.md
└── core.md
├── lib
└── tasks
│ ├── brakeman.rake
│ └── jasmine.rake
├── log
└── .gitkeep
├── public
├── 404.html
├── 422.html
├── 500.html
├── favicon.ico
└── robots.txt
├── script
├── bootstrap
└── rails
├── spec
├── acceptance
│ ├── item_spec.rb
│ └── key_spec.rb
├── controllers
│ ├── auth
│ │ └── rsa_controller_spec.rb
│ └── items_controller_spec.rb
├── fixtures
│ ├── priv.pem
│ └── pub.pem
├── javascripts
│ ├── fixtures
│ │ ├── priv.pem
│ │ └── pub.pem
│ ├── models
│ │ ├── item_key_spec.coffee
│ │ ├── item_spec.coffee
│ │ ├── keypair_authenticator_spec.coffee
│ │ └── keypair_spec.coffee
│ ├── routers
│ │ └── item_router_spec.coffee
│ ├── spec.css
│ ├── spec.js.coffee
│ └── views
│ │ └── item
│ │ └── create_spec.coffee
├── models
│ ├── item_spec.rb
│ ├── rsa_challenge_spec.rb
│ └── user_spec.rb
├── presenters
│ ├── item_list_presenter_spec.rb
│ ├── item_presenter_spec.rb
│ └── user_presenter_spec.rb
├── spec_helper.rb
└── support
│ ├── acceptance.rb
│ ├── controller_spec_helpers.rb
│ ├── fixtures.rb
│ ├── mocks.rb
│ └── webmock.rb
└── vendor
└── assets
├── javascripts
├── ZeroClipboard.js
├── backbone.js
├── backbone.layoutmanager.js
├── forge.coffee
├── forge
│ ├── aes.js
│ ├── asn1.js
│ ├── des.js
│ ├── hmac.js
│ ├── jsbn.js
│ ├── md5.js
│ ├── mgf1.js
│ ├── oids.js
│ ├── pbkdf2.js
│ ├── pki.js
│ ├── prng.js
│ ├── pss.js
│ ├── random.js
│ ├── rc2.js
│ ├── rsa.js
│ ├── sha1.js
│ ├── sha256.js
│ └── util.js
├── form2js.js
├── jquery.js
├── jquery.toObject.js
├── mustache.js
└── underscore.js
└── swf
└── ZeroClipboard.swf
/.env:
--------------------------------------------------------------------------------
1 | # These values are used only in test and development mode. To run Swordfish in
2 | # production, set these variables in your production server environment.
3 |
4 | MONGODB_URI=mongodb://localhost:27017
5 |
6 | # These keys are used for RSA key authentication. It is essential that they be
7 | # changed to new values in production to maintain authentication security.
8 | #
9 | # Generted from `rails console` with `SecureRandom.base64(16)`
10 | AUTH_SECRET=2ObZZxmcFJi5LZa97sv5KyAe+TLA0kqR
11 | # Generted from `rails console` with `SecureRandom.base64(64)`
12 | SECRET_TOKEN=Ik7iwp1YV2y0zpHSgk+ZY5dIP3uJ9bAzI4Tr3j/1SK3OZ8kHqw/TK1zebDNd22aCFr331b6OYoi6Hnwsg4tB6A==
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle
2 | /log/*.log
3 | /tmp
4 | .DS_Store
5 | /.rbenv-version
6 | /bin
7 | /vendor/gems
8 | /db/*.sqlite3
9 | .ruby-version
10 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 1.9.3
4 | env: GH_POSTGRESQL_USER=postgres
5 | before_script:
6 | - psql -c 'create database swordfish_test;' -U postgres
7 | - rake db:test:load
8 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## If you find a security vulnerability:
4 |
5 | 1. **DO NOT POST ABOUT IT PUBLICLY**
6 | 2. Send an email to bkeepers@github.com with details about the security vulnerability.
7 | 3. After a fix has been released, a public announcement will be made giving all glory and honor to you.
8 |
9 | ## If you find what looks like a bug:
10 |
11 | 1. Search the [mailing list](https://groups.google.com/group/swordfishapp) to see if anyone else had the same issue.
12 | 2. Check the [GitHub issue tracker](https://github.com/github/swordfish/issues) to see if anyone else has reported issue.
13 | 3. If you don't see anything, [create an issue](https://github.com/github/swordfish/issues/new) with information on how to reproduce the issue.
14 |
15 | ## If you want to contribute an enhancement or a fix:
16 |
17 | 1. Fork the project on GitHub.
18 | 2. Make your changes with tests.
19 | 3. Commit the changes without making changes to the Rakefile, Gemfile, gemspec, or any other files that aren't related to your enhancement or fix.
20 | 4. Send a pull request.
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | ruby '1.9.3'
4 |
5 | gem 'rails'
6 | gem 'pg'
7 | gem 'net-ssh' # for RSA key fingerprinting
8 | gem 'dotenv', :group => [:test, :development]
9 | gem 'strong_parameters'
10 |
11 | group :assets do
12 | gem 'sass-rails'
13 | gem 'compass-rails'
14 | gem 'coffee-rails'
15 | gem 'uglifier'
16 | gem 'hogan_assets'
17 | end
18 |
19 | group :development, :test do
20 | gem 'rspec-rails'
21 | gem 'jasminerice'
22 | gem 'guard-jasmine'
23 | end
24 |
25 | group :test do
26 | gem 'webmock', :require => false
27 | gem 'poltergeist'
28 | gem 'database_cleaner'
29 | gem 'brakeman', :require => false
30 | gem 'launchy'
31 | gem 'pry'
32 | end
33 |
34 | group :guard do
35 | gem 'guard-bundler'
36 | gem 'guard-rspec'
37 | end
38 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actionmailer (3.2.13)
5 | actionpack (= 3.2.13)
6 | mail (~> 2.5.3)
7 | actionpack (3.2.13)
8 | activemodel (= 3.2.13)
9 | activesupport (= 3.2.13)
10 | builder (~> 3.0.0)
11 | erubis (~> 2.7.0)
12 | journey (~> 1.0.4)
13 | rack (~> 1.4.5)
14 | rack-cache (~> 1.2)
15 | rack-test (~> 0.6.1)
16 | sprockets (~> 2.2.1)
17 | activemodel (3.2.13)
18 | activesupport (= 3.2.13)
19 | builder (~> 3.0.0)
20 | activerecord (3.2.13)
21 | activemodel (= 3.2.13)
22 | activesupport (= 3.2.13)
23 | arel (~> 3.0.2)
24 | tzinfo (~> 0.3.29)
25 | activeresource (3.2.13)
26 | activemodel (= 3.2.13)
27 | activesupport (= 3.2.13)
28 | activesupport (3.2.13)
29 | i18n (= 0.6.1)
30 | multi_json (~> 1.0)
31 | addressable (2.2.8)
32 | arel (3.0.2)
33 | brakeman (1.9.1)
34 | erubis (~> 2.6)
35 | fastercsv (~> 1.5)
36 | haml (~> 3.0)
37 | highline (~> 1.6)
38 | multi_json (~> 1.3)
39 | ruby2ruby (~> 2.0)
40 | ruby_parser (~> 3.1.1)
41 | sass (~> 3.0)
42 | terminal-table (~> 1.4)
43 | builder (3.0.4)
44 | capybara (1.1.4)
45 | mime-types (>= 1.16)
46 | nokogiri (>= 1.3.3)
47 | rack (>= 1.0.0)
48 | rack-test (>= 0.5.4)
49 | selenium-webdriver (~> 2.0)
50 | xpath (~> 0.1.4)
51 | childprocess (0.3.8)
52 | ffi (~> 1.0, >= 1.0.11)
53 | chunky_png (1.2.5)
54 | coderay (1.0.8)
55 | coffee-rails (3.2.2)
56 | coffee-script (>= 2.2.0)
57 | railties (~> 3.2.0)
58 | coffee-script (2.2.0)
59 | coffee-script-source
60 | execjs
61 | coffee-script-source (1.4.0)
62 | compass (0.12.2)
63 | chunky_png (~> 1.2)
64 | fssm (>= 0.2.7)
65 | sass (~> 3.1)
66 | compass-rails (1.0.3)
67 | compass (>= 0.12.2, < 0.14)
68 | crack (0.3.1)
69 | database_cleaner (0.9.1)
70 | diff-lcs (1.1.3)
71 | dotenv (0.1.0)
72 | erubis (2.7.0)
73 | eventmachine (1.0.0)
74 | execjs (1.4.0)
75 | multi_json (~> 1.0)
76 | fastercsv (1.5.5)
77 | faye-websocket (0.4.6)
78 | eventmachine (>= 0.12.0)
79 | ffi (1.3.1)
80 | fssm (0.2.9)
81 | guard (1.2.3)
82 | listen (>= 0.4.2)
83 | thor (>= 0.14.6)
84 | guard-bundler (1.0.0)
85 | bundler (~> 1.0)
86 | guard (~> 1.1)
87 | guard-jasmine (1.11.1)
88 | childprocess
89 | guard (>= 1.1.0)
90 | multi_json
91 | thor
92 | guard-rspec (1.2.0)
93 | guard (>= 1.1)
94 | haml (3.1.7)
95 | highline (1.6.15)
96 | hike (1.2.1)
97 | hogan_assets (1.3.1)
98 | execjs (>= 1.2.9)
99 | sprockets (>= 2.0.3)
100 | tilt (>= 1.3.3)
101 | http_parser.rb (0.5.3)
102 | i18n (0.6.1)
103 | jasminerice (0.0.10)
104 | coffee-rails
105 | haml
106 | journey (1.0.4)
107 | json (1.7.7)
108 | launchy (2.1.0)
109 | addressable (~> 2.2.6)
110 | listen (0.4.7)
111 | rb-fchange (~> 0.0.5)
112 | rb-fsevent (~> 0.9.1)
113 | rb-inotify (~> 0.8.8)
114 | mail (2.5.3)
115 | i18n (>= 0.4.0)
116 | mime-types (~> 1.16)
117 | treetop (~> 1.4.8)
118 | method_source (0.8.1)
119 | mime-types (1.22)
120 | multi_json (1.7.2)
121 | net-ssh (2.5.2)
122 | nokogiri (1.5.6)
123 | pg (0.14.1)
124 | poltergeist (1.0.2)
125 | capybara (~> 1.1)
126 | childprocess (~> 0.3)
127 | faye-websocket (~> 0.4, >= 0.4.4)
128 | http_parser.rb (~> 0.5.3)
129 | multi_json (~> 1.0)
130 | polyglot (0.3.3)
131 | pry (0.9.10)
132 | coderay (~> 1.0.5)
133 | method_source (~> 0.8)
134 | slop (~> 3.3.1)
135 | rack (1.4.5)
136 | rack-cache (1.2)
137 | rack (>= 0.4)
138 | rack-ssl (1.3.3)
139 | rack
140 | rack-test (0.6.2)
141 | rack (>= 1.0)
142 | rails (3.2.13)
143 | actionmailer (= 3.2.13)
144 | actionpack (= 3.2.13)
145 | activerecord (= 3.2.13)
146 | activeresource (= 3.2.13)
147 | activesupport (= 3.2.13)
148 | bundler (~> 1.0)
149 | railties (= 3.2.13)
150 | railties (3.2.13)
151 | actionpack (= 3.2.13)
152 | activesupport (= 3.2.13)
153 | rack-ssl (~> 1.3.2)
154 | rake (>= 0.8.7)
155 | rdoc (~> 3.4)
156 | thor (>= 0.14.6, < 2.0)
157 | rake (10.0.4)
158 | rb-fchange (0.0.5)
159 | ffi
160 | rb-fsevent (0.9.1)
161 | rb-inotify (0.8.8)
162 | ffi (>= 0.5.0)
163 | rdoc (3.12.2)
164 | json (~> 1.4)
165 | rspec (2.11.0)
166 | rspec-core (~> 2.11.0)
167 | rspec-expectations (~> 2.11.0)
168 | rspec-mocks (~> 2.11.0)
169 | rspec-core (2.11.0)
170 | rspec-expectations (2.11.1)
171 | diff-lcs (~> 1.1.3)
172 | rspec-mocks (2.11.1)
173 | rspec-rails (2.11.0)
174 | actionpack (>= 3.0)
175 | activesupport (>= 3.0)
176 | railties (>= 3.0)
177 | rspec (~> 2.11.0)
178 | ruby2ruby (2.0.2)
179 | ruby_parser (~> 3.1)
180 | sexp_processor (~> 4.0)
181 | ruby_parser (3.1.1)
182 | sexp_processor (~> 4.1)
183 | rubyzip (0.9.9)
184 | sass (3.2.5)
185 | sass-rails (3.2.6)
186 | railties (~> 3.2.0)
187 | sass (>= 3.1.10)
188 | tilt (~> 1.3)
189 | selenium-webdriver (2.29.0)
190 | childprocess (>= 0.2.5)
191 | multi_json (~> 1.0)
192 | rubyzip
193 | websocket (~> 1.0.4)
194 | sexp_processor (4.1.4)
195 | slop (3.3.3)
196 | sprockets (2.2.2)
197 | hike (~> 1.2)
198 | multi_json (~> 1.0)
199 | rack (~> 1.0)
200 | tilt (~> 1.1, != 1.3.0)
201 | strong_parameters (0.1.6)
202 | actionpack (~> 3.0)
203 | activemodel (~> 3.0)
204 | railties (~> 3.0)
205 | terminal-table (1.4.5)
206 | thor (0.18.1)
207 | tilt (1.3.6)
208 | treetop (1.4.12)
209 | polyglot
210 | polyglot (>= 0.3.1)
211 | tzinfo (0.3.37)
212 | uglifier (1.2.4)
213 | execjs (>= 0.3.0)
214 | multi_json (>= 1.0.2)
215 | webmock (1.8.8)
216 | addressable (~> 2.2.8)
217 | crack (>= 0.1.7)
218 | websocket (1.0.7)
219 | xpath (0.1.4)
220 | nokogiri (~> 1.3)
221 |
222 | PLATFORMS
223 | ruby
224 |
225 | DEPENDENCIES
226 | brakeman
227 | coffee-rails
228 | compass-rails
229 | database_cleaner
230 | dotenv
231 | guard-bundler
232 | guard-jasmine
233 | guard-rspec
234 | hogan_assets
235 | jasminerice
236 | launchy
237 | net-ssh
238 | pg
239 | poltergeist
240 | pry
241 | rails
242 | rspec-rails
243 | sass-rails
244 | strong_parameters
245 | uglifier
246 | webmock
247 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | guard 'bundler' do
2 | watch('Gemfile')
3 | end
4 |
5 | guard 'rspec', :version => 2 do
6 | watch(%r{^spec/.+_spec\.rb$})
7 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
8 | watch('spec/spec_helper.rb') { "spec" }
9 |
10 | # Rails example
11 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
12 | watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
13 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
14 | watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
15 | watch('app/controllers/application_controller.rb') { "spec/controllers" }
16 | end
17 |
18 | guard :jasmine do
19 | watch(%r{spec/javascripts/spec\.(js\.coffee|js|coffee)$}) { 'spec/javascripts' }
20 | watch(%r{spec/javascripts/.+_spec\.(js\.coffee|js|coffee)$})
21 | watch(%r{app/assets/javascripts/(.+?)\.(js\.coffee|js|coffee)(?:\.\w+)*$}) { |m| "spec/javascripts/#{ m[1] }_spec.#{ m[2] }" }
22 | end
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **NOTE: This repository is no longer supported or updated by GitHub. If you wish to continue to develop this code yourself, we recommend you fork it.**
2 |
3 | # Swordfish [](http://travis-ci.org/github/swordfish)
4 |
5 | Swordfish was an experiment in building a group-optimized password management
6 | app. It is unmaintained and is likely insecure.
7 |
8 | ## Recommended Reading
9 |
10 | * [Why passwords have never been weaker—and crackers have never been stronger](http://arstechnica.com/security/2012/08/passwords-under-assault/) - A great article about password cracking techniques and the implications for choosing strong passwords.
11 | * [JavaScript Cryptography Considered Harmful](http://www.matasano.com/articles/javascript-cryptography/) - A thoughtful critique of JavaScript encryption, discussed in [#28](https://github.com/github/swordfish/issues/28).
12 | * [A JavaScript Implementation of TLS](http://digitalbazaar.com/2010/07/20/javascript-tls-1/) - The background of [Forge](https://github.com/digitalbazaar/forge/blob/master/README.md), the encryption library used by Swordfish.
13 | * [Web Cryptography API](http://www.w3.org/2012/webcrypto/WebCryptoAPI/) - A draft W3C proposal for a JavaScript cryptography API. If all goes well, this will eventually replace Forge for all of the cryptography in Swordfish.
14 | * [Megabad: A quick look at the state of Mega’s encryption](http://arstechnica.com/business/2013/01/megabad-a-quick-look-at-the-state-of-megas-encryption/) - Mega essentially uses the same approach as Swordfish: RSA keys for each user, coupled with a randomly generated key for AES encryption of each item.
15 |
16 | ## Working on Swordfish
17 |
18 | Use the `bootstrap` script to get the environment set up.
19 |
20 | script/bootstrap
21 |
22 | Now you will need to run the database migrations before you run the server.
23 |
24 | bundle exec rake db:create db:migrate
25 |
26 | Finally you can start the application.
27 |
28 | script/rails s
29 |
30 | Running the tests requires PhantomJS. If you're on Mac OS X you can use homebrew
31 | to install this for you.
32 |
33 | brew install phantomjs
34 |
35 | If you hack on Swordfish and end up adding or editing features you will want to
36 | run the tests.
37 |
38 | bundle exec rake
39 |
40 | Want to join the [core team](https://github.com/github/swordfish/blob/master/docs/core.md)?
41 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | # Add your own tasks in files placed in lib/tasks ending in .rake,
3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4 |
5 | require File.expand_path('../config/application', __FILE__)
6 |
7 | Swordfish::Application.load_tasks
8 |
9 | task :default => 'guard:jasmine'
10 |
--------------------------------------------------------------------------------
/app/assets/images/emoji/warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/app/assets/images/emoji/warning.png
--------------------------------------------------------------------------------
/app/assets/images/emoji/white_check_mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/app/assets/images/emoji/white_check_mark.png
--------------------------------------------------------------------------------
/app/assets/images/mockup/grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/app/assets/images/mockup/grid.png
--------------------------------------------------------------------------------
/app/assets/images/mockup/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/app/assets/images/mockup/home.png
--------------------------------------------------------------------------------
/app/assets/images/mockup/person.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/app/assets/images/mockup/person.png
--------------------------------------------------------------------------------
/app/assets/images/mockup/retina_wood.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/app/assets/images/mockup/retina_wood.png
--------------------------------------------------------------------------------
/app/assets/images/mockup/web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/app/assets/images/mockup/web.png
--------------------------------------------------------------------------------
/app/assets/javascripts/application.coffee:
--------------------------------------------------------------------------------
1 | #= require jquery
2 | #= require models
3 |
4 | class @Application
5 | _.extend @prototype, Backbone.Events
6 |
7 | @on: (args...) ->
8 | @prototype.on(args...)
9 |
10 | constructor: (@keypair = Keypair.load()) ->
11 | @trigger 'initialize'
12 |
13 | setKey: (key) ->
14 | @keypair = new Keypair(key)
15 | @keypair.savePrivateKey()
16 | @keypair
17 |
18 | authenticate: ->
19 | new KeypairAuthenticator(@keypair).request()
20 |
21 | # FIXME: make a class for UI concerns and move this there
22 | layout: (layout) ->
23 | unless layout == @current_layout
24 | @current_layout = layout
25 | $(document.body).html(layout.el)
26 | layout.render()
27 |
--------------------------------------------------------------------------------
/app/assets/javascripts/collections/item.coffee:
--------------------------------------------------------------------------------
1 | class Item.Collection extends Backbone.Collection
2 | model: Item
3 | url: '/items'
4 |
5 | constructor: (models, options) ->
6 | @keypair = options.keypair
7 | super
8 |
--------------------------------------------------------------------------------
/app/assets/javascripts/ext/backbone.coffee:
--------------------------------------------------------------------------------
1 | # Swordfish customizations to Backbone
2 |
3 | _.extend Backbone.Collection.prototype,
4 | load: (id) ->
5 | result = @deferred()
6 | if record = @get(id)
7 | result.resolve(record)
8 | else
9 | finder = =>
10 | @off 'reset', finder
11 | result.resolve(@get(id))
12 | @on 'reset', finder
13 | result
14 |
15 | deferred: ->
16 | jQuery.Deferred()
17 |
18 |
--------------------------------------------------------------------------------
/app/assets/javascripts/ext/backbone.layoutmanager.coffee:
--------------------------------------------------------------------------------
1 | # Swordfish customizations to LayoutManager
2 |
3 | Backbone.LayoutManager.configure(
4 | manage: true
5 |
6 | fetch: (path) ->
7 | HoganTemplates[path]
8 |
9 | render: (template, context) ->
10 | template.render(context || {})
11 | )
12 |
--------------------------------------------------------------------------------
/app/assets/javascripts/mockup.coffee:
--------------------------------------------------------------------------------
1 | #= require lib/jquery
2 |
3 | $(document).on 'keypress', (e) ->
4 | if e.keyCode == 96
5 | $(document.body).toggleClass('grid')
6 |
7 | $ ->
8 | $('body').append('
');
--------------------------------------------------------------------------------
/app/assets/javascripts/models.coffee:
--------------------------------------------------------------------------------
1 | #= require underscore
2 | #= require backbone
3 | #= require ext/backbone
4 | #= require forge
5 | #= require_tree ./models
6 | #= require_tree ./collections
7 |
--------------------------------------------------------------------------------
/app/assets/javascripts/models/item.coffee:
--------------------------------------------------------------------------------
1 | class @Item extends Backbone.Model
2 | initialize: ->
3 | @set 'key', @collection.keypair.encrypt(ItemKey.generate()) unless @has('key')
4 | @on 'change:data', @encryptData
5 | @encryptData()
6 |
7 | encryptData: ->
8 | if data = @get('data')
9 | @set 'encrypted_data', @key().encrypt(data)
10 | @unset 'data'
11 |
12 | key: ->
13 | new ItemKey(@collection.keypair.decrypt(@get('key')))
14 |
15 | data: ->
16 | @key().decrypt(@get('encrypted_data'))
--------------------------------------------------------------------------------
/app/assets/javascripts/models/item_key.coffee:
--------------------------------------------------------------------------------
1 | class @ItemKey
2 | @cipher: forge.aes
3 | @key_size: 256 / 8
4 |
5 | @generate: ->
6 | forge.random.getBytes(@key_size)
7 |
8 | constructor: (@key) ->
9 |
10 | encrypt: (data) ->
11 | iv = forge.random.getBytes(16)
12 | buffer = forge.util.createBuffer(JSON.stringify(data))
13 | cipher = @constructor.cipher.startEncrypting(@key, iv)
14 |
15 | cipher.update(buffer)
16 | cipher.finish()
17 | ciphertext = cipher.output.data
18 |
19 | JSON.stringify
20 | iv : forge.util.encode64(iv)
21 | data: forge.util.encode64(ciphertext)
22 |
23 | decrypt: (data) ->
24 | params = JSON.parse(data)
25 | iv = forge.util.decode64(params['iv'])
26 | ciphertext = forge.util.decode64(params['data'])
27 | buffer = forge.util.createBuffer(ciphertext)
28 | cipher = forge.aes.startDecrypting(@key, iv)
29 |
30 | cipher.update(buffer)
31 | cipher.finish()
32 | JSON.parse(cipher.output.data)
33 |
--------------------------------------------------------------------------------
/app/assets/javascripts/models/keypair.coffee:
--------------------------------------------------------------------------------
1 | class @Keypair
2 | @localStorage: window.localStorage
3 | @ajax: jQuery.ajax
4 |
5 | @load: ->
6 | new @(key) if key = @localStorage['privateKey']
7 |
8 | constructor: (@privateKeyPem) ->
9 |
10 | savePrivateKey: () ->
11 | @constructor.localStorage['privateKey'] = @privateKeyPem
12 |
13 | # Public: Unlock the keypair with the given passphrase.
14 | #
15 | # Returns a jQuery.Deferred() that will resolve when successful.
16 | unlock: (password) ->
17 | if @privateKey = forge.pki.decryptRsaPrivateKey(@privateKeyPem, password)
18 | @publicKey = forge.pki.rsa.setPublicKey(@privateKey.n, @privateKey.e)
19 |
20 | @isUnlocked()
21 |
22 | isUnlocked: ->
23 | !!@privateKey
24 |
25 | encrypt: (data) ->
26 | forge.util.encode64(@publicKey.encrypt(data))
27 |
28 | decrypt: (data) ->
29 | @privateKey.decrypt(forge.util.decode64(data))
30 |
31 | publicKeyPem: ->
32 | forge.pki.publicKeyToPem(@publicKey)
33 |
--------------------------------------------------------------------------------
/app/assets/javascripts/models/keypair_authenticator.coffee:
--------------------------------------------------------------------------------
1 | class @KeypairAuthenticator
2 | @ajax: jQuery.ajax
3 |
4 | constructor: (@keypair) ->
5 | jQuery.extend @, jQuery.Deferred()
6 | @done @setupAjax
7 |
8 | # Public: Request a challenge from the server.
9 | request: ->
10 | @constructor.ajax(
11 | type: 'POST'
12 | url: '/auth/rsa'
13 | data: @keypair.publicKeyPem()
14 | dataType: 'text'
15 | ).done @respond
16 | @
17 |
18 | # Internal: Respond to the challenge from the server.
19 | respond: (data) =>
20 | @challenge = @decryptChallenge(data)
21 |
22 | @constructor.ajax(
23 | type: 'PUT'
24 | url: '/auth/rsa'
25 | dataType: 'text'
26 | data: @challenge
27 | ).done => @resolve(@challenge)
28 |
29 | # Internal: Decrypt the challenge from the server.
30 | decryptChallenge: (challenge) ->
31 | forge.util.encode64(@keypair.decrypt(challenge))
32 |
33 | # Internal
34 | setupAjax: =>
35 | jQuery.ajaxPrefilter @setHeader
36 |
37 | # Internal
38 | setHeader: (options, originalOptions, xhr) =>
39 | xhr.setRequestHeader('X-Challenge', @challenge)
40 |
--------------------------------------------------------------------------------
/app/assets/javascripts/models/keypair_generator.coffee:
--------------------------------------------------------------------------------
1 | # Generate an RSA public and encrypted private key.
2 | #
3 | # Generating a keypair is time intensive, but this implementation works in
4 | # chunks, pausing to allow the UI to update.
5 | #
6 | # generator = new KeypairGenerator('mypassphrase').start()
7 | #
8 | # generator.progress ->
9 | # console.log 'progress'
10 | # updateUI()
11 | #
12 | # generator.done (pub, priv) ->
13 | # console.log 'generated keys', pub, priv
14 | # saveKeys(pub, priv)
15 | #
16 | class @KeypairGenerator
17 | keysize: 1024
18 | interval: 10
19 |
20 | constructor: (@passphrase) ->
21 | @deferred = jQuery.Deferred()
22 |
23 | start: ->
24 | @state = forge.pki.rsa.createKeyPairGenerationState(@keysize)
25 | @delay @progress
26 | @deferred
27 |
28 | progress: =>
29 | if forge.pki.rsa.stepKeyPairGenerationState(@state, @interval)
30 | @finish()
31 | else
32 | @deferred.notify(@state)
33 | @delay @progress
34 |
35 | finish: ->
36 | @deferred.resolve(@publicKey(), @encryptedPrivateKey())
37 |
38 | encryptedPrivateKey: ->
39 | forge.pki.encryptRsaPrivateKey(@state.keys.privateKey, @passphrase)
40 |
41 | publicKey: ->
42 | forge.pki.publicKeyToPem(@state.keys.publicKey)
43 |
44 | # Delay to prevent from locking the interface.
45 | #
46 | # Override in tests to allow synchronous tests.
47 | delay: (fn) ->
48 | _.delay(fn, 0)
49 |
50 |
--------------------------------------------------------------------------------
/app/assets/javascripts/routers/item_router.coffee:
--------------------------------------------------------------------------------
1 | class @ItemRouter extends Backbone.Router
2 | routes:
3 | '': 'items'
4 | 'items/new': 'newItem'
5 | 'items/:id': 'item'
6 | 'items/:id/edit': 'edit'
7 |
8 | constructor: (options) ->
9 | super
10 |
11 | @app = options.app
12 | @on 'all', @ensureLayout
13 |
14 | @itemsCollection = new Item.Collection([], keypair: @app.keypair)
15 |
16 | @layout = new Backbone.LayoutManager({
17 | template: "templates/main",
18 |
19 | views:
20 | "#items": new Item.Views.List(collection: @itemsCollection)
21 | "#details": @details = new Backbone.View()
22 | })
23 |
24 | ensureLayout: =>
25 | @app.layout @layout
26 |
27 | items: =>
28 | # ensure items collection has access to the keypair
29 | @itemsCollection.keypair = @app.keypair
30 | @itemsCollection.fetch()
31 |
32 | newItem: (id) =>
33 | @content new Item.Views.Create(collection: @itemsCollection)
34 |
35 | item: (id) =>
36 | @itemsCollection.load(id).then (item) =>
37 | @content new Item.Views.Show(model: item)
38 |
39 | edit: (id) =>
40 | @itemsCollection.load(id).then (item) =>
41 | @content new Item.Views.Edit(model: item)
42 |
43 | content: (view) ->
44 | @details.setView(view).render()
45 |
--------------------------------------------------------------------------------
/app/assets/javascripts/routers/key_router.coffee:
--------------------------------------------------------------------------------
1 | class @KeyRouter extends Backbone.Router
2 | routes:
3 | 'key/new': 'newKey'
4 | 'key/download': 'download'
5 | 'key/load': 'load'
6 | 'key/unlock': 'unlock'
7 |
8 | constructor: (options) ->
9 | super
10 | @app = options.app
11 | @layout = new Backbone.LayoutManager(
12 | template: 'templates/setup'
13 | )
14 | @on 'all', @ensureLayout
15 |
16 | if @app.keypair
17 | window.location.hash = "#key/unlock" unless @app.keypair.isUnlocked()
18 | else
19 | window.location.hash = "#key/new"
20 |
21 | newKey: ->
22 | @layout.setView('#content', new Keypair.Views.Create(app: @app)).render()
23 |
24 | download: ->
25 | @layout.setView('#content', new Keypair.Views.Download(app: @app)).render()
26 |
27 | load: ->
28 | @layout.setView('#content', new Keypair.Views.Load(app: @app)).render()
29 |
30 | unlock: ->
31 | @layout.setView('#content', new Keypair.Views.Unlock(app: @app)).render()
32 |
33 | ensureLayout: =>
34 | @app.layout @layout
35 |
36 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/item/create.mustache:
--------------------------------------------------------------------------------
1 |
34 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/item/edit.mustache:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/item/list.mustache:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/item/list_item.mustache:
--------------------------------------------------------------------------------
1 |
2 | {{title}}
3 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/item/show.mustache:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{#data}}
4 |
5 | Username
6 | {{username}}
7 | URL
8 | {{url}}
9 | Password
10 |
11 |
12 | ••••••••••••
13 | reveal
14 | hide
15 |
16 | copy to clipboard
17 |
18 |
19 | {{/data}}
20 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/keypair/create.mustache:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/keypair/download.mustache:
--------------------------------------------------------------------------------
1 | Download Your Private Key
2 |
3 | This private key and your passphrase is required to access all of your data. It is not saved on the server , so you will need to upload it whenever you sign in. If you lose this key, you will not be able to access your data.
4 |
5 | Download Private Key
6 |
7 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/keypair/load.mustache:
--------------------------------------------------------------------------------
1 | Load Private Key
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/keypair/unlock.mustache:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/main.mustache:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/setup.mustache:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/assets/javascripts/ui.coffee:
--------------------------------------------------------------------------------
1 | #= require hogan
2 | #= require backbone.layoutmanager
3 | #= require form2js
4 | #= require jquery.toObject
5 | #= require mustache
6 | #= require ext/backbone.layoutmanager
7 |
8 | #= require_tree ./templates
9 | #= require_tree ./views
10 | #= require_tree ./routers
11 |
12 | Application.on 'initialize', ->
13 | new KeyRouter(app: @)
14 | new ItemRouter(app: @)
15 | Backbone.history.start()
16 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/item/_form.coffee:
--------------------------------------------------------------------------------
1 | Item.Views ?= {}
2 |
3 | class Item.Views.Form extends Backbone.View
4 | events:
5 | 'submit form': 'submit'
6 | 'keyup #item-password': 'checkPassword'
7 | 'change #item-password': 'checkPassword'
8 | 'keyup #item-confirm-password': 'checkPassword'
9 | 'change #item-confirm-password': 'checkPassword'
10 |
11 | checkPassword: =>
12 | @$('#item-password-confirmation-status').attr 'class',
13 | if @passwordConfirmed()
14 | if @password()
15 | 'match'
16 | else
17 | ''
18 | else
19 | 'mismatch'
20 |
21 | submit: (event) =>
22 | event.preventDefault()
23 | if @passwordConfirmed()
24 | params = @$('form').toObject(mode: 'combine')
25 | params.data ||= {}
26 | params.data.password = @password()
27 | @save(params)
28 | else
29 | @$('.error').text("Password doesn't match Confirm. Please type your password again.")
30 |
31 | passwordConfirmed: ->
32 | @password() == @passwordConfirmation()
33 |
34 | password: -> @$('#item-password').val()
35 | passwordConfirmation: -> @$('#item-confirm-password').val()
36 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/item/create.coffee:
--------------------------------------------------------------------------------
1 | Item.Views ?= {}
2 |
3 | class Item.Views.Create extends Item.Views.Form
4 | template: 'templates/item/create'
5 |
6 | save: (params) ->
7 | @collection.create params, success: (item) =>
8 | Backbone.history.navigate "items/#{item.id}", true
9 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/item/edit.coffee:
--------------------------------------------------------------------------------
1 | Item.Views ?= {}
2 |
3 | class Item.Views.Edit extends Item.Views.Form
4 | template: 'templates/item/edit'
5 |
6 | save: (params) ->
7 | @model.save params, success: (item) =>
8 | Backbone.history.navigate "items/#{item.id}", true
9 |
10 | serialize: ->
11 | _.extend @model.toJSON(), data: @model.data()
12 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/item/list.coffee:
--------------------------------------------------------------------------------
1 | Item.Views ?= {}
2 |
3 | class Item.Views.List extends Backbone.View
4 | template: 'templates/item/list'
5 |
6 | constructor: ->
7 | super
8 | @collection.on 'add reset', @render, @
9 |
10 | beforeRender: =>
11 | @collection.each @add
12 |
13 | add: (model) =>
14 | @insertView '.items', new Item.Views.ListItem(model: model)
15 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/item/list_item.coffee:
--------------------------------------------------------------------------------
1 | Item.Views ?= {}
2 |
3 | class Item.Views.ListItem extends Backbone.View
4 | template: 'templates/item/list_item'
5 | className: 'item'
6 |
7 | constructor: ->
8 | super
9 | @model.on 'change', @render, @
10 |
11 | serialize: ->
12 | _.extend @model.toJSON(), data: @model.data(), domain: @domain()
13 |
14 | domain: ->
15 | a = document.createElement('a')
16 | a.href = @model.data().url
17 | a.hostname
18 |
19 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/item/show.coffee.erb:
--------------------------------------------------------------------------------
1 | #= require ZeroClipboard
2 |
3 | Item.Views ?= {}
4 |
5 | class Item.Views.Show extends Backbone.View
6 | template: 'templates/item/show'
7 |
8 | events:
9 | 'click a.reveal': 'reveal'
10 | 'click a.hide': 'hide'
11 |
12 | afterRender: ->
13 | ZeroClipboard.config moviePath: "<%= asset_path 'ZeroClipboard.swf' %>"
14 | @clipboard = new ZeroClipboard @$('#copy_link')[0]
15 | @clipboard.on 'dataRequested', (client, args) =>
16 | @clipboard.setText @model.data().password
17 |
18 | serialize: ->
19 | _.extend @model.toJSON(), data: @model.data()
20 |
21 | reveal: (e) =>
22 | window.view = @
23 | e.preventDefault()
24 | @$('.password').text(@model.data().password)
25 | @$el.addClass('revealed')
26 |
27 | hide: (e) =>
28 | e.preventDefault()
29 | @$('.password').text('••••••••••••')
30 | @$el.removeClass('revealed')
31 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/keypair/create.coffee:
--------------------------------------------------------------------------------
1 | Keypair.Views ?= {}
2 |
3 | class Keypair.Views.Create extends Backbone.View
4 | template: 'templates/keypair/create'
5 |
6 | events:
7 | 'submit form': 'generate'
8 |
9 | constructor: (options) ->
10 | super
11 | @app = options.app
12 |
13 | generate: (event) =>
14 | @passphrase = @$('input[name=passphrase]').val()
15 | generator = new KeypairGenerator(@passphrase).start()
16 | @start()
17 | generator.done @done
18 | false
19 |
20 | start: =>
21 | @$('#status').text('Generating keys…')
22 |
23 | done: (publicKey, privateKey) =>
24 | keypair = @app.setKey(privateKey)
25 | keypair.unlock(@passphrase)
26 | Backbone.history.navigate "key/download", true
27 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/keypair/download.coffee:
--------------------------------------------------------------------------------
1 | Keypair.Views ?= {}
2 |
3 | class Keypair.Views.Download extends Backbone.View
4 | template: 'templates/keypair/download'
5 |
6 | constructor: (options) ->
7 | @app = options.app
8 | super
9 |
10 | serialize: ->
11 | {href: @dataUri()}
12 |
13 | dataUri: ->
14 | "data:application/x-pem-file,#{encodeURIComponent(@app.keypair.privateKeyPem)}"
15 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/keypair/load.coffee:
--------------------------------------------------------------------------------
1 | Keypair.Views ?= {}
2 |
3 | class Keypair.Views.Load extends Backbone.View
4 | template: 'templates/keypair/load'
5 |
6 | events:
7 | 'change input[type=file]': 'change'
8 |
9 | constructor: (options) ->
10 | super
11 | @app = options.app
12 |
13 | change: (e) =>
14 | file = e.target.files[0]
15 | reader = new FileReader()
16 | reader.onload = @load
17 | reader.readAsText(file)
18 |
19 | load: (e) =>
20 | @app.setKey e.target.result
21 | Backbone.history.navigate 'key/unlock', true
22 |
23 |
--------------------------------------------------------------------------------
/app/assets/javascripts/views/keypair/unlock.coffee:
--------------------------------------------------------------------------------
1 | Keypair.Views ?= {}
2 |
3 | class Keypair.Views.Unlock extends Backbone.View
4 | template: 'templates/keypair/unlock'
5 |
6 | events:
7 | 'submit form': 'submit'
8 |
9 | constructor: (options) ->
10 | super
11 | @app = options.app
12 |
13 | afterRender: ->
14 | @$('input:first').focus()
15 |
16 | submit: (e) =>
17 | if @app.keypair.unlock(@$('input[type=password]').val())
18 | @app.authenticate().done(@done).fail(@fail)
19 | else
20 | @fail()
21 |
22 | false
23 |
24 | done: =>
25 | Backbone.history.navigate '', true
26 |
27 | fail: =>
28 | @$('p.error').text('Your passphrase was incorrect!')
29 | @$('input[type=password]').val('')
30 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the top of the
9 | * compiled file, but it's generally better to create a new file per style scope.
10 | *
11 | *= require_self
12 | *= require_tree .
13 | */
14 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/mockup.scss:
--------------------------------------------------------------------------------
1 | @import 'compass/reset';
2 | @import "blueprint";
3 |
4 | @include blueprint-typography;
5 |
6 |
7 | $container-color: rgba(110,83,46,0.2);
8 | $shadow-color: opacify($container-color, 0.1);
9 |
10 | // ==== debugging ===
11 |
12 | #grid {
13 | position: absolute;
14 | top: 0;
15 | left: 0;
16 | right: 0;
17 | bottom: 0;
18 | background-image: image-url('mockup/grid.png');
19 | display: none;
20 | z-index: 100;
21 |
22 | .grid & {
23 | display: block;
24 | }
25 | }
26 |
27 | // ==== basic ====
28 |
29 | body {
30 | font-family: "Lucida Grande", Arial, Helvetica, sans-serif;
31 | background-image: image-url('mockup/retina_wood.png');
32 | }
33 |
34 | a {
35 | text-decoration: none;
36 | }
37 |
38 | h1, h2, h3, h4, h5, h6 {
39 | font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
40 | font-weight: 100;
41 | }
42 |
43 | // ==== layout =====
44 |
45 | @mixin pane {
46 | position: absolute;
47 | top: 45px;
48 | bottom: 18px;
49 | overflow: hidden;
50 |
51 | footer {
52 | height: 53px;
53 | border-top: 1px solid #f7f7f7;
54 | line-height: 54px;
55 | border-bottom-left-radius: 4px;
56 | border-bottom-right-radius: 4px;
57 | @include background(linear-gradient(rgba(255,255,255,0.85), rgba(255,255,255,1)));
58 | padding: 0 18px;
59 | position: absolute;
60 | left: 0;
61 | right: 0;
62 | bottom: 0;
63 | z-index: 3;
64 | text-align: right;
65 | }
66 |
67 | .pane-content {
68 | z-index: 2;
69 | position: absolute;
70 | top: 0;
71 | right: 0;
72 | bottom: 0;
73 | left: 0;
74 | padding: 9px 0 72px 0;
75 | overflow: auto;
76 | }
77 | }
78 |
79 | #container {
80 | position: absolute;
81 | width: 960px;
82 | top: 18px;
83 | bottom: 18px;
84 | left: 50%;
85 | margin-left: -480px;
86 | background-color: $container-color;
87 | box-shadow: inset 0px 0px 5px $shadow-color;
88 | border-radius: 4px;
89 |
90 | &.setup {
91 | top: 50%;
92 | width: 480px;
93 | height: 300px;
94 | margin-left: -240px;
95 | margin-top: -150px;
96 |
97 | #content {
98 | @include pane;
99 | background: #fff;
100 | top: 18px;
101 | left: 18px;
102 | right: 18px;
103 | box-shadow: 0px 0px 4px $shadow-color;
104 | border-radius: 4px;
105 | padding: 18px;
106 |
107 | }
108 | }
109 |
110 | &>section {
111 | padding: 9px;
112 | }
113 | }
114 |
115 | @import 'compass/css3';
116 |
117 | #header {
118 | height: 45px;
119 | padding: 0 18px;
120 | border-radius: 4px 4px 0 0;
121 | position: relative;
122 |
123 | h1 {
124 | font-size: 22px;
125 | line-height: 45px;
126 | color: #666;
127 | letter-spacing: 2px;
128 | }
129 |
130 | #search, nav {
131 | line-height: 27px;
132 | position: absolute;
133 | top: 9px;
134 | right: 18px;
135 | float: right;
136 |
137 | input {
138 | font-size: 14px;
139 | width: 200px;
140 | line-height: 18px;
141 | }
142 | }
143 | }
144 |
145 | #vaults {
146 | @include pane;
147 | left: 0px;
148 | width: 192px;
149 | padding-left: 18px;
150 | z-index: 20;
151 |
152 | .selected {
153 | background: #fff;
154 | box-shadow: -1px 1px 4px $shadow-color;
155 | }
156 |
157 | h1 {
158 | margin: 9px 0;
159 | color: #999;
160 | color: rgba(0,0,0,0.4);
161 | text-shadow: 0px 1px 1px rgba(255,255,255,0.3);
162 | font-size: 11px;
163 | line-height: 18px;
164 | font-weight: normal;
165 | text-transform: uppercase;
166 | position: relative;
167 |
168 | .add {
169 | position: absolute;
170 | top: 0;
171 | right: 9px;
172 | }
173 | }
174 |
175 | .vault {
176 | border-radius: 4px 0 0 4px;
177 |
178 | a {
179 | color: #000;
180 | display: block;
181 | padding: 18px;
182 | }
183 |
184 | img {
185 | @extend .icon;
186 | }
187 | }
188 | }
189 |
190 | #items {
191 | @include pane;
192 | background: #fff;
193 | z-index: 10;
194 |
195 | left: 210px;
196 | right: 50%;
197 | border-right: 1px dotted #f7f7f7;
198 |
199 | box-shadow: -1px 0px 4px $shadow-color;
200 | border-radius: 4px 0 0 4px;
201 |
202 | .item {
203 | display: block;
204 | padding: 9px 18px;
205 | img {
206 | padding-right: 5px;
207 | }
208 | a {
209 | vertical-align: top;
210 | }
211 | }
212 | }
213 |
214 | #details {
215 | z-index: 20;
216 |
217 | @include pane;
218 | background: #fff;
219 | left: 50%;
220 | right: 18px;
221 | border-radius: 0 4px 4px 0;
222 | box-shadow: 1px 0px 4px $shadow-color;
223 |
224 | header {
225 | margin-bottom: 36px;
226 | }
227 |
228 | .pane-content {
229 | padding-left: 18px;
230 | padding-right: 18px;
231 | }
232 |
233 | section {
234 | @include clearfix;
235 | padding-bottom: 9px;
236 | margin-bottom: 9px;
237 | }
238 |
239 | h1 {
240 | margin-bottom: 0;
241 | font-weight: 100;
242 | line-height: 54px;
243 | }
244 |
245 | .url {
246 | color: #bbb;
247 | span {
248 | color: #ddd;
249 | }
250 | }
251 |
252 | .preview {
253 | float: right;
254 | width: 168px;
255 | height: 126px;
256 | border-radius: 4px;
257 | overflow: hidden;
258 | box-shadow: 0px 1px 4px rgba(0,0,0,0.4);
259 | margin-right: 4px;
260 |
261 | img {
262 | width: 100%;
263 | }
264 | }
265 |
266 | .hide { display: none; }
267 | .revealed {
268 | .hide { display: inline; }
269 | .reveal { display: none; }
270 | }
271 |
272 |
273 | }
274 |
275 | .icon {
276 | width: 18px;
277 | height: 18px;
278 | vertical-align: middle;
279 | display: inline-block;
280 | margin-right: 4px;
281 | text-indent: -2000px;
282 | overflow: hidden;
283 |
284 | &.text {
285 | font-family: "Times New Roman", serif;
286 | text-indent: 0;
287 | background: #ccc;
288 | color: #FFF;
289 | border-radius: 9px;
290 | font-weight: bold;
291 | font-size: 14px;
292 | font-style: italic;
293 | line-height: 19px;
294 | text-align: center;
295 | }
296 | }
297 |
298 | dl {
299 | dt {
300 | clear: left;
301 | float: left;
302 | width: 63px;
303 | text-align: right;
304 | padding-right: 9px;
305 | color: #999;
306 | font-weight: normal;
307 | }
308 | dd {
309 | margin-left: 72px;
310 | min-height: 18px;
311 | }
312 | }
313 |
314 | form {
315 | p {
316 | @include clearfix;
317 |
318 | label {
319 | display: block;
320 | float: left;
321 | width: 84px;
322 | margin-right: 9px;
323 | color: #999;
324 | text-align: right;
325 | line-height: 36px;
326 | }
327 | }
328 | }
329 |
330 | #item-password-confirmation-status {
331 | display: inline-block;
332 | width: 32px;
333 | height: 32px;
334 | background-size: 32px;
335 | vertical-align: top;
336 | &.match {
337 | background-image: image-url('emoji/white_check_mark.png');
338 | }
339 | &.mismatch {
340 | background-image: image-url('emoji/warning.png');
341 | }
342 | }
343 |
344 | input[type=text], input[type=password], textarea {
345 | font-size: 22px;
346 | font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
347 | font-weight: 100;
348 | }
349 |
350 | input.wide {
351 | width: 100%;
352 | box-sizing: border-box;
353 | }
354 |
355 | button, .button {
356 | background-color:#fff;
357 | border-radius:3px;
358 | border:1px solid #dadada;
359 | @include background(linear-gradient(#fff 0%, #f1f1f1 35%, #f1f1f1 65%, #fff 100%));
360 | box-shadow: inset 0 0 3px #fff, 0px 1px 2px rgba(100, 100, 100, 0.1);
361 | font-weight:bold;
362 | padding:9px 18px;
363 | text-decoration:none;
364 | text-shadow:1px 1px 0px #ffffff;
365 | text-align: center;
366 | }
367 |
368 | p.error {
369 | color: red;
370 | }
371 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | before_filter :sign_in_required
3 |
4 | private
5 |
6 | def sign_in_required
7 | head 401 unless signed_in?
8 | end
9 |
10 | def current_user
11 | @current_user ||= current_user_from_challenge
12 | end
13 |
14 | def current_user_from_challenge
15 | if request.headers['X-Challenge']
16 | challenge = RsaChallenge::Response.new(request.headers['X-Challenge'])
17 | User.find(challenge.user_id) if challenge.valid?
18 | end
19 | end
20 |
21 | def signed_in?
22 | !!current_user
23 | end
24 |
25 | end
26 |
--------------------------------------------------------------------------------
/app/controllers/auth/rsa_controller.rb:
--------------------------------------------------------------------------------
1 | class Auth::RsaController < ApplicationController
2 | skip_before_filter :sign_in_required
3 |
4 | def create
5 | challenge = RsaChallenge::Request.new(request.raw_post)
6 | render :text => challenge.value, :content_type => 'text/plain'
7 | end
8 |
9 | def update
10 | challenge = RsaChallenge::Response.new(request.raw_post)
11 | if challenge.valid?
12 | head 200
13 | else
14 | head 401
15 | end
16 | end
17 | end
--------------------------------------------------------------------------------
/app/controllers/dashboard_controller.rb:
--------------------------------------------------------------------------------
1 | class DashboardController < ApplicationController
2 | skip_before_filter :sign_in_required
3 |
4 | def index
5 | @user = UserPresenter.new(current_user)
6 | end
7 |
8 | def mockup
9 | render :layout => false
10 | end
11 | end
--------------------------------------------------------------------------------
/app/controllers/items_controller.rb:
--------------------------------------------------------------------------------
1 | class ItemsController < ApplicationController
2 | def index
3 | render :json => ItemListPresenter.new(current_user)
4 | end
5 |
6 | def create
7 | item = Item.create!(item_params)
8 | share = item.share_with(current_user, params[:key])
9 | render :json => ItemPresenter.new(item, share), :status => :created
10 | end
11 |
12 | def update
13 | item = Item.find(params[:id])
14 | share = item.share_for(current_user)
15 | item.update_attributes(item_params)
16 | render :json => ItemPresenter.new(item, share)
17 | end
18 |
19 | private
20 |
21 | def item_params
22 | params.permit(:title, :encrypted_data)
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/app/mailers/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/app/mailers/.gitkeep
--------------------------------------------------------------------------------
/app/models/item.rb:
--------------------------------------------------------------------------------
1 | class Item < ActiveRecord::Base
2 |
3 | include ActiveModel::ForbiddenAttributesProtection
4 |
5 | has_many :shares
6 |
7 | self.include_root_in_json = false
8 |
9 | def share_with(user, key)
10 | Share.create! :item_id => id, :user_id => user.id, :key => key
11 | end
12 |
13 | def share_for(user)
14 | shares.where(:user_id => user.id).first!
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/app/models/rsa_challenge.rb:
--------------------------------------------------------------------------------
1 | require 'openssl'
2 |
3 | module RsaChallenge
4 | module Encryption
5 | def decrypt(value)
6 | crypt(:decrypt, value).split('--')
7 | rescue OpenSSL::Cipher::CipherError
8 | nil
9 | end
10 |
11 | def crypt(cipher_method, value) #:nodoc:
12 | cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
13 | cipher.send(cipher_method)
14 | cipher.pkcs5_keyivgen(ENV['SECRET_TOKEN'])
15 | result = cipher.update(value)
16 | result << cipher.final
17 | end
18 | end
19 |
20 | # Create an RSA challenge encrypted with the given public key.
21 | #
22 | # Inspired by SSH RSA authentication, this class will generate a challenge
23 | # encrypted with the given public key. If the client returns an unencrypted
24 | # version of this challenge, then they have the private key.
25 | #
26 | # The challenge includes the user ID that owns this key and a secret only
27 | # known by the server. The challenge is symetrically encrypted with a secret
28 | # key, and then encrypted with the public key.
29 | class Request
30 | def initialize(key)
31 | @public_key = OpenSSL::PKey::RSA.new(key)
32 | end
33 |
34 | def value
35 | Base64.encode64 @public_key.public_encrypt(encrypted_challenge_values)
36 | end
37 |
38 | def user
39 | @user ||= User.with_public_key(@public_key)
40 | end
41 |
42 | private
43 |
44 | include Encryption
45 |
46 | def challenge_values
47 | [user.id, ENV["AUTH_SECRET"], Time.now.to_f]
48 | end
49 |
50 | def encrypted_challenge_values
51 | crypt :encrypt, challenge_values.join('--')
52 | end
53 | end
54 |
55 | # Verifies the challenge from the client.
56 | #
57 | # If the provided value can be decrypted with the secret key, then the user
58 | # posesses the private key and it is a valid challenge response.
59 | class Response
60 | include Encryption
61 |
62 | attr_reader :user_id
63 |
64 | def initialize(value)
65 | @value = value
66 | end
67 |
68 | def valid?
69 | @user_id, secret, time = decrypt(Base64.decode64(@value))
70 | secret == ENV["AUTH_SECRET"]
71 | end
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/app/models/share.rb:
--------------------------------------------------------------------------------
1 | class Share < ActiveRecord::Base
2 |
3 | include ActiveModel::ForbiddenAttributesProtection
4 |
5 | belongs_to :item
6 | belongs_to :user
7 |
8 | self.include_root_in_json = false
9 |
10 | end
11 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ActiveRecord::Base
2 |
3 | include ActiveModel::ForbiddenAttributesProtection
4 |
5 | has_many :shares
6 |
7 | self.include_root_in_json = false
8 |
9 | def self.with_public_key(key)
10 | where(:fingerprint => key.fingerprint).first ||
11 | create!(:public_key => key.to_s, :fingerprint => key.fingerprint)
12 | end
13 |
14 | end
15 |
--------------------------------------------------------------------------------
/app/presenters/item_list_presenter.rb:
--------------------------------------------------------------------------------
1 | class ItemListPresenter
2 | def initialize(user)
3 | @user = user
4 | end
5 |
6 | def shares
7 | @shares ||= Share.where(:user_id => @user.id).index_by(&:item_id)
8 | end
9 |
10 | def items
11 | @items ||= Item.where(:id => shares.keys)
12 | end
13 |
14 | def as_json(options = nil)
15 | items.map {|item| ItemPresenter.new(item, shares[item.id]) }.as_json(options)
16 | end
17 | end
--------------------------------------------------------------------------------
/app/presenters/item_presenter.rb:
--------------------------------------------------------------------------------
1 | class ItemPresenter
2 | def initialize(item, share)
3 | @item = item
4 | @share = share
5 | end
6 |
7 | def as_json(options = {})
8 | {
9 | 'id' => @item.id.to_s,
10 | 'title' => @item.title,
11 | 'encrypted_data' => @item.encrypted_data,
12 | 'key' => @share.key
13 | }
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/presenters/user_presenter.rb:
--------------------------------------------------------------------------------
1 | class UserPresenter
2 | def initialize(user)
3 | @user = user
4 | end
5 |
6 | def as_json(options = nil)
7 | {
8 | 'public_key' => @user.public_key
9 | }
10 | end
11 | end
--------------------------------------------------------------------------------
/app/views/dashboard/index.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for :head do %>
2 |
7 | <% end %>
8 |
--------------------------------------------------------------------------------
/app/views/dashboard/mockup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Swordfish
5 | <%= stylesheet_link_tag :mockup %>
6 | <%= javascript_include_tag :mockup %>
7 |
8 |
9 |
10 |
16 |
17 |
18 | My Vaults
19 | +
20 |
21 |
27 |
28 |
29 | Shared Vaults
30 | +
31 |
32 |
38 |
44 |
50 |
51 |
52 |
53 |
59 |
65 |
71 |
77 |
83 |
89 |
95 |
101 |
107 |
113 |
119 |
125 |
131 |
137 |
143 |
149 |
155 |
161 |
167 |
173 |
174 |
177 |
178 |
179 |
180 |
184 |
185 |
186 |
187 |
188 |
189 | Username brandon@opensoul.org
190 | Password ••••••••••
191 |
192 |
193 |
194 |
195 | Access Log
196 |
197 |
198 | Shawn Davenport
199 | July 25, 9:14am
200 | i
201 |
202 |
203 |
204 |
205 | IP 208.83.31.194
206 | User Agent Chrome 20.0.1132.57
207 | Location New Orleans, LA
208 |
209 |
210 |
211 |
212 | Brandon Keepers
213 | July 23, 3:14pm
214 | Info
215 |
216 |
217 |
218 |
219 |
220 | Edit
221 | Delete
222 |
223 |
224 |
225 |
226 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Swordfish
6 | <%= stylesheet_link_tag :application, :media => "all" %>
7 | <%= javascript_include_tag :application, :ui %>
8 | <%= yield :head %>
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Swordfish::Application
5 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require "rails/all"
4 |
5 | if defined?(Bundler)
6 | # If you precompile assets before deploying to production, use this line
7 | Bundler.require(*Rails.groups(:assets => %w(development test)))
8 | # If you want your assets lazily compiled in production, use this line
9 | # Bundler.require(:default, :assets, Rails.env)
10 | end
11 |
12 | module Swordfish
13 | class Application < Rails::Application
14 | # Settings in config/environments/* take precedence over those specified here.
15 | # Application configuration should go into files in config/initializers
16 | # -- all .rb files in that directory are automatically loaded.
17 |
18 | # Custom directories with classes and modules you want to be autoloadable.
19 | config.autoload_paths += [
20 | config.root.join('app', 'presenters')
21 | ]
22 |
23 | # Only load the plugins named here, in the order given (default is alphabetical).
24 | # :all can be used as a placeholder for all plugins not explicitly named.
25 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
26 |
27 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
28 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
29 | # config.time_zone = 'Central Time (US & Canada)'
30 |
31 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
32 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
33 | # config.i18n.default_locale = :de
34 |
35 | # Configure the default encoding used in templates for Ruby 1.9.
36 | config.encoding = "utf-8"
37 |
38 | # Configure sensitive parameters which will be filtered from the log file.
39 | config.filter_parameters += [:password, :passphrase]
40 |
41 | # Enable escaping HTML in JSON.
42 | config.active_support.escape_html_entities_in_json = true
43 |
44 | # Use SQL instead of Active Record's schema dumper when creating the database.
45 | # This is necessary if your schema can't be completely dumped by the schema dumper,
46 | # like if you have constraints or database-specific column types
47 | # config.active_record.schema_format = :sql
48 |
49 | # Enable the asset pipeline
50 | config.assets.enabled = true
51 |
52 | # Version of your assets, change this if you want to expire all your assets
53 | config.assets.version = '1.0'
54 |
55 | config.assets.initialize_on_precompile = false
56 | config.assets.precompile += %w( ui.js )
57 | config.assets.paths << Rails.root.join("vendor", "assets", "swf")
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 |
3 | # Set up gems listed in the Gemfile.
4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
5 |
6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
7 |
--------------------------------------------------------------------------------
/config/cucumber.yml:
--------------------------------------------------------------------------------
1 | <%
2 | rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
3 | rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
4 | std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip"
5 | %>
6 | default: <%= std_opts %> features
7 | wip: --tags @wip:3 --wip features
8 | rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip
9 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | defaults: &defaults
2 | adapter: postgresql
3 | encoding: unicode
4 | pool: 5
5 | host: localhost
6 | port: <%= ENV['GH_POSTGRESQL_PORT'] || 5432 %>
7 | username: <%= ENV['GH_POSTGRESQL_USER'] || ENV['USER'] %>
8 | min_messages: WARNING
9 |
10 | development:
11 | <<: *defaults
12 | database: swordfish_development
13 |
14 | test:
15 | <<: *defaults
16 | database: swordfish_test
17 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the rails application
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the rails application
5 | Swordfish::Application.initialize!
6 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Swordfish::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Log error messages when you accidentally call methods on nil.
10 | config.whiny_nils = true
11 |
12 | # Show full error reports and disable caching
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger
20 | config.active_support.deprecation = :log
21 |
22 | # Only use best-standards-support built into browsers
23 | config.action_dispatch.best_standards_support = :builtin
24 |
25 | # Do not compress assets
26 | config.assets.compress = false
27 |
28 | # Expands the lines which load the assets
29 | config.assets.debug = true
30 | end
31 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Swordfish::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # Code is not reloaded between requests
5 | config.cache_classes = true
6 |
7 | # Full error reports are disabled and caching is turned on
8 | config.consider_all_requests_local = false
9 | config.action_controller.perform_caching = true
10 |
11 | # Disable Rails's static asset server (Apache or nginx will already do this)
12 | config.serve_static_assets = false
13 |
14 | # Compress JavaScripts and CSS
15 | config.assets.compress = true
16 |
17 | # Don't fallback to assets pipeline if a precompiled asset is missed
18 | config.assets.compile = false
19 |
20 | # Generate digests for assets URLs
21 | config.assets.digest = true
22 |
23 | # Defaults to nil and saved in location specified by config.assets.prefix
24 | # config.assets.manifest = YOUR_PATH
25 |
26 | # Specifies the header that your server uses for sending files
27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
29 |
30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
31 | config.force_ssl = true
32 |
33 | # See everything in the log (default is :info)
34 | # config.log_level = :debug
35 |
36 | # Prepend all log lines with the following tags
37 | # config.log_tags = [ :subdomain, :uuid ]
38 |
39 | # Use a different logger for distributed setups
40 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
41 |
42 | # Use a different cache store in production
43 | # config.cache_store = :mem_cache_store
44 |
45 | # Enable serving of images, stylesheets, and JavaScripts from an asset server
46 | # config.action_controller.asset_host = "http://assets.example.com"
47 |
48 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
49 | # config.assets.precompile += %w( search.js )
50 |
51 | # Disable delivery errors, bad email addresses will be ignored
52 | # config.action_mailer.raise_delivery_errors = false
53 |
54 | # Enable threaded mode
55 | # config.threadsafe!
56 |
57 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
58 | # the I18n.default_locale when a translation can not be found)
59 | config.i18n.fallbacks = true
60 |
61 | # Send deprecation notices to registered listeners
62 | config.active_support.deprecation = :notify
63 |
64 | # Log the query plan for queries taking more than this (works
65 | # with SQLite, MySQL, and PostgreSQL)
66 | # config.active_record.auto_explain_threshold_in_seconds = 0.5
67 | end
68 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Swordfish::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Configure static asset server for tests with Cache-Control for performance
11 | config.serve_static_assets = true
12 | config.static_cache_control = "public, max-age=3600"
13 |
14 | # Log error messages when you accidentally call methods on nil
15 | config.whiny_nils = true
16 |
17 | # Show full error reports and disable caching
18 | config.consider_all_requests_local = true
19 | config.action_controller.perform_caching = false
20 |
21 | # Raise exceptions instead of rendering exception templates
22 | config.action_dispatch.show_exceptions = false
23 |
24 | # Disable request forgery protection in test environment
25 | config.action_controller.allow_forgery_protection = false
26 |
27 | # Tell Action Mailer not to deliver emails to the real world.
28 | # The :test delivery method accumulates sent emails in the
29 | # ActionMailer::Base.deliveries array.
30 | config.action_mailer.delivery_method = :test
31 |
32 | # Print deprecation notices to the stderr
33 | config.active_support.deprecation = :stderr
34 | end
35 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format
4 | # (all these examples are active by default):
5 | # ActiveSupport::Inflector.inflections do |inflect|
6 | # inflect.plural /^(ox)$/i, '\1en'
7 | # inflect.singular /^(ox)en/i, '\1'
8 | # inflect.irregular 'person', 'people'
9 | # inflect.uncountable %w( fish sheep )
10 | # end
11 | #
12 | # These inflection rules are supported but not enabled by default:
13 | # ActiveSupport::Inflector.inflections do |inflect|
14 | # inflect.acronym 'RESTful'
15 | # end
16 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 | # Make sure the secret is at least 30 characters and all random,
6 | # no regular words or you'll be exposed to dictionary attacks.
7 | Swordfish::Application.config.secret_token = ENV['SECRET_TOKEN']
8 |
--------------------------------------------------------------------------------
/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Swordfish::Application.config.session_store :cookie_store, :key => '_passwords_session'
4 |
5 | # Use the database for sessions instead of the cookie-based default,
6 | # which shouldn't be used to store highly confidential information
7 | # (create the session table with "rails generate session_migration")
8 | # Swordfish::Application.config.session_store :active_record_store
9 |
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters :format => [:json]
9 | end
10 |
11 | # Disable root element in JSON by default.
12 | ActiveSupport.on_load(:active_record) do
13 | self.include_root_in_json = false
14 | end
15 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Sample localization file for English. Add more files in this directory for other locales.
2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 |
4 | en:
5 | hello: "Hello world"
6 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Swordfish::Application.routes.draw do
2 | resources :items
3 |
4 | namespace :auth do
5 | resource :rsa, :only => [:create, :update], :controller => 'rsa'
6 | end
7 |
8 | match 'mockup', :to => 'dashboard#mockup'
9 | root :to => 'dashboard#index'
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20130120120320_create_items.rb:
--------------------------------------------------------------------------------
1 | class CreateItems < ActiveRecord::Migration
2 | def change
3 | create_table :items do |t|
4 | t.string :title
5 | t.string :encrypted_data
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20130120120408_create_shares.rb:
--------------------------------------------------------------------------------
1 | class CreateShares < ActiveRecord::Migration
2 | def change
3 | create_table :shares do |t|
4 | t.integer :item_id
5 | t.integer :user_id
6 | t.string :key
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20130120120503_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration
2 | def change
3 | create_table :users do |t|
4 | t.string :public_key
5 | t.string :fingerprint
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20130212195016_change_public_key_to_text.rb:
--------------------------------------------------------------------------------
1 | class ChangePublicKeyToText < ActiveRecord::Migration
2 | def change
3 | change_column :users, :public_key, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # This file is auto-generated from the current state of the database. Instead
3 | # of editing this file, please use the migrations feature of Active Record to
4 | # incrementally modify your database, and then regenerate this schema definition.
5 | #
6 | # Note that this schema.rb definition is the authoritative source for your
7 | # database schema. If you need to create the application database on another
8 | # system, you should be using db:schema:load, not running all the migrations
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 | # you'll amass, the slower it'll run and the greater likelihood for issues).
11 | #
12 | # It's strongly recommended to check this file into your version control system.
13 |
14 | ActiveRecord::Schema.define(:version => 20130212195016) do
15 |
16 | create_table "items", :force => true do |t|
17 | t.string "title"
18 | t.string "encrypted_data"
19 | t.datetime "created_at", :null => false
20 | t.datetime "updated_at", :null => false
21 | end
22 |
23 | create_table "shares", :force => true do |t|
24 | t.integer "item_id"
25 | t.integer "user_id"
26 | t.string "key"
27 | t.datetime "created_at", :null => false
28 | t.datetime "updated_at", :null => false
29 | end
30 |
31 | create_table "users", :force => true do |t|
32 | t.text "public_key"
33 | t.string "fingerprint"
34 | t.datetime "created_at", :null => false
35 | t.datetime "updated_at", :null => false
36 | end
37 |
38 | end
39 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
7 | # Mayor.create(:name => 'Emanuel', :city => cities.first)
8 |
--------------------------------------------------------------------------------
/docs/DataFormat.md:
--------------------------------------------------------------------------------
1 | # Data Format
2 |
3 | ## Items
4 |
5 | Items are persisted in a MongoDB collection called `items`, with insecure data stored in plain text, and secure data stored as an encrypted blob in the `data` attribute.
6 |
7 | {
8 | id: ObjectId("…"),
9 | title: "GitHub",
10 | data: "[JSON HASH, ENCRYPTED WITH RANDOM KEY]"
11 | }
12 |
13 | The data attribute is encrypted using a random key generated on the client.
14 |
15 | ## Shares
16 |
17 | When an item is created, the randomly generated item key is encrypted with the creator's public key and persisted in the `shares` collection.
18 |
19 | {
20 | item_id: ObjectId("…"),
21 | user_id: ObjectId("…"),
22 | key: "[RANDOM ITEM KEY ENCRYPTED WITH USER'S PUBLIC KEY]"
23 | }
24 |
25 | Sharing an item with another user is as simple as decrypting the *item key* with your own private key and re-incrypting it with their public key.
--------------------------------------------------------------------------------
/docs/core.md:
--------------------------------------------------------------------------------
1 | # Core Team
2 |
3 | Swordfish is built and maintained by volunteers with a desire to increase security amongst teams without adding unnecessary overhead for individuals.
4 |
5 | ## Areas of Need
6 |
7 | There are several areas of expertise needed to make Swordfish successful.
8 |
9 | * **JavaScript** - Most of the front application is written in JavaScript (well, CoffeeScript), built around [Backbone.js](http://backbonejs.org) and uses many other libraries.
10 | * **Ruby** - The API is a relatively small Ruby on Rails application, tested with RSpec and Cucumber, with data persisted in MongoDB.
11 | * **Design**
12 | * **Cryptography** - The application uses AES to encrypt items, and then encrypts the item key with an RSA key for each user.
13 | * **Security** - Strong cryptography is useless if there are simple exploits in other parts of the application.
14 |
15 | ## Responsibilities
16 |
17 | * Review, discuss and merge [pull requests](https://github.com/github/swordfish/pulls)
18 | * Offer feedback on [Issues](https://github.com/github/swordfish/issues) and [mailing list discussions](https://groups.google.com/group/swordfishapp)
19 | * Be a decent human being
20 | * Give a damn
21 |
22 | ## Joining the team
23 |
24 | Send a 2,000 word essay on why you think you would be a good fit for the core team, along with a portrait and profile photograph to…just kidding.
25 |
26 | Just get involved. Send a pull request, comment on an issue, or participate in a discussion. If you are still interested in joining the core team after you've been involved for a few weeks, then get in touch with [Brandon Keepers](http://github.com/bkeepers).
27 |
28 | ## Members
29 |
30 | * [Brandon Keepers](http://github.com/bkeepers)
31 |
--------------------------------------------------------------------------------
/lib/tasks/brakeman.rake:
--------------------------------------------------------------------------------
1 | namespace :brakeman do
2 |
3 | desc "Run Brakeman"
4 | task :run, :output_files do |t, args|
5 | require 'brakeman'
6 |
7 | files = args[:output_files].split(' ') if args[:output_files]
8 | Brakeman.run(
9 | :app_path => ".",
10 | :output_files => files,
11 | :print_report => true,
12 | :skip_checks => ['CheckForgerySetting'],
13 | :exit_on_warn => true
14 | )
15 | end
16 | end
17 |
18 | task :default => 'brakeman:run'
19 |
--------------------------------------------------------------------------------
/lib/tasks/jasmine.rake:
--------------------------------------------------------------------------------
1 | begin
2 | require 'guard/jasmine/task'
3 | Guard::JasmineTask.new
4 | rescue LoadError
5 | end
--------------------------------------------------------------------------------
/log/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/log/.gitkeep
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The page you were looking for doesn't exist.
23 |
You may have mistyped the address or the page may have moved.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The change you wanted was rejected.
23 |
Maybe you tried to change something you didn't have access to.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
We're sorry, but something went wrong.
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-Agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/script/bootstrap:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Make sure all our deps exist.
3 |
4 | # Export CC to explicitly set the compiler used for cexts.
5 |
6 | export CC=cc
7 |
8 | # Generate .bundle/config instead of using env vars or command line
9 | # flags so that we have consistent configuration for our `bundle
10 | # check` and `bundle install` calls here, as well as any manual calls
11 | # to `bundle` that people might make.
12 |
13 | # We don't want old config hanging around right now. This is sorta
14 | # jank: We can do better.
15 |
16 | rm -rf .bundle
17 | mkdir .bundle
18 |
19 | echo "---
20 | BUNDLE_BIN: bin
21 | BUNDLE_DISABLE_SHARED_GEMS: "1"
22 | BUNDLE_PATH: vendor/gems
23 | BUNDLE_WITHOUT: staging:production
24 | " > .bundle/config
25 |
26 | # Bundle install unless we're already up to date.
27 |
28 | bundle check > /dev/null 2>&1 || bundle install "$@"
29 |
--------------------------------------------------------------------------------
/script/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3 |
4 | APP_PATH = File.expand_path('../../config/application', __FILE__)
5 | require File.expand_path('../../config/boot', __FILE__)
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/spec/acceptance/item_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | feature 'Items' do
4 | before(:each) do
5 | generate_key
6 | visit root_path
7 | unlock_key
8 | end
9 |
10 | scenario 'creating and editing an item' do
11 | create_item(
12 | "Title" => "example.com",
13 | "Username" => "myusername",
14 | "Password" => "mypassword",
15 | "Confirm" => "mypassword"
16 | )
17 |
18 | within('#items') { expect(page).to have_content('example.com') }
19 |
20 | visit root_path
21 | unlock_key
22 | expect(page).to have_content("example.com")
23 | click_link "example.com"
24 | expect(page).to have_content("myusername")
25 |
26 | click_link "reveal"
27 | expect(page).to have_content("mypassword")
28 | click_link "hide"
29 | expect(page).to have_no_content("mypassword")
30 |
31 | click_link "Edit"
32 | expect(find_field("Title").value).to eql("example.com")
33 | expect(find_field("Username").value).to eql("myusername")
34 | expect(find_field("Password").value).to eql("mypassword")
35 | expect(find_field("Confirm").value).to eql("mypassword")
36 |
37 | fill_in "Title", :with => "Example"
38 | fill_in "Username", :with => "updated-username"
39 | click_button "Save"
40 |
41 | within('#items') { expect(page).to have_content('Example') }
42 | expect(page).to have_content("updated-username")
43 | end
44 |
45 | scenario 'mismatching password confirmation' do
46 | click_link "+"
47 | fill_in "Title", :with => "example.com"
48 | fill_in "Username", :with => "myusername"
49 | fill_in "Password", :with => "mypassword"
50 | click_button "Create"
51 | expect(page).to have_no_content('example.com')
52 |
53 | fill_in "Password", :with => ""
54 | fill_in "Confirm", :with => "mypassword"
55 | click_button "Create"
56 | expect(page).to have_no_content('example.com')
57 | end
58 |
59 | scenario 'requires matching passwords on update' do
60 | create_item
61 |
62 | click_link 'Edit'
63 | fill_in "Password", :with => "newpassword"
64 | click_button "Save"
65 | expect(page).to have_content('Save')
66 |
67 | fill_in "Confirm", :with => "newpassword"
68 | click_button "Save"
69 | click_link "reveal"
70 | expect(page).to have_content("newpassword")
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/spec/acceptance/key_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | feature 'Key Management' do
4 | scenario 'Create key to sign up' do
5 | visit root_path
6 |
7 | expect(page).to have_content('Create a Key')
8 |
9 | fill_in 'passphrase', :with => 'this is my secret'
10 | click_button 'Generate Key'
11 |
12 | wait_forever do
13 | expect(page).to have_content('Download Private Key')
14 | end
15 |
16 | click_link 'Done'
17 | expect(page).to be_unlocked
18 | end
19 |
20 | scenario 'Upload key to sign up' do
21 | visit root_path
22 | expect(page).to have_content('Load existing key')
23 |
24 | click_link 'Load existing key'
25 | attach_file 'key', 'spec/fixtures/priv.pem'
26 | fill_in 'passphrase', :with => 'testing'
27 | click_button 'Unlock'
28 | expect(page).to be_unlocked
29 |
30 | # Ensure item creation works with loaded key
31 | create_item 'Title' => 'Loaded Key'
32 | expect(page).to have_content('Loaded Key')
33 | end
34 |
35 | scenario 'Unlocking key' do
36 | generate_key
37 | visit root_path
38 | unlock_key
39 | expect(page).to be_unlocked
40 | end
41 |
42 | scenario 'Failing to unlock key' do
43 | generate_key
44 | visit root_path
45 |
46 | unlock_key 'wrong passphrase'
47 | expect(page).to have_content('Your passphrase was incorrect!')
48 |
49 | unlock_key
50 | expect(page).to be_unlocked
51 | end
52 |
53 | scenario 'Locking key' do
54 | generate_key
55 | visit root_path
56 | unlock_key
57 |
58 | click_link 'Lock'
59 |
60 | expect(page).to have_content('Unlock')
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/spec/controllers/auth/rsa_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Auth::RsaController do
4 | let(:public_key) { fixture('pub.pem') }
5 |
6 | describe 'create' do
7 | subject do
8 | raw_post :create, public_key
9 | end
10 |
11 | it 'responds with 200' do
12 | expect(subject).to be_success
13 | end
14 |
15 | it 'responds with text/plain content type' do
16 | expect(subject.content_type).to eq('text/plain')
17 | end
18 |
19 | it 'returns a challenge' do
20 | challenge = mock(:value => "foo")
21 | RsaChallenge::Request.should_receive(:new).and_return(challenge)
22 | expect(subject.body).to eql("foo")
23 | end
24 | end
25 |
26 | describe 'update' do
27 | let(:user) { User.create!(:public_key => public_key) }
28 |
29 | context 'with a valid response' do
30 | before do
31 | challenge = mock(:valid? => true, :user_id => user.id)
32 | RsaChallenge::Response.stub(:new).and_return(challenge)
33 | end
34 |
35 | subject do
36 | raw_post :update, 'valid'
37 | end
38 |
39 | it 'returns 200' do
40 | expect(subject).to be_success
41 | end
42 | end
43 |
44 | context 'with an invalid response' do
45 | before do
46 | challenge = mock(:valid? => false)
47 | RsaChallenge::Response.stub(:new).and_return(challenge)
48 | end
49 |
50 | subject do
51 | raw_post :update, 'invalid'
52 | end
53 |
54 | it 'returns 401' do
55 | expect(subject.status).to eq(401)
56 | end
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/spec/controllers/items_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe ItemsController do
4 | context 'when signed in' do
5 | before { sign_in_as mock_user }
6 |
7 | describe 'create' do
8 | subject do
9 | post :create,
10 | :title => 'example.com',
11 | :data => 'encrypted',
12 | :key => 'userkey'
13 | end
14 |
15 | it { expect(subject.status).to be(201) }
16 |
17 | it 'creates an item' do
18 | subject
19 | expect(Item.where(:title => 'example.com' ).first).to be_instance_of(Item)
20 | end
21 | end
22 |
23 | describe 'index' do
24 | subject do
25 | get :index
26 | end
27 |
28 | it { expect(subject.status).to be(200) }
29 | end
30 |
31 | describe 'update' do
32 | let(:item) { Item.create! }
33 |
34 | subject do
35 | put :update, :id => item.id.to_s, :title => 'Updated'
36 | end
37 |
38 | context 'when user has access' do
39 | before do
40 | item.share_with current_user, 'key'
41 | end
42 |
43 | it { expect(subject.status).to be(200) }
44 |
45 | it 'updates item' do
46 | subject
47 | item.reload
48 | expect(item.title).to eql('Updated')
49 | end
50 | end
51 |
52 | context 'when user does not have access' do
53 | it { expect { subject.status }.to raise_error(ActiveRecord::RecordNotFound) }
54 | end
55 | end
56 | end
57 |
58 | context 'when signed out' do
59 | it { expect(post(:create, :format => :json).status).to be(401) }
60 | end
61 | end
--------------------------------------------------------------------------------
/spec/fixtures/priv.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN ENCRYPTED PRIVATE KEY-----
2 | MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIcr+AY81p67ACAggA
3 | MB0GCWCGSAFlAwQBAgQQ+MZ40SX/K9W8nxetEKPfHQSCAoBSGqUKsTPXNX/qpAoD
4 | cn9DvWZcCUQpWiLCtuaYp8trh51/OG8dj2+Mwq/xFFH+qlDM/QZZ2lEUEHSiDDOi
5 | ojBxtGiIDEKsHKDt9RKXij1FhVfW8TLaTYNoIJS1CnU3L+MG0sEAMyiReKJgPaBO
6 | uDYZ5aSfscPQ2BElzjOAEciWCF7Tc+8rn32pf4yGL2hximTxBGSVDiQdefKFH32C
7 | sujw5yzkHBStU5J4nlMYax7+yG48SecpP+EaR2n3kVWfM6Y1y85RLsVX4Yw91d5c
8 | 02q6/TUggAwHbAuBBC/3u6+S+sTXRqx+oXkRdw/0sGOAEUViDzwPBkiK01Tk44Uy
9 | VsyHHG4u/Hk0/lLspv++Ee9BL4+KIWjZImvnJ7t6f3UnwdA9nLp5wfrdf13F+ENW
10 | 1C3Gv+stu6SPJkpmo15BrXkM5gJtFrDn35kqGmvT+YA0TfRG8FI46LbwjbUDJhQ9
11 | S/g9Ks0Ps4MoyZpzi+3QD7DyxcnZ1wRGsPtTgIK4XKqQDTxvboe7/6d/D7TLRLcS
12 | 99GuC7QfcfH6O0yH4C1NJ/6qtphY8yLNsodg61qNd1CL/tgpEzDWaDWngMg8To0h
13 | 4elzhixvU9baXvTuQFvyCLlkh5AGCOJgqZChWkR64G+izvodUfjEggDpKfjEcNKy
14 | wITPbTHg0x6ibl72XGXiCU8+D4baxvThniwdMJAEWqmB3+2cvlU6dHkQzRxZHBEM
15 | dXzdv9xtiQfkuiMEyaYS7+72/GQ2ghSdZBUxB0qqq5ZbCz4AGLc/nGeP+DH6q57u
16 | ws8SDVgntQ9CB3mFaCyQQbRPhxcq1mNEnuJkDrZfrL8S7Nmi7anWPccTnZJ+97Qr
17 | yu9t
18 | -----END ENCRYPTED PRIVATE KEY-----
19 |
--------------------------------------------------------------------------------
/spec/fixtures/pub.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCAXY6jq67YAXSzlZtpwO0xe3/k
3 | ZAEfW7roIdCQEDWGneUdnwer+EfkTA/7wQnZxq993x7DzhaWHeUnanFFkjFn+mDb
4 | lOTowPaLWl9CYo7JLCRY3hSgT9qci2VcBCqIsP7JJAdS/PGmKMNDZufnblqlfzfU
5 | 2shYSEmynVDgiDQITQIDAQAB
6 | -----END PUBLIC KEY-----
--------------------------------------------------------------------------------
/spec/javascripts/fixtures/priv.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN ENCRYPTED PRIVATE KEY-----
2 | MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIcr+AY81p67ACAggA
3 | MB0GCWCGSAFlAwQBAgQQ+MZ40SX/K9W8nxetEKPfHQSCAoBSGqUKsTPXNX/qpAoD
4 | cn9DvWZcCUQpWiLCtuaYp8trh51/OG8dj2+Mwq/xFFH+qlDM/QZZ2lEUEHSiDDOi
5 | ojBxtGiIDEKsHKDt9RKXij1FhVfW8TLaTYNoIJS1CnU3L+MG0sEAMyiReKJgPaBO
6 | uDYZ5aSfscPQ2BElzjOAEciWCF7Tc+8rn32pf4yGL2hximTxBGSVDiQdefKFH32C
7 | sujw5yzkHBStU5J4nlMYax7+yG48SecpP+EaR2n3kVWfM6Y1y85RLsVX4Yw91d5c
8 | 02q6/TUggAwHbAuBBC/3u6+S+sTXRqx+oXkRdw/0sGOAEUViDzwPBkiK01Tk44Uy
9 | VsyHHG4u/Hk0/lLspv++Ee9BL4+KIWjZImvnJ7t6f3UnwdA9nLp5wfrdf13F+ENW
10 | 1C3Gv+stu6SPJkpmo15BrXkM5gJtFrDn35kqGmvT+YA0TfRG8FI46LbwjbUDJhQ9
11 | S/g9Ks0Ps4MoyZpzi+3QD7DyxcnZ1wRGsPtTgIK4XKqQDTxvboe7/6d/D7TLRLcS
12 | 99GuC7QfcfH6O0yH4C1NJ/6qtphY8yLNsodg61qNd1CL/tgpEzDWaDWngMg8To0h
13 | 4elzhixvU9baXvTuQFvyCLlkh5AGCOJgqZChWkR64G+izvodUfjEggDpKfjEcNKy
14 | wITPbTHg0x6ibl72XGXiCU8+D4baxvThniwdMJAEWqmB3+2cvlU6dHkQzRxZHBEM
15 | dXzdv9xtiQfkuiMEyaYS7+72/GQ2ghSdZBUxB0qqq5ZbCz4AGLc/nGeP+DH6q57u
16 | ws8SDVgntQ9CB3mFaCyQQbRPhxcq1mNEnuJkDrZfrL8S7Nmi7anWPccTnZJ+97Qr
17 | yu9t
18 | -----END ENCRYPTED PRIVATE KEY-----
19 |
--------------------------------------------------------------------------------
/spec/javascripts/fixtures/pub.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCAXY6jq67YAXSzlZtpwO0xe3/k
3 | ZAEfW7roIdCQEDWGneUdnwer+EfkTA/7wQnZxq993x7DzhaWHeUnanFFkjFn+mDb
4 | lOTowPaLWl9CYo7JLCRY3hSgT9qci2VcBCqIsP7JJAdS/PGmKMNDZufnblqlfzfU
5 | 2shYSEmynVDgiDQITQIDAQAB
6 | -----END PUBLIC KEY-----
--------------------------------------------------------------------------------
/spec/javascripts/models/item_key_spec.coffee:
--------------------------------------------------------------------------------
1 | #= require forge
2 | #= require models/item_key
3 |
4 | describe 'ItemKey', ->
5 | beforeEach ->
6 | @key = new ItemKey(ItemKey.generate())
7 |
8 | describe 'encrypt/decrypt', ->
9 | it 'encrypts and decrypts the data', ->
10 | expect(@key.decrypt(@key.encrypt('topsecret'))).toEqual('topsecret')
11 |
--------------------------------------------------------------------------------
/spec/javascripts/models/item_spec.coffee:
--------------------------------------------------------------------------------
1 | #= require models
2 |
3 | describe 'Item', ->
4 | beforeEach ->
5 | @keypair =
6 | encrypt: jasmine.createSpy('encrypt').andReturn('encrypted')
7 | decrypt: jasmine.createSpy('decrypt').andReturn('decrypted')
8 |
9 | @collection = {keypair: @keypair, url:'/items'}
10 | spyOn(jQuery, 'ajax')
11 |
12 | describe 'initialize', ->
13 | it 'generates a key', ->
14 | spyOn(ItemKey, 'generate').andReturn('generated key')
15 |
16 | item = new Item({}, collection: @collection)
17 | expect(item.get('key')).toEqual('encrypted')
18 | expect(ItemKey.generate).toHaveBeenCalled()
19 | expect(@keypair.encrypt).toHaveBeenCalledWith('generated key')
20 |
21 | it 'does not override existing key', ->
22 | item = new Item({key: 'existing key'}, collection: @collection)
23 | expect(item.get('key')).toEqual('existing key')
24 |
25 | describe 'set', ->
26 | beforeEach ->
27 | spyOn(ItemKey.prototype, 'encrypt').andReturn('encrypted-data')
28 | @item = new Item({}, {collection: @collection})
29 | @item.set(data: {foo: 'bar'})
30 |
31 | it 'encrypts data', ->
32 | expect(@item.get('encrypted_data')).toEqual('encrypted-data')
33 |
34 | it 'clears unencrypted data', ->
35 | expect(@item.get('data')).toBe(undefined)
36 | expect(@item.toJSON().data).toBe(undefined)
37 |
38 |
--------------------------------------------------------------------------------
/spec/javascripts/models/keypair_authenticator_spec.coffee:
--------------------------------------------------------------------------------
1 | #= require jquery
2 | #= require underscore
3 | #= require forge
4 | #= require models/keypair
5 | #= require models/keypair_authenticator
6 |
7 | describe 'KeypairAuthenticator', ->
8 | beforeEach ->
9 | @deferred = jQuery.Deferred()
10 | KeypairAuthenticator.ajax = @ajax = jasmine.createSpy('ajax').andReturn(@deferred)
11 |
12 | @keypair = new Keypair("key")
13 | spyOn(@keypair, 'publicKeyPem').andReturn('public key')
14 |
15 | @authenticator = new KeypairAuthenticator(@keypair)
16 | spyOn(@authenticator, 'decryptChallenge').andReturn('decrypted challenge')
17 |
18 | describe 'request', ->
19 | it 'gets challenge from the server', ->
20 | @authenticator.request()
21 |
22 | request = @ajax.mostRecentCall.args[0]
23 |
24 | expect(request.url).toEqual('/auth/rsa')
25 | expect(request.type).toEqual('POST')
26 | expect(request.data).toEqual('public key')
27 |
28 | it 'returns authenticator', ->
29 | expect(@authenticator.request()).toBe(@authenticator)
30 |
31 | describe 'respond', ->
32 | it 'sends decrypted challenge response to server', ->
33 | @authenticator.request()
34 | @deferred.resolve('encrypted data')
35 |
36 | request = @ajax.mostRecentCall.args[0]
37 |
38 | expect(request.url).toEqual('/auth/rsa')
39 | expect(request.type).toEqual('PUT')
40 | expect(request.data).toEqual('decrypted challenge')
41 |
42 | it 'resolves with successful challenge', ->
43 | callback = jasmine.createSpy('done callback')
44 | @authenticator.done callback
45 | @authenticator.request()
46 | @deferred.resolve()
47 |
48 | expect(callback).toHaveBeenCalledWith('decrypted challenge')
49 |
--------------------------------------------------------------------------------
/spec/javascripts/models/keypair_spec.coffee:
--------------------------------------------------------------------------------
1 | #= require jquery
2 | #= require forge
3 | #= require models/keypair
4 |
5 | describe 'Keypair', ->
6 | beforeEach ->
7 | Keypair.ajax = @ajax = jasmine.createSpy('ajax')
8 | Keypair.localStorage = @local = {}
9 |
10 | @privateKey = readFixtures('priv.pem.txt')
11 | @keypair = new Keypair(@privateKey)
12 |
13 | describe 'savePrivateKey', ->
14 | it 'saves private key to local storage', ->
15 | @keypair.savePrivateKey()
16 | expect(@local['privateKey']).toEqual(@privateKey)
17 |
18 | describe 'load', ->
19 | describe 'when keypair is not in local storage', ->
20 | it 'returns falsy', ->
21 | expect(Keypair.load()).not.toBeTruthy()
22 |
23 | describe 'when private key is set', ->
24 | beforeEach -> @local['privateKey'] = @privateKey
25 |
26 | it 'returns keypair with public/private key', ->
27 | keypair = Keypair.load(@privateKey)
28 | expect(keypair).toBeTruthy()
29 | expect(keypair.privateKeyPem).toBe(@privateKey)
30 |
31 | describe 'unlock', ->
32 | describe 'with the correct password', ->
33 | it 'returns true', ->
34 | expect(@keypair.unlock('testing')).toBe(true)
35 |
36 | it 'sets public key', ->
37 | @keypair.unlock('testing')
38 | expect(@keypair.publicKey).toBeTruthy()
39 | expect(@keypair.publicKey.encrypt).toBeTruthy()
40 |
41 | describe 'with an incorrect password', ->
42 | it 'returns false', ->
43 | expect(@keypair.unlock('wrong password')).toBe(false)
44 |
--------------------------------------------------------------------------------
/spec/javascripts/routers/item_router_spec.coffee:
--------------------------------------------------------------------------------
1 | #= require forge
2 | #= require application
3 | #= require models/keypair
4 | #= require routers/item_router
5 |
6 | describe 'ItemRouter', ->
7 | describe 'items', ->
8 | it 'should set the keypair on the collection if it does not exist', ->
9 | app = Application
10 | key1 = new Keypair("private1")
11 | app.keypair = key1
12 | router = new ItemRouter(app: app)
13 | router.items()
14 | expect(router.itemsCollection.keypair).toEqual(key1)
15 |
16 | it 'should set the keypair on the collection to the new key if it does exist', ->
17 | app = Application
18 | key1 = new Keypair("private1")
19 | key2 = new Keypair("private2")
20 | app.keypair = key1
21 | router = new ItemRouter(app: app)
22 | router.items()
23 | # Key1 should be set properly on the collection
24 | expect(router.itemsCollection.keypair).toEqual(key1)
25 | # Simulate the user changing keypairs
26 | app.keypair = key2
27 | router.items()
28 | expect(router.itemsCollection.keypair).toEqual(key2)
29 |
--------------------------------------------------------------------------------
/spec/javascripts/spec.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/spec/javascripts/spec.css
--------------------------------------------------------------------------------
/spec/javascripts/spec.js.coffee:
--------------------------------------------------------------------------------
1 | #= require application
2 | #= require_tree ./
--------------------------------------------------------------------------------
/spec/javascripts/views/item/create_spec.coffee:
--------------------------------------------------------------------------------
1 | #= require application
2 | #= require ui
3 |
4 | describe 'Item.Views.Create', ->
5 | beforeEach ->
6 | @collection = {create: jasmine.createSpy('create')}
7 | @view = new Item.Views.Create(collection: @collection)
8 | @event = {preventDefault: jasmine.createSpy('preventDefault')}
9 |
10 | describe 'submit', ->
11 | it 'creates an item', ->
12 | @view.submit(@event)
13 | expect(@collection.create).toHaveBeenCalled()
14 |
15 | it 'cancels the normal form submission', ->
16 | @view.submit(@event)
17 | expect(@event.preventDefault).toHaveBeenCalled()
18 |
19 | it 'does not create an item if the passwords do not match', ->
20 | @view.passwordConfirmed = -> false
21 | @view.submit(@event)
22 | expect(@collection.create).not.toHaveBeenCalled()
23 |
24 | it 'cancels the normal form submission if the passwords do not match', ->
25 | @view.passwordConfirmed = -> false
26 | @view.submit(@event)
27 | expect(@event.preventDefault).toHaveBeenCalled()
28 |
--------------------------------------------------------------------------------
/spec/models/item_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Item do
4 | let(:item) { Item.new }
5 | let(:user) { User.new }
6 |
7 | describe 'share_with' do
8 | it 'creates a share' do
9 | share = item.share_with(user, 'mykey')
10 | expect(share).to be_instance_of(Share)
11 | expect(share).not_to be_new_record
12 | expect(share.key).to eql('mykey')
13 | end
14 | end
15 | end
--------------------------------------------------------------------------------
/spec/models/rsa_challenge_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe RsaChallenge do
4 | let(:public_key) { fixture('pub.pem') }
5 |
6 | describe RsaChallenge::Request do
7 | subject do
8 | RsaChallenge::Request.new(public_key)
9 | end
10 |
11 | it 'finds user' do
12 | user = mock(:user)
13 | User.should_receive(:with_public_key).and_return(user)
14 | expect(subject.user).to eql(user)
15 | end
16 | end
17 |
18 | describe RsaChallenge::Response do
19 | let(:user) { double(:user, :id => 42) }
20 | let(:challenge) { RsaChallenge::Request.new(public_key) }
21 |
22 | before do
23 | User.stub!(:with_public_key).and_return(user)
24 | end
25 |
26 | describe 'valid?' do
27 | context 'with a decrypted challenge' do
28 | before do
29 | priv = OpenSSL::PKey::RSA.new(fixture('priv.pem'), 'testing')
30 | value = Base64.decode64(challenge.value)
31 | @decrypted = Base64.encode64(priv.private_decrypt(value))
32 | end
33 |
34 | subject do
35 | RsaChallenge::Response.new(@decrypted)
36 | end
37 |
38 | it 'returns true' do
39 | expect(subject.valid?).to be_true
40 | end
41 |
42 | it 'sets user_id' do
43 | expect(subject.user_id).to be_nil
44 | subject.valid?
45 | expect(subject.user_id).to eql(user.id.to_s)
46 | end
47 | end
48 |
49 | context 'with an invalid challenge' do
50 | it 'returns false' do
51 | response = RsaChallenge::Response.new(challenge.value)
52 | expect(response.valid?).to be_false
53 | end
54 | end
55 | end
56 | end
57 | end
--------------------------------------------------------------------------------
/spec/models/user_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe User do
4 | describe '.with_public_key' do
5 | context 'when the user does not exist' do
6 | it 'sets fingerprint before saving' do
7 | key = double(:key, :fingerprint => 'fingerprint', :to_s => 'key')
8 | user = User.with_public_key(key)
9 | expect(user.fingerprint).to be_present
10 | end
11 | end
12 | end
13 |
14 | end
--------------------------------------------------------------------------------
/spec/presenters/item_list_presenter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe ItemListPresenter do
4 | let(:user) { mock_user }
5 | subject { ItemListPresenter.new(user) }
6 |
7 | describe 'items' do
8 | let(:item) { Item.create! }
9 |
10 | it 'includes items shared with user' do
11 | item.share_with(user, 'key')
12 | expect(subject.items).to include(item)
13 | end
14 |
15 | it 'does not include items not shared' do
16 | expect(subject.items).not_to include(item)
17 | end
18 | end
19 |
20 | describe 'as_json' do
21 | let(:item) { Item.create!(:title => 'example.com') }
22 |
23 | before do
24 | item.share_with(user, 'thekey')
25 | end
26 |
27 | it 'includes item attributes' do
28 | item = subject.as_json.first
29 | expect(item['title']).to eql('example.com')
30 | end
31 |
32 | it 'includes key' do
33 | expect(subject.as_json.first['key']).to eql('thekey')
34 | end
35 | end
36 | end
--------------------------------------------------------------------------------
/spec/presenters/item_presenter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe ItemPresenter do
4 | let(:item) { double(:item, :id => BSON::ObjectId.new).as_null_object }
5 | let(:presenter) { ItemPresenter.new(item) }
6 | end
--------------------------------------------------------------------------------
/spec/presenters/user_presenter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe UserPresenter do
4 | let(:user) { mock_user }
5 | subject { UserPresenter.new(user) }
6 |
7 | describe 'to_json' do
8 | it 'includes public_key' do
9 | expect(subject.as_json).to have_key('public_key')
10 | end
11 | end
12 | end
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # This file is copied to spec/ when you run 'rails generate rspec:install'
2 | ENV["RAILS_ENV"] ||= 'test'
3 | require File.expand_path("../../config/environment", __FILE__)
4 | require 'rspec/rails'
5 | require 'rspec/autorun'
6 |
7 | # Requires supporting ruby files with custom matchers and macros, etc,
8 | # in spec/support/ and its subdirectories.
9 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
10 |
11 | RSpec.configure do |config|
12 | # If true, the base class of anonymous controllers will be inferred
13 | # automatically. This will be the default behavior in future versions of
14 | # rspec-rails.
15 | config.infer_base_class_for_anonymous_controllers = false
16 |
17 | # Run specs in random order to surface order dependencies. If you find an
18 | # order dependency and want to debug it, you can fix the order by providing
19 | # the seed, which is printed after each run.
20 | # --seed 1234
21 | config.order = "random"
22 |
23 | config.expect_with(:rspec) { |c| c.syntax = :expect }
24 |
25 | config.use_transactional_fixtures = false
26 |
27 | config.before do
28 | DatabaseCleaner.strategy = :truncation
29 | end
30 |
31 | config.before do
32 | DatabaseCleaner.start
33 | end
34 |
35 | config.after do
36 | DatabaseCleaner.clean
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/spec/support/acceptance.rb:
--------------------------------------------------------------------------------
1 | require 'capybara/rspec'
2 | require 'capybara/poltergeist'
3 |
4 | Capybara.default_driver = ENV['SELENIUM'] ? :selenium : :poltergeist
5 | Capybara.register_driver :selenium do |app|
6 | Capybara::Selenium::Driver.new(app, :browser => :chrome)
7 | end
8 |
9 | module AcceptanceSpecHelpers
10 | def generate_key
11 | User.create!(:public_key => fixture('pub.pem'))
12 | visit '/'
13 | page.execute_script "localStorage['privateKey'] = #{fixture('priv.pem').to_json}"
14 | end
15 |
16 | def unlock_key(passphrase = 'testing')
17 | fill_in 'passphrase', :with => passphrase
18 | click_button 'Unlock'
19 | end
20 |
21 | def create_item(attrs = {})
22 | click_link '+'
23 | item_attrs(attrs).each do |key,value|
24 | fill_in key, :with => value
25 | end
26 | click_button 'Create'
27 | end
28 |
29 | def wait_forever(&block)
30 | wait_time = Capybara.default_wait_time
31 | Capybara.default_wait_time = 60
32 | block.call
33 | ensure
34 | Capybara.default_wait_time = wait_time
35 | end
36 |
37 | ### ATTRIBUTES
38 |
39 | def item_attrs(attrs = {})
40 | {
41 | 'Title' => 'title',
42 | 'URL' => 'http://example.com',
43 | 'Username' => 'username',
44 | 'Password' => 'password',
45 | 'Confirm' => 'password'
46 | }.merge(attrs)
47 | end
48 |
49 | ### MATCHERS
50 |
51 | def be_unlocked
52 | have_content('+') # yeah, yeah, this sucks
53 | end
54 | end
55 |
56 | RSpec.configure do |config|
57 | config.include AcceptanceSpecHelpers, :capybara_feature => true
58 | config.after :each, :capybara_feature => true do
59 | page.execute_script 'localStorage.clear()'
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/spec/support/controller_spec_helpers.rb:
--------------------------------------------------------------------------------
1 | module ControllerSpecHelpers
2 | attr_reader :current_user
3 |
4 | def sign_in_as(user)
5 | controller.stub(:current_user).and_return(user)
6 | @current_user = user
7 | end
8 |
9 | def raw_post(action, body)
10 | request.env['RAW_POST_DATA'] = body
11 | post action
12 | end
13 |
14 | end
15 |
16 | RSpec.configure do |config|
17 | config.include ControllerSpecHelpers, :type => 'controller'
18 | end
19 |
--------------------------------------------------------------------------------
/spec/support/fixtures.rb:
--------------------------------------------------------------------------------
1 | module FixtureSpecHelper
2 | def fixture(name)
3 | Rails.root.join('spec', 'fixtures', name).read
4 | end
5 | end
6 |
7 | RSpec.configure do |config|
8 | config.include FixtureSpecHelper
9 | end
10 |
--------------------------------------------------------------------------------
/spec/support/mocks.rb:
--------------------------------------------------------------------------------
1 | module Mocks
2 | def mock_user(attrs = {})
3 | double(:user, user_attrs(attrs)).as_null_object
4 | end
5 |
6 | def user_attrs(attrs = {})
7 | {
8 | :id => 1,
9 | :public_key => double(:public_key)
10 | }.merge(attrs)
11 | end
12 | end
13 |
14 | RSpec.configure do |config|
15 | config.include Mocks
16 | end
17 |
--------------------------------------------------------------------------------
/spec/support/webmock.rb:
--------------------------------------------------------------------------------
1 | require 'webmock/rspec'
2 |
3 | WebMock.disable_net_connect!(:allow_localhost => true)
4 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge.coffee:
--------------------------------------------------------------------------------
1 | #= require forge/util
2 | #= require forge/asn1
3 | #= require forge/sha1
4 | #= require forge/rsa
5 | #= require_tree ./forge
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge/hmac.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Hash-based Message Authentication Code implementation. Requires a message
3 | * digest object that can be obtained, for example, from forge.md.sha1 or
4 | * forge.md.md5.
5 | *
6 | * @author Dave Longley
7 | *
8 | * Copyright (c) 2010-2012 Digital Bazaar, Inc. All rights reserved.
9 | */
10 | (function() {
11 |
12 | // define forge
13 | if(typeof(window) !== 'undefined') {
14 | var forge = window.forge = window.forge || {};
15 | forge.hmac = {};
16 | }
17 | // define node.js module
18 | else if(typeof(module) !== 'undefined' && module.exports) {
19 | var forge = {
20 | md: require('./md'),
21 | util: require('./util')
22 | };
23 | module.exports = forge.hmac = {};
24 | }
25 |
26 | /* HMAC API */
27 | var hmac = forge.hmac;
28 |
29 | /**
30 | * Creates an HMAC object that uses the given message digest object.
31 | *
32 | * @return an HMAC object.
33 | */
34 | hmac.create = function() {
35 | // the hmac key to use
36 | var _key = null;
37 |
38 | // the message digest to use
39 | var _md = null;
40 |
41 | // the inner padding
42 | var _ipadding = null;
43 |
44 | // the outer padding
45 | var _opadding = null;
46 |
47 | // hmac context
48 | var ctx = {};
49 |
50 | /**
51 | * Starts or restarts the HMAC with the given key and message digest.
52 | *
53 | * @param md the message digest to use, null to reuse the previous one,
54 | * a string to use builtin 'sha1', 'md5', 'sha256'.
55 | * @param key the key to use as a string, array of bytes, byte buffer,
56 | * or null to reuse the previous key.
57 | */
58 | ctx.start = function(md, key) {
59 | if(md !== null) {
60 | if(md.constructor == String) {
61 | // create builtin message digest
62 | md = md.toLowerCase();
63 | if(md in forge.md.algorithms) {
64 | _md = forge.md.algorithms[md].create();
65 | }
66 | else {
67 | throw 'Unknown hash algorithm "' + md + '"';
68 | }
69 | }
70 | else {
71 | // store message digest
72 | _md = md;
73 | }
74 | }
75 |
76 | if(key === null) {
77 | // reuse previous key
78 | key = _key;
79 | }
80 | else {
81 | // convert string into byte buffer
82 | if(key.constructor == String) {
83 | key = forge.util.createBuffer(key);
84 | }
85 | // convert byte array into byte buffer
86 | else if(key.constructor == Array) {
87 | var tmp = key;
88 | key = forge.util.createBuffer();
89 | for(var i = 0; i < tmp.length; ++i) {
90 | key.putByte(tmp[i]);
91 | }
92 | }
93 |
94 | // if key is longer than blocksize, hash it
95 | var keylen = key.length();
96 | if(keylen > _md.blockLength) {
97 | _md.start();
98 | _md.update(key.bytes());
99 | key = _md.digest();
100 | }
101 |
102 | // mix key into inner and outer padding
103 | // ipadding = [0x36 * blocksize] ^ key
104 | // opadding = [0x5C * blocksize] ^ key
105 | _ipadding = forge.util.createBuffer();
106 | _opadding = forge.util.createBuffer();
107 | keylen = key.length();
108 | for(var i = 0; i < keylen; ++i) {
109 | var tmp = key.at(i);
110 | _ipadding.putByte(0x36 ^ tmp);
111 | _opadding.putByte(0x5C ^ tmp);
112 | }
113 |
114 | // if key is shorter than blocksize, add additional padding
115 | if(keylen < _md.blockLength) {
116 | var tmp = _md.blockLength - keylen;
117 | for(var i = 0; i < tmp; ++i) {
118 | _ipadding.putByte(0x36);
119 | _opadding.putByte(0x5C);
120 | }
121 | }
122 | _key = key;
123 | _ipadding = _ipadding.bytes();
124 | _opadding = _opadding.bytes();
125 | }
126 |
127 | // digest is done like so: hash(opadding | hash(ipadding | message))
128 |
129 | // prepare to do inner hash
130 | // hash(ipadding | message)
131 | _md.start();
132 | _md.update(_ipadding);
133 | };
134 |
135 | /**
136 | * Updates the HMAC with the given message bytes.
137 | *
138 | * @param bytes the bytes to update with.
139 | */
140 | ctx.update = function(bytes) {
141 | _md.update(bytes);
142 | };
143 |
144 | /**
145 | * Produces the Message Authentication Code (MAC).
146 | *
147 | * @return a byte buffer containing the digest value.
148 | */
149 | ctx.getMac = function() {
150 | // digest is done like so: hash(opadding | hash(ipadding | message))
151 | // here we do the outer hashing
152 | var inner = _md.digest().bytes();
153 | _md.start();
154 | _md.update(_opadding);
155 | _md.update(inner);
156 | return _md.digest();
157 | };
158 | // alias for getMac
159 | ctx.digest = ctx.getMac;
160 |
161 | return ctx;
162 | };
163 |
164 | })();
165 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge/md5.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Message Digest Algorithm 5 with 128-bit digest (MD5) implementation.
3 | *
4 | * This implementation is currently limited to message lengths (in bytes) that
5 | * are up to 32-bits in size.
6 | *
7 | * @author Dave Longley
8 | *
9 | * Copyright (c) 2010-2012 Digital Bazaar, Inc.
10 | */
11 | (function() {
12 |
13 | var md5 = {};
14 |
15 | // define forge
16 | if(typeof(window) !== 'undefined') {
17 | var forge = window.forge = window.forge || {};
18 | }
19 | // define node.js module
20 | else if(typeof(module) !== 'undefined' && module.exports) {
21 | var forge = {
22 | util: require('./util')
23 | };
24 | module.exports = md5;
25 | }
26 | forge.md = forge.md || {};
27 | forge.md.algorithms = forge.md.algorithms || {};
28 | forge.md.md5 = forge.md.algorithms['md5'] = md5;
29 |
30 | // padding, constant tables for calculating md5
31 | var _padding = null;
32 | var _g = null;
33 | var _r = null;
34 | var _k = null;
35 | var _initialized = false;
36 |
37 | /**
38 | * Initializes the constant tables.
39 | */
40 | var _init = function() {
41 | // create padding
42 | _padding = String.fromCharCode(128);
43 | _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
44 |
45 | // g values
46 | _g = [
47 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
48 | 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12,
49 | 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2,
50 | 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9];
51 |
52 | // rounds table
53 | _r = [
54 | 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
55 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
56 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
57 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21];
58 |
59 | // get the result of abs(sin(i + 1)) as a 32-bit integer
60 | _k = new Array(64);
61 | for(var i = 0; i < 64; ++i) {
62 | _k[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 0x100000000);
63 | }
64 |
65 | // now initialized
66 | _initialized = true;
67 | };
68 |
69 | /**
70 | * Updates an MD5 state with the given byte buffer.
71 | *
72 | * @param s the MD5 state to update.
73 | * @param w the array to use to store words.
74 | * @param bytes the byte buffer to update with.
75 | */
76 | var _update = function(s, w, bytes) {
77 | // consume 512 bit (64 byte) chunks
78 | var t, a, b, c, d, f, r, i;
79 | var len = bytes.length();
80 | while(len >= 64) {
81 | // initialize hash value for this chunk
82 | a = s.h0;
83 | b = s.h1;
84 | c = s.h2;
85 | d = s.h3;
86 |
87 | // round 1
88 | for(i = 0; i < 16; ++i) {
89 | w[i] = bytes.getInt32Le();
90 | f = d ^ (b & (c ^ d));
91 | t = (a + f + _k[i] + w[i]);
92 | r = _r[i];
93 | a = d;
94 | d = c;
95 | c = b;
96 | b += (t << r) | (t >>> (32 - r));
97 | }
98 | // round 2
99 | for(; i < 32; ++i) {
100 | f = c ^ (d & (b ^ c));
101 | t = (a + f + _k[i] + w[_g[i]]);
102 | r = _r[i];
103 | a = d;
104 | d = c;
105 | c = b;
106 | b += (t << r) | (t >>> (32 - r));
107 | }
108 | // round 3
109 | for(; i < 48; ++i) {
110 | f = b ^ c ^ d;
111 | t = (a + f + _k[i] + w[_g[i]]);
112 | r = _r[i];
113 | a = d;
114 | d = c;
115 | c = b;
116 | b += (t << r) | (t >>> (32 - r));
117 | }
118 | // round 4
119 | for(; i < 64; ++i) {
120 | f = c ^ (b | ~d);
121 | t = (a + f + _k[i] + w[_g[i]]);
122 | r = _r[i];
123 | a = d;
124 | d = c;
125 | c = b;
126 | b += (t << r) | (t >>> (32 - r));
127 | }
128 |
129 | // update hash state
130 | s.h0 = (s.h0 + a) & 0xFFFFFFFF;
131 | s.h1 = (s.h1 + b) & 0xFFFFFFFF;
132 | s.h2 = (s.h2 + c) & 0xFFFFFFFF;
133 | s.h3 = (s.h3 + d) & 0xFFFFFFFF;
134 |
135 | len -= 64;
136 | }
137 | };
138 |
139 | /**
140 | * Creates an MD5 message digest object.
141 | *
142 | * @return a message digest object.
143 | */
144 | md5.create = function() {
145 | // do initialization as necessary
146 | if(!_initialized) {
147 | _init();
148 | }
149 |
150 | // MD5 state contains four 32-bit integers
151 | var _state = null;
152 |
153 | // input buffer
154 | var _input = forge.util.createBuffer();
155 |
156 | // used for word storage
157 | var _w = new Array(16);
158 |
159 | // message digest object
160 | var md = {
161 | algorithm: 'md5',
162 | blockLength: 64,
163 | digestLength: 16,
164 | // length of message so far (does not including padding)
165 | messageLength: 0
166 | };
167 |
168 | /**
169 | * Starts the digest.
170 | */
171 | md.start = function() {
172 | md.messageLength = 0;
173 | _input = forge.util.createBuffer();
174 | _state = {
175 | h0: 0x67452301,
176 | h1: 0xEFCDAB89,
177 | h2: 0x98BADCFE,
178 | h3: 0x10325476
179 | };
180 | };
181 | // start digest automatically for first time
182 | md.start();
183 |
184 | /**
185 | * Updates the digest with the given message input. The given input can
186 | * treated as raw input (no encoding will be applied) or an encoding of
187 | * 'utf8' maybe given to encode the input using UTF-8.
188 | *
189 | * @param msg the message input to update with.
190 | * @param encoding the encoding to use (default: 'raw', other: 'utf8').
191 | */
192 | md.update = function(msg, encoding) {
193 | if(encoding === 'utf8') {
194 | msg = forge.util.encodeUtf8(msg);
195 | }
196 |
197 | // update message length
198 | md.messageLength += msg.length;
199 |
200 | // add bytes to input buffer
201 | _input.putBytes(msg);
202 |
203 | // process bytes
204 | _update(_state, _w, _input);
205 |
206 | // compact input buffer every 2K or if empty
207 | if(_input.read > 2048 || _input.length() === 0) {
208 | _input.compact();
209 | }
210 | };
211 |
212 | /**
213 | * Produces the digest.
214 | *
215 | * @return a byte buffer containing the digest value.
216 | */
217 | md.digest = function() {
218 | /* Note: Here we copy the remaining bytes in the input buffer and
219 | add the appropriate MD5 padding. Then we do the final update
220 | on a copy of the state so that if the user wants to get
221 | intermediate digests they can do so. */
222 |
223 | /* Determine the number of bytes that must be added to the message
224 | to ensure its length is congruent to 448 mod 512. In other words,
225 | a 64-bit integer that gives the length of the message will be
226 | appended to the message and whatever the length of the message is
227 | plus 64 bits must be a multiple of 512. So the length of the
228 | message must be congruent to 448 mod 512 because 512 - 64 = 448.
229 |
230 | In order to fill up the message length it must be filled with
231 | padding that begins with 1 bit followed by all 0 bits. Padding
232 | must *always* be present, so if the message length is already
233 | congruent to 448 mod 512, then 512 padding bits must be added. */
234 |
235 | // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
236 | // _padding starts with 1 byte with first bit is set in it which
237 | // is byte value 128, then there may be up to 63 other pad bytes
238 | var len = md.messageLength;
239 | var padBytes = forge.util.createBuffer();
240 | padBytes.putBytes(_input.bytes());
241 | padBytes.putBytes(_padding.substr(0, 64 - ((len + 8) % 64)));
242 |
243 | /* Now append length of the message. The length is appended in bits
244 | as a 64-bit number in little-endian format. Since we store the
245 | length in bytes, we must multiply it by 8 (or left shift by 3). So
246 | here store the high 3 bits in the high end of the second 32-bits of
247 | the 64-bit number and the lower 5 bits in the low end of the
248 | second 32-bits. */
249 | padBytes.putInt32Le((len << 3) & 0xFFFFFFFF);
250 | padBytes.putInt32Le((len >>> 29) & 0xFF);
251 | var s2 = {
252 | h0: _state.h0,
253 | h1: _state.h1,
254 | h2: _state.h2,
255 | h3: _state.h3
256 | };
257 | _update(s2, _w, padBytes);
258 | var rval = forge.util.createBuffer();
259 | rval.putInt32Le(s2.h0);
260 | rval.putInt32Le(s2.h1);
261 | rval.putInt32Le(s2.h2);
262 | rval.putInt32Le(s2.h3);
263 | return rval;
264 | };
265 |
266 | return md;
267 | };
268 |
269 | })();
270 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge/mgf1.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Javascript implementation of mask generation function MGF1.
3 | *
4 | * @author Stefan Siegl
5 | *
6 | * Copyright (c) 2012 Stefan Siegl
7 | */
8 | (function() {
9 |
10 | var mgf1 = {};
11 |
12 | // define forge
13 | var forge = {};
14 | if(typeof(window) !== 'undefined') {
15 | forge = window.forge = window.forge || {};
16 | }
17 | // define node.js module
18 | else if(typeof(module) !== 'undefined' && module.exports) {
19 | forge = {
20 | util: require('./util')
21 | };
22 | module.exports = mgf1;
23 | }
24 |
25 | forge.mgf = forge.mgf || {};
26 | forge.mgf.mgf1 = mgf1;
27 |
28 | /**
29 | * Creates a MGF1 mask generation function object.
30 | *
31 | * @return a mask generation function object.
32 | */
33 | mgf1.create = function(md) {
34 | var mgf = {
35 | /**
36 | * Generate mask of specified length.
37 | *
38 | * @param {String} seed The seed for mask generation.
39 | * @param maskLen Number of bytes to generate.
40 | * @return {String} The generated mask.
41 | */
42 | generate: function(seed, maskLen) {
43 | /* 2. Let T be the empty octet string. */
44 | var t = new forge.util.ByteBuffer();
45 |
46 | /* 3. For counter from 0 to maskLen / hLen - 1, do the following: */
47 | var len = Math.ceil(maskLen / md.digestLength);
48 | for(var i = 0; i < len; i++) {
49 | /* a. Convert counter to an octet string C of length 4 octets */
50 | var c = new forge.util.ByteBuffer();
51 | c.putInt32(i);
52 |
53 | /* b. Concatenate the hash of the seed mgfSeed and C to the octet
54 | * string T: */
55 | md.start();
56 | md.update(seed + c.getBytes());
57 | t.putBuffer(md.digest());
58 | }
59 |
60 | /* Output the leading maskLen octets of T as the octet string mask. */
61 | t.truncate(t.length() - maskLen);
62 | return t.getBytes();
63 | }
64 | };
65 |
66 | return mgf;
67 | };
68 |
69 | })();
70 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge/oids.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Object IDs for ASN.1.
3 | *
4 | * @author Dave Longley
5 | *
6 | * Copyright (c) 2010-2012 Digital Bazaar, Inc.
7 | */
8 | (function() {
9 |
10 | var oids = {};
11 |
12 | // define forge
13 | if(typeof(window) !== 'undefined') {
14 | var forge = window.forge = window.forge || {};
15 | }
16 | // define node.js module
17 | else if(typeof(module) !== 'undefined' && module.exports) {
18 | var forge = {};
19 | module.exports = oids;
20 | }
21 | forge.pki = forge.pki || {};
22 | forge.pki.oids = oids;
23 |
24 | // algorithm OIDs
25 | oids['1.2.840.113549.1.1.1'] = 'rsaEncryption';
26 | oids['rsaEncryption'] = '1.2.840.113549.1.1.1';
27 | // Note: md2 & md4 not implemented
28 | //oids['1.2.840.113549.1.1.2'] = 'md2withRSAEncryption';
29 | //oids['md2withRSAEncryption'] = '1.2.840.113549.1.1.2';
30 | //oids['1.2.840.113549.1.1.3'] = 'md4withRSAEncryption';
31 | //oids['md4withRSAEncryption'] = '1.2.840.113549.1.1.3';
32 | oids['1.2.840.113549.1.1.4'] = 'md5withRSAEncryption';
33 | oids['md5withRSAEncryption'] = '1.2.840.113549.1.1.4';
34 | oids['1.2.840.113549.1.1.5'] = 'sha1withRSAEncryption';
35 | oids['sha1withRSAEncryption'] = '1.2.840.113549.1.1.5';
36 | oids['1.2.840.113549.1.1.7'] = 'RSAES-OAEP';
37 | oids['RSAES-OAEP'] = '1.2.840.113549.1.1.7';
38 | oids['1.2.840.113549.1.1.8'] = 'mgf1';
39 | oids['mgf1'] = '1.2.840.113549.1.1.8';
40 | oids['1.2.840.113549.1.1.9'] = 'pSpecified';
41 | oids['pSpecified'] = '1.2.840.113549.1.1.9';
42 | oids['1.2.840.113549.1.1.10'] = 'RSASSA-PSS';
43 | oids['RSASSA-PSS'] = '1.2.840.113549.1.1.10';
44 | oids['1.2.840.113549.1.1.11'] = 'sha256WithRSAEncryption';
45 | oids['sha256WithRSAEncryption'] = '1.2.840.113549.1.1.11';
46 | oids['1.2.840.113549.1.1.12'] = 'sha384WithRSAEncryption';
47 | oids['sha384WithRSAEncryption'] = '1.2.840.113549.1.1.12';
48 | oids['1.2.840.113549.1.1.13'] = 'sha512WithRSAEncryption';
49 | oids['sha512WithRSAEncryption'] = '1.2.840.113549.1.1.13';
50 |
51 | oids['1.3.14.3.2.26'] = 'sha1';
52 | oids['sha1'] = '1.3.14.3.2.26';
53 | oids['2.16.840.1.101.3.4.2.1'] = 'sha256';
54 | oids['sha256'] = '2.16.840.1.101.3.4.2.1';
55 | oids['2.16.840.1.101.3.4.2.2'] = 'sha384';
56 | oids['sha384'] = '2.16.840.1.101.3.4.2.2';
57 | oids['2.16.840.1.101.3.4.2.3'] = 'sha512';
58 | oids['sha512'] = '2.16.840.1.101.3.4.2.3';
59 | oids['1.2.840.113549.2.5'] = 'md5';
60 | oids['md5'] = '1.2.840.113549.2.5';
61 |
62 | // pkcs#7 content types
63 | oids['1.2.840.113549.1.7.1'] = 'data';
64 | oids['data'] = '1.2.840.113549.1.7.1';
65 | oids['1.2.840.113549.1.7.2'] = 'signedData';
66 | oids['signedData'] = '1.2.840.113549.1.7.2';
67 | oids['1.2.840.113549.1.7.3'] = 'envelopedData';
68 | oids['envelopedData'] = '1.2.840.113549.1.7.3';
69 | oids['1.2.840.113549.1.7.4'] = 'signedAndEnvelopedData';
70 | oids['signedAndEnvelopedData'] = '1.2.840.113549.1.7.4';
71 | oids['1.2.840.113549.1.7.5'] = 'digestedData';
72 | oids['digestedData'] = '1.2.840.113549.1.7.5';
73 | oids['1.2.840.113549.1.7.6'] = 'encryptedData';
74 | oids['encryptedData'] = '1.2.840.113549.1.7.6';
75 |
76 | // pkcs#9 oids
77 | oids['1.2.840.113549.1.9.20'] = 'friendlyName';
78 | oids['friendlyName'] = '1.2.840.113549.1.9.20';
79 | oids['1.2.840.113549.1.9.21'] = 'localKeyId';
80 | oids['localKeyId'] = '1.2.840.113549.1.9.21';
81 | oids['1.2.840.113549.1.9.22.1'] = 'x509Certificate';
82 | oids['x509Certificate'] = '1.2.840.113549.1.9.22.1';
83 |
84 | // pkcs#12 safe bags
85 | oids['1.2.840.113549.1.12.10.1.1'] = 'keyBag';
86 | oids['keyBag'] = '1.2.840.113549.1.12.10.1.1';
87 | oids['1.2.840.113549.1.12.10.1.2'] = 'pkcs8ShroudedKeyBag';
88 | oids['pkcs8ShroudedKeyBag'] = '1.2.840.113549.1.12.10.1.2';
89 | oids['1.2.840.113549.1.12.10.1.3'] = 'certBag';
90 | oids['certBag'] = '1.2.840.113549.1.12.10.1.3';
91 | oids['1.2.840.113549.1.12.10.1.4'] = 'crlBag';
92 | oids['crlBag'] = '1.2.840.113549.1.12.10.1.4';
93 | oids['1.2.840.113549.1.12.10.1.5'] = 'secretBag';
94 | oids['secretBag'] = '1.2.840.113549.1.12.10.1.5';
95 | oids['1.2.840.113549.1.12.10.1.6'] = 'safeContentsBag';
96 | oids['safeContentsBag'] = '1.2.840.113549.1.12.10.1.6';
97 |
98 | // password-based-encryption for pkcs#12
99 | oids['1.2.840.113549.1.5.13'] = 'pkcs5PBES2';
100 | oids['pkcs5PBES2'] = '1.2.840.113549.1.5.13';
101 | oids['1.2.840.113549.1.5.12'] = 'pkcs5PBKDF2';
102 | oids['pkcs5PBKDF2'] = '1.2.840.113549.1.5.12';
103 |
104 | oids['1.2.840.113549.1.12.1.1'] = 'pbeWithSHAAnd128BitRC4';
105 | oids['pbeWithSHAAnd128BitRC4'] = '1.2.840.113549.1.12.1.1';
106 | oids['1.2.840.113549.1.12.1.2'] = 'pbeWithSHAAnd40BitRC4';
107 | oids['pbeWithSHAAnd40BitRC4'] = '1.2.840.113549.1.12.1.2';
108 | oids['1.2.840.113549.1.12.1.3'] = 'pbeWithSHAAnd3-KeyTripleDES-CBC';
109 | oids['pbeWithSHAAnd3-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.3';
110 | oids['1.2.840.113549.1.12.1.4'] = 'pbeWithSHAAnd2-KeyTripleDES-CBC';
111 | oids['pbeWithSHAAnd2-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.4';
112 | oids['1.2.840.113549.1.12.1.5'] = 'pbeWithSHAAnd128BitRC2-CBC';
113 | oids['pbeWithSHAAnd128BitRC2-CBC'] = '1.2.840.113549.1.12.1.5';
114 | oids['1.2.840.113549.1.12.1.6'] = 'pbewithSHAAnd40BitRC2-CBC';
115 | oids['pbewithSHAAnd40BitRC2-CBC'] = '1.2.840.113549.1.12.1.6';
116 |
117 | // symmetric key algorithm oids
118 | oids['1.2.840.113549.3.7'] = 'des-EDE3-CBC';
119 | oids['des-EDE3-CBC'] = '1.2.840.113549.3.7';
120 | oids['2.16.840.1.101.3.4.1.2'] = 'aes128-CBC';
121 | oids['aes128-CBC'] = '2.16.840.1.101.3.4.1.2';
122 | oids['2.16.840.1.101.3.4.1.22'] = 'aes192-CBC';
123 | oids['aes192-CBC'] = '2.16.840.1.101.3.4.1.22';
124 | oids['2.16.840.1.101.3.4.1.42'] = 'aes256-CBC';
125 | oids['aes256-CBC'] = '2.16.840.1.101.3.4.1.42';
126 |
127 | // certificate issuer/subject OIDs
128 | oids['2.5.4.3'] = 'commonName';
129 | oids['commonName'] = '2.5.4.3';
130 | oids['2.5.4.5'] = 'serialName';
131 | oids['serialName'] = '2.5.4.5';
132 | oids['2.5.4.6'] = 'countryName';
133 | oids['countryName'] = '2.5.4.6';
134 | oids['2.5.4.7'] = 'localityName';
135 | oids['localityName'] = '2.5.4.7';
136 | oids['2.5.4.8'] = 'stateOrProvinceName';
137 | oids['stateOrProvinceName'] = '2.5.4.8';
138 | oids['2.5.4.10'] = 'organizationName';
139 | oids['organizationName'] = '2.5.4.10';
140 | oids['2.5.4.11'] = 'organizationalUnitName';
141 | oids['organizationalUnitName'] = '2.5.4.11';
142 | oids['1.2.840.113549.1.9.1'] = 'emailAddress';
143 | oids['emailAddress'] = '1.2.840.113549.1.9.1';
144 |
145 | // X.509 extension OIDs
146 | oids['2.5.29.1'] = 'authorityKeyIdentifier'; // deprecated, use .35
147 | oids['2.5.29.2'] = 'keyAttributes'; // obsolete use .37 or .15
148 | oids['2.5.29.3'] = 'certificatePolicies'; // deprecated, use .32
149 | oids['2.5.29.4'] = 'keyUsageRestriction'; // obsolete use .37 or .15
150 | oids['2.5.29.5'] = 'policyMapping'; // deprecated use .33
151 | oids['2.5.29.6'] = 'subtreesConstraint'; // obsolete use .30
152 | oids['2.5.29.7'] = 'subjectAltName'; // deprecated use .17
153 | oids['2.5.29.8'] = 'issuerAltName'; // deprecated use .18
154 | oids['2.5.29.9'] = 'subjectDirectoryAttributes';
155 | oids['2.5.29.10'] = 'basicConstraints'; // deprecated use .19
156 | oids['2.5.29.11'] = 'nameConstraints'; // deprecated use .30
157 | oids['2.5.29.12'] = 'policyConstraints'; // deprecated use .36
158 | oids['2.5.29.13'] = 'basicConstraints'; // deprecated use .19
159 | oids['2.5.29.14'] = 'subjectKeyIdentifier';
160 | oids['subjectKeyIdentifier'] = '2.5.29.14';
161 | oids['2.5.29.15'] = 'keyUsage';
162 | oids['keyUsage'] = '2.5.29.15';
163 | oids['2.5.29.16'] = 'privateKeyUsagePeriod';
164 | oids['2.5.29.17'] = 'subjectAltName';
165 | oids['subjectAltName'] = '2.5.29.17';
166 | oids['2.5.29.18'] = 'issuerAltName';
167 | oids['issuerAltName'] = '2.5.29.18';
168 | oids['2.5.29.19'] = 'basicConstraints';
169 | oids['basicConstraints'] = '2.5.29.19';
170 | oids['2.5.29.20'] = 'cRLNumber';
171 | oids['2.5.29.21'] = 'cRLReason';
172 | oids['2.5.29.22'] = 'expirationDate';
173 | oids['2.5.29.23'] = 'instructionCode';
174 | oids['2.5.29.24'] = 'invalidityDate';
175 | oids['2.5.29.25'] = 'cRLDistributionPoints'; // deprecated use .31
176 | oids['2.5.29.26'] = 'issuingDistributionPoint'; // deprecated use .28
177 | oids['2.5.29.27'] = 'deltaCRLIndicator';
178 | oids['2.5.29.28'] = 'issuingDistributionPoint';
179 | oids['2.5.29.29'] = 'certificateIssuer';
180 | oids['2.5.29.30'] = 'nameConstraints';
181 | oids['2.5.29.31'] = 'cRLDistributionPoints';
182 | oids['2.5.29.32'] = 'certificatePolicies';
183 | oids['2.5.29.33'] = 'policyMappings';
184 | oids['2.5.29.34'] = 'policyConstraints'; // deprecated use .36
185 | oids['2.5.29.35'] = 'authorityKeyIdentifier';
186 | oids['2.5.29.36'] = 'policyConstraints';
187 | oids['2.5.29.37'] = 'extKeyUsage';
188 | oids['extKeyUsage'] = '2.5.29.37';
189 | oids['2.5.29.46'] = 'freshestCRL';
190 | oids['2.5.29.54'] = 'inhibitAnyPolicy';
191 |
192 | })();
193 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge/pbkdf2.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Password-Based Key-Derivation Function #2 implementation.
3 | *
4 | * See RFC 2898 for details.
5 | *
6 | * @author Dave Longley
7 | *
8 | * Copyright (c) 2010-2012 Digital Bazaar, Inc.
9 | */
10 | (function() {
11 |
12 | // define forge
13 | if(typeof(window) !== 'undefined') {
14 | var forge = window.forge = window.forge || {};
15 | forge.pkcs5 = {};
16 | }
17 | // define node.js module
18 | else if(typeof(module) !== 'undefined' && module.exports) {
19 | var forge = {
20 | hmac: require('./hmac'),
21 | md: require('./md'),
22 | util: require('./util')
23 | };
24 | module.exports = forge.pkcs5 = {};
25 | }
26 |
27 | var pkcs5 = forge.pkcs5;
28 |
29 | /**
30 | * Derives a key from a password.
31 | *
32 | * @param p the password as a string of bytes.
33 | * @param s the salt as a string of bytes.
34 | * @param c the iteration count, a positive integer.
35 | * @param dkLen the intended length, in bytes, of the derived key,
36 | * (max: 2^32 - 1) * hash length of the PRF.
37 | * @param md the message digest to use in the PRF, defaults to SHA-1.
38 | *
39 | * @return the derived key, as a string of bytes.
40 | */
41 | pkcs5.pbkdf2 = function(p, s, c, dkLen, md) {
42 | // default prf to SHA-1
43 | if(typeof(md) === 'undefined' || md === null) {
44 | md = forge.md.sha1.create();
45 | }
46 |
47 | var hLen = md.digestLength;
48 |
49 | /* 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and
50 | stop. */
51 | if(dkLen > (0xFFFFFFFF * hLen)) {
52 | throw {
53 | message: 'Derived key is too long.'
54 | };
55 | }
56 |
57 | /* 2. Let len be the number of hLen-octet blocks in the derived key,
58 | rounding up, and let r be the number of octets in the last
59 | block:
60 |
61 | len = CEIL(dkLen / hLen),
62 | r = dkLen - (len - 1) * hLen. */
63 | var len = Math.ceil(dkLen / hLen);
64 | var r = dkLen - (len - 1) * hLen;
65 |
66 | /* 3. For each block of the derived key apply the function F defined
67 | below to the password P, the salt S, the iteration count c, and
68 | the block index to compute the block:
69 |
70 | T_1 = F(P, S, c, 1),
71 | T_2 = F(P, S, c, 2),
72 | ...
73 | T_len = F(P, S, c, len),
74 |
75 | where the function F is defined as the exclusive-or sum of the
76 | first c iterates of the underlying pseudorandom function PRF
77 | applied to the password P and the concatenation of the salt S
78 | and the block index i:
79 |
80 | F(P, S, c, i) = u_1 XOR u_2 XOR ... XOR u_c
81 |
82 | where
83 |
84 | u_1 = PRF(P, S || INT(i)),
85 | u_2 = PRF(P, u_1),
86 | ...
87 | u_c = PRF(P, u_{c-1}).
88 |
89 | Here, INT(i) is a four-octet encoding of the integer i, most
90 | significant octet first. */
91 | var prf = forge.hmac.create();
92 | prf.start(md, p);
93 | var dk = '';
94 | var xor, u_c, u_c1;
95 | for(var i = 1; i <= len; ++i) {
96 | // PRF(P, S || INT(i)) (first iteration)
97 | prf.update(s);
98 | prf.update(forge.util.int32ToBytes(i));
99 | xor = u_c1 = prf.digest().getBytes();
100 |
101 | // PRF(P, u_{c-1}) (other iterations)
102 | for(var j = 2; j <= c; ++j) {
103 | prf.start(null, null);
104 | prf.update(u_c1);
105 | u_c = prf.digest().getBytes();
106 | // F(p, s, c, i)
107 | xor = forge.util.xorBytes(xor, u_c, hLen);
108 | u_c1 = u_c;
109 | }
110 |
111 | /* 4. Concatenate the blocks and extract the first dkLen octets to
112 | produce a derived key DK:
113 |
114 | DK = T_1 || T_2 || ... || T_len<0..r-1> */
115 | dk += (i < len) ? xor : xor.substr(0, r);
116 | }
117 |
118 | /* 5. Output the derived key DK. */
119 | return dk;
120 | };
121 |
122 | })();
123 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge/prng.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A javascript implementation of a cryptographically-secure
3 | * Pseudo Random Number Generator (PRNG). The Fortuna algorithm is mostly
4 | * followed here. SHA-1 is used instead of SHA-256.
5 | *
6 | * @author Dave Longley
7 | *
8 | * Copyright (c) 2010-2012 Digital Bazaar, Inc.
9 | */
10 | (function() {
11 |
12 | // define forge
13 | if(typeof(window) !== 'undefined') {
14 | var forge = window.forge = window.forge || {};
15 | forge.prng = {};
16 | }
17 | // define node.js module
18 | else if(typeof(module) !== 'undefined' && module.exports) {
19 | var forge = {
20 | md: require('./md'),
21 | util: require('./util')
22 | };
23 | forge.md.sha1.create();
24 | module.exports = forge.prng = {};
25 | }
26 |
27 | /* PRNG API */
28 | var prng = forge.prng;
29 |
30 | /**
31 | * Creates a new PRNG context.
32 | *
33 | * A PRNG plugin must be passed in that will provide:
34 | *
35 | * 1. A function that initializes the key and seed of a PRNG context. It
36 | * will be given a 16 byte key and a 16 byte seed. Any key expansion
37 | * or transformation of the seed from a byte string into an array of
38 | * integers (or similar) should be performed.
39 | * 2. The cryptographic function used by the generator. It takes a key and
40 | * a seed.
41 | * 3. A seed increment function. It takes the seed and return seed + 1.
42 | * 4. An api to create a message digest.
43 | *
44 | * For an example, see random.js.
45 | *
46 | * @param plugin the PRNG plugin to use.
47 | */
48 | prng.create = function(plugin) {
49 | var ctx = {
50 | plugin: plugin,
51 | key: null,
52 | seed: null,
53 | time: null,
54 | // number of reseeds so far
55 | reseeds: 0,
56 | // amount of data generated so far
57 | generated: 0
58 | };
59 |
60 | // create 32 entropy pools (each is a message digest)
61 | var md = plugin.md;
62 | var pools = new Array(32);
63 | for(var i = 0; i < 32; ++i) {
64 | pools[i] = md.create();
65 | }
66 | ctx.pools = pools;
67 |
68 | // entropy pools are written to cyclically, starting at index 0
69 | ctx.pool = 0;
70 |
71 | /**
72 | * Generates random bytes.
73 | *
74 | * @param count the number of random bytes to generate.
75 | *
76 | * @return count random bytes as a string.
77 | */
78 | ctx.generate = function(count) {
79 | // do first seed if necessary
80 | if(ctx.key === null) {
81 | _reseed();
82 | }
83 |
84 | // simple generator using counter-based CBC
85 | var cipher = ctx.plugin.cipher;
86 | var increment = ctx.plugin.increment;
87 | var formatKey = ctx.plugin.formatKey;
88 | var formatSeed = ctx.plugin.formatSeed;
89 | var b = forge.util.createBuffer();
90 | while(b.length() < count) {
91 | // generate the random bytes
92 | var bytes = cipher(ctx.key, ctx.seed);
93 | ctx.generated += bytes.length;
94 | b.putBytes(bytes);
95 |
96 | // generate bytes for a new key and seed
97 | ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
98 | ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
99 |
100 | // if amount of data generated is greater than 1 MiB, reseed
101 | if(ctx.generated >= 1048576) {
102 | // only do reseed at most 10 times/second (every 100 ms)
103 | var now = +new Date();
104 | if(now - ctx.time < 100) {
105 | _reseed();
106 | }
107 | }
108 | }
109 |
110 | return b.getBytes(count);
111 | };
112 |
113 | /**
114 | * Private function that reseeds a generator.
115 | */
116 | function _reseed() {
117 | // not enough seed data... but we need to get going so just
118 | // be sad and add some weak random data
119 | if(ctx.pools[0].messageLength < 32) {
120 | /* Draws from Park-Miller "minimal standard" 31 bit PRNG,
121 | implemented with David G. Carta's optimization: with 32 bit math
122 | and without division (Public Domain). */
123 | var needed = (32 - ctx.pools[0].messageLength) << 5;
124 | var b = '';
125 | var hi, lo, next;
126 | var seed = Math.floor(Math.random() * 0xFFFF);
127 | while(b.length < needed) {
128 | lo = 16807 * (seed & 0xFFFF);
129 | hi = 16807 * (seed >> 16);
130 | lo += (hi & 0x7FFF) << 16;
131 | lo += hi >> 15;
132 | lo = (lo & 0x7FFFFFFF) + (lo >> 31);
133 | seed = lo & 0xFFFFFFFF;
134 |
135 | // consume lower 3 bytes of seed
136 | for(var i = 0; i < 3; ++i) {
137 | // throw in more pseudo random
138 | next = seed >>> (i << 3);
139 | next ^= Math.floor(Math.random() * 0xFF);
140 | b += String.fromCharCode(next & 0xFF);
141 | }
142 | }
143 | // will automatically reseed in collect
144 | ctx.collect(b);
145 | }
146 | else {
147 | // create a SHA-1 message digest
148 | var md = forge.md.sha1.create();
149 |
150 | // digest pool 0's entropy and restart it
151 | md.update(ctx.pools[0].digest().getBytes());
152 | ctx.pools[0].start();
153 |
154 | // digest the entropy of other pools whose index k meet the
155 | // condition '2^k mod n == 0' where n is the number of reseeds
156 | var k = 1;
157 | for(var i = 1; i < 32; ++i) {
158 | // prevent signed numbers from being used
159 | k = (k == 31) ? 2147483648 : (k << 2);
160 | if(k % ctx.reseeds === 0) {
161 | md.update(ctx.pools[i].digest().getBytes());
162 | ctx.pools[i].start();
163 | }
164 | }
165 |
166 | // get digest for key bytes and iterate again for seed bytes
167 | var keyBytes = md.digest().getBytes();
168 | md.start();
169 | md.update(keyBytes);
170 | var seedBytes = md.digest().getBytes();
171 |
172 | // update
173 | ctx.key = ctx.plugin.formatKey(keyBytes);
174 | ctx.seed = ctx.plugin.formatSeed(seedBytes);
175 | ++ctx.reseeds;
176 | ctx.generated = 0;
177 | ctx.time = +new Date();
178 | }
179 | }
180 |
181 | /**
182 | * Adds entropy to a prng ctx's accumulator.
183 | *
184 | * @param bytes the bytes of entropy as a string.
185 | */
186 | ctx.collect = function(bytes) {
187 | // iterate over pools distributing entropy cyclically
188 | var count = bytes.length;
189 | for(var i = 0; i < count; ++i) {
190 | ctx.pools[ctx.pool].update(bytes.substr(i, 1));
191 | ctx.pool = (ctx.pool === 31) ? 0 : ctx.pool + 1;
192 | }
193 |
194 | // do reseed if pool 0 has at least 32 bytes (enough to create a new
195 | // key and seed)
196 | if(ctx.pools[0].messageLength >= 32) {
197 | // only do reseed at most 10 times/second (every 100 ms)
198 | var now = +new Date();
199 | if(ctx.time === null || (now - ctx.time < 100)) {
200 | _reseed();
201 | }
202 | }
203 | };
204 |
205 | /**
206 | * Collects an integer of n bits.
207 | *
208 | * @param i the integer entropy.
209 | * @param n the number of bits in the integer.
210 | */
211 | ctx.collectInt = function(i, n) {
212 | var bytes = '';
213 | do {
214 | n -= 8;
215 | bytes += String.fromCharCode((i >> n) & 0xFF);
216 | }
217 | while(n > 0);
218 | ctx.collect(bytes);
219 | };
220 |
221 | return ctx;
222 | };
223 |
224 | })();
225 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge/pss.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Javascript implementation of PKCS#1 PSS signature padding.
3 | *
4 | * @author Stefan Siegl
5 | *
6 | * Copyright (c) 2012 Stefan Siegl
7 | */
8 | (function() {
9 |
10 | // define forge
11 | var forge = {};
12 | if(typeof(window) !== 'undefined') {
13 | forge = window.forge = window.forge || {};
14 | }
15 | // define node.js module
16 | else if(typeof(module) !== 'undefined' && module.exports) {
17 | forge = {
18 | random: require('./random'),
19 | util: require('./util')
20 | };
21 | module.exports = forge.pss = {};
22 | }
23 |
24 | // shortcut for PSS API
25 | var pss = forge.pss = forge.pss || {};
26 |
27 | /**
28 | * Creates a PSS signature scheme object.
29 | *
30 | * @param hash hash function to use, a Forge md instance
31 | * @param mgf mask generation function to use, a Forge mgf instance
32 | * @param sLen length of the salt in octets
33 | * @return a signature scheme object.
34 | */
35 | pss.create = function(hash, mgf, sLen) {
36 | var hLen = hash.digestLength;
37 | var pssobj = {};
38 |
39 | /**
40 | * Verify PSS signature
41 | *
42 | * This function implements EMSA-PSS-VERIFY as per RFC 3447, section 9.1.2
43 | *
44 | * @param {String} mHash The message digest hash to compare against
45 | * the signature.
46 | * @param {String} em The encoded message (RSA decryption result).
47 | * @param modsBits Length of the RSA modulus in bits.
48 | * @return true if the signature was verified, false if not.
49 | */
50 | pssobj.verify = function(mHash, em, modBits) {
51 | var i;
52 | var emBits = modBits - 1;
53 | var emLen = Math.ceil(emBits / 8);
54 |
55 | /* c. Convert the message representative m to an encoded message EM
56 | * of length emLen = (modBits - 1) / 8 octets, where modBits
57 | * is the length in bits of the RSA modulus n */
58 | em = em.substr(-emLen);
59 |
60 | /* 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. */
61 | if(emLen < hLen + sLen + 2) {
62 | throw {
63 | message: 'Inconsistent parameters to PSS signature verification.'
64 | };
65 | }
66 |
67 | /* 4. If the rightmost octet of EM does not have hexadecimal value
68 | * 0xbc, output "inconsistent" and stop. */
69 | if(em.charCodeAt(emLen - 1) !== 0xbc) {
70 | throw {
71 | message: 'Encoded message does not end in 0xBC.'
72 | };
73 | }
74 |
75 | /* 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
76 | * let H be the next hLen octets. */
77 | var maskLen = emLen - hLen - 1;
78 | var maskedDB = em.substr(0, maskLen);
79 | var h = em.substr(maskLen, hLen);
80 |
81 | /* 6. If the leftmost 8emLen - emBits bits of the leftmost octet in
82 | * maskedDB are not all equal to zero, output "inconsistent" and stop. */
83 | var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
84 | if((maskedDB.charCodeAt(0) & mask) !== 0) {
85 | throw {
86 | message: 'Bits beyond keysize not zero as expected.'
87 | };
88 | }
89 |
90 | /* 7. Let dbMask = MGF(H, emLen - hLen - 1). */
91 | var dbMask = mgf.generate(h, maskLen);
92 |
93 | /* 8. Let DB = maskedDB \xor dbMask. */
94 | var db = '';
95 | for(i = 0; i < maskLen; i ++) {
96 | db += String.fromCharCode(maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i));
97 | }
98 |
99 | /* 9. Set the leftmost 8emLen - emBits bits of the leftmost octet
100 | * in DB to zero. */
101 | db = String.fromCharCode(db.charCodeAt(0) & ~mask) + db.substr(1);
102 |
103 | /* 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
104 | * or if the octet at position emLen - hLen - sLen - 1 (the leftmost
105 | * position is "position 1") does not have hexadecimal value 0x01,
106 | * output "inconsistent" and stop. */
107 | var checkLen = emLen - hLen - sLen - 2;
108 | for(i = 0; i < checkLen; i ++) {
109 | if(db.charCodeAt(i) !== 0x00) {
110 | throw {
111 | message: 'Leftmost octets not zero as expected'
112 | };
113 | }
114 | }
115 |
116 | if(db.charCodeAt(checkLen) !== 0x01) {
117 | throw {
118 | message: 'Inconsistent PSS signature, 0x01 marker not found'
119 | };
120 | }
121 |
122 | /* 11. Let salt be the last sLen octets of DB. */
123 | var salt = db.substr(-sLen);
124 |
125 | /* 12. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt */
126 | var m_ = new forge.util.ByteBuffer();
127 | m_.fillWithByte(0, 8);
128 | m_.putBytes(mHash);
129 | m_.putBytes(salt);
130 |
131 | /* 13. Let H' = Hash(M'), an octet string of length hLen. */
132 | hash.start();
133 | hash.update(m_.getBytes());
134 | var h_ = hash.digest().getBytes();
135 |
136 | /* 14. If H = H', output "consistent." Otherwise, output "inconsistent." */
137 | return h === h_;
138 | };
139 |
140 | /**
141 | * Encode PSS signature.
142 | *
143 | * This function implements EMSA-PSS-ENCODE as per RFC 3447, section 9.1.1
144 | *
145 | * @param md the message digest object with the hash to sign.
146 | * @param modsBits Length of the RSA modulus in bits.
147 | * @return the encoded message, string of length (modBits - 1) / 8
148 | */
149 | pssobj.encode = function(md, modBits) {
150 | var i;
151 | var emBits = modBits - 1;
152 | var emLen = Math.ceil(emBits / 8);
153 |
154 | /* 2. Let mHash = Hash(M), an octet string of length hLen. */
155 | var mHash = md.digest().getBytes();
156 |
157 | /* 3. If emLen < hLen + sLen + 2, output "encoding error" and stop. */
158 | if(emLen < hLen + sLen + 2) {
159 | throw {
160 | message: 'Message is too long to encrypt'
161 | };
162 | }
163 |
164 | /* 4. Generate a random octet string salt of length sLen; if sLen = 0,
165 | * then salt is the empty string. */
166 | var salt = forge.random.getBytes(sLen);
167 |
168 | /* 5. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt; */
169 | var m_ = new forge.util.ByteBuffer();
170 | m_.fillWithByte(0, 8);
171 | m_.putBytes(mHash);
172 | m_.putBytes(salt);
173 |
174 | /* 6. Let H = Hash(M'), an octet string of length hLen. */
175 | hash.start();
176 | hash.update(m_.getBytes());
177 | var h = hash.digest().getBytes();
178 |
179 | /* 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
180 | * zero octets. The length of PS may be 0. */
181 | var ps = new forge.util.ByteBuffer();
182 | ps.fillWithByte(0, emLen - sLen - hLen - 2);
183 |
184 | /* 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
185 | * emLen - hLen - 1. */
186 | ps.putByte(0x01);
187 | ps.putBytes(salt);
188 | var db = ps.getBytes();
189 |
190 | /* 9. Let dbMask = MGF(H, emLen - hLen - 1). */
191 | var maskLen = emLen - hLen - 1;
192 | var dbMask = mgf.generate(h, maskLen);
193 |
194 | /* 10. Let maskedDB = DB \xor dbMask. */
195 | var maskedDB = '';
196 | for(i = 0; i < maskLen; i ++) {
197 | maskedDB += String.fromCharCode(db.charCodeAt(i) ^ dbMask.charCodeAt(i));
198 | }
199 |
200 | /* 11. Set the leftmost 8emLen - emBits bits of the leftmost octet in
201 | * maskedDB to zero. */
202 | var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
203 | maskedDB = String.fromCharCode(maskedDB.charCodeAt(0) & ~mask) +
204 | maskedDB.substr(1);
205 |
206 | /* 12. Let EM = maskedDB || H || 0xbc.
207 | * 13. Output EM. */
208 | return maskedDB + h + String.fromCharCode(0xbc);
209 | };
210 |
211 | return pssobj;
212 | };
213 |
214 | })();
215 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge/random.js:
--------------------------------------------------------------------------------
1 | /**
2 | * An API for getting cryptographically-secure random bytes. The bytes are
3 | * generated using the Fortuna algorithm devised by Bruce Schneier and
4 | * Niels Ferguson.
5 | *
6 | * Getting strong random bytes is not yet easy to do in javascript. The only
7 | * truish random entropy that can be collected is from the mouse, keyboard, or
8 | * from timing with respect to page loads, etc. This generator makes a poor
9 | * attempt at providing random bytes when those sources haven't yet provided
10 | * enough entropy to initially seed or to reseed the PRNG.
11 | *
12 | * @author Dave Longley
13 | *
14 | * Copyright (c) 2009-2012 Digital Bazaar, Inc.
15 | */
16 | (function($) {
17 |
18 | // define forge
19 | if(typeof(window) !== 'undefined') {
20 | var forge = window.forge = window.forge || {};
21 | forge.random = {};
22 | }
23 | // define node.js module
24 | else if(typeof(module) !== 'undefined' && module.exports) {
25 | var forge = {
26 | aes: require('./aes'),
27 | md: require('./md'),
28 | prng: require('./prng'),
29 | util: require('./util')
30 | };
31 | module.exports = forge.random = {};
32 | }
33 |
34 | // the default prng plugin, uses AES-128
35 | var prng_aes = {};
36 | var _prng_aes_output = new Array(4);
37 | var _prng_aes_buffer = forge.util.createBuffer();
38 | prng_aes.formatKey = function(key) {
39 | // convert the key into 32-bit integers
40 | var tmp = forge.util.createBuffer(key);
41 | key = new Array(4);
42 | key[0] = tmp.getInt32();
43 | key[1] = tmp.getInt32();
44 | key[2] = tmp.getInt32();
45 | key[3] = tmp.getInt32();
46 |
47 | // return the expanded key
48 | return forge.aes._expandKey(key, false);
49 | };
50 | prng_aes.formatSeed = function(seed) {
51 | // convert seed into 32-bit integers
52 | tmp = forge.util.createBuffer(seed);
53 | seed = new Array(4);
54 | seed[0] = tmp.getInt32();
55 | seed[1] = tmp.getInt32();
56 | seed[2] = tmp.getInt32();
57 | seed[3] = tmp.getInt32();
58 | return seed;
59 | };
60 | prng_aes.cipher = function(key, seed) {
61 | forge.aes._updateBlock(key, seed, _prng_aes_output, false);
62 | _prng_aes_buffer.putInt32(_prng_aes_output[0]);
63 | _prng_aes_buffer.putInt32(_prng_aes_output[1]);
64 | _prng_aes_buffer.putInt32(_prng_aes_output[2]);
65 | _prng_aes_buffer.putInt32(_prng_aes_output[3]);
66 | return _prng_aes_buffer.getBytes();
67 | };
68 | prng_aes.increment = function(seed) {
69 | // FIXME: do we care about carry or signed issues?
70 | ++seed[3];
71 | return seed;
72 | };
73 | prng_aes.md = forge.md.sha1;
74 |
75 | // create default prng context
76 | var _ctx = forge.prng.create(prng_aes);
77 |
78 | // get load time entropy
79 | _ctx.collectInt(+new Date(), 32);
80 |
81 | // add some entropy from navigator object
82 | if(typeof(navigator) !== 'undefined') {
83 | var _navBytes = '';
84 | for(var key in navigator) {
85 | try {
86 | if(typeof(navigator[key]) == 'string') {
87 | _navBytes += navigator[key];
88 | }
89 | } catch(e) {
90 | /* Some navigator keys might not be accessible, e.g. the geolocation
91 | attribute throws an exception if touched in Mozilla chrome:// context.
92 |
93 | Silently ignore this and just don't use this as a source of entropy. */
94 | }
95 | }
96 | _ctx.collect(_navBytes);
97 | _navBytes = null;
98 | }
99 |
100 | // add mouse and keyboard collectors if jquery is available
101 | if($) {
102 | // set up mouse entropy capture
103 | $().mousemove(function(e) {
104 | // add mouse coords
105 | _ctx.collectInt(e.clientX, 16);
106 | _ctx.collectInt(e.clientY, 16);
107 | });
108 |
109 | // set up keyboard entropy capture
110 | $().keypress(function(e) {
111 | _ctx.collectInt(e.charCode, 8);
112 | });
113 | }
114 |
115 | /* Random API */
116 |
117 | /**
118 | * Gets random bytes. This method tries to make the bytes more
119 | * unpredictable by drawing from data that can be collected from
120 | * the user of the browser, ie mouse movement.
121 | *
122 | * @param count the number of random bytes to get.
123 | *
124 | * @return the random bytes in a string.
125 | */
126 | forge.random.getBytes = function(count) {
127 | return _ctx.generate(count);
128 | };
129 |
130 | })(typeof(jQuery) !== 'undefined' ? jQuery : null);
131 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge/rc2.js:
--------------------------------------------------------------------------------
1 | /**
2 | * RC2 implementation.
3 | *
4 | * @author Stefan Siegl
5 | *
6 | * Copyright (c) 2012 Stefan Siegl
7 | *
8 | * Information on the RC2 cipher is available from RFC #2268,
9 | * http://www.ietf.org/rfc/rfc2268.txt
10 | */
11 |
12 | (function()
13 | {
14 |
15 | // define forge
16 | var forge = {};
17 | if(typeof(window) !== 'undefined')
18 | {
19 | forge = window.forge = window.forge || {};
20 | forge.rc2 = {};
21 | }
22 | // define node.js module
23 | else if(typeof(module) !== 'undefined' && module.exports)
24 | {
25 | forge =
26 | {
27 | util: require('./util')
28 | };
29 | module.exports = forge.rc2 = {};
30 | }
31 |
32 | var piTable = [
33 | 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d,
34 | 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2,
35 | 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32,
36 | 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82,
37 | 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc,
38 | 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26,
39 | 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03,
40 | 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7,
41 | 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a,
42 | 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec,
43 | 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39,
44 | 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31,
45 | 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9,
46 | 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9,
47 | 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e,
48 | 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad
49 | ];
50 |
51 | var s = [1, 2, 3, 5];
52 |
53 |
54 | /**
55 | * Rotate a word left by given number of bits.
56 | *
57 | * Bits that are shifted out on the left are put back in on the right
58 | * hand side.
59 | *
60 | * @param word The word to shift left.
61 | * @param bits The number of bits to shift by.
62 | * @return The rotated word.
63 | */
64 | var rol = function(word, bits) {
65 | return ((word << bits) & 0xffff) | ((word & 0xffff) >> (16 - bits));
66 | };
67 |
68 | /**
69 | * Rotate a word right by given number of bits.
70 | *
71 | * Bits that are shifted out on the right are put back in on the left
72 | * hand side.
73 | *
74 | * @param word The word to shift right.
75 | * @param bits The number of bits to shift by.
76 | * @return The rotated word.
77 | */
78 | var ror = function(word, bits) {
79 | return ((word & 0xffff) >> bits) | ((word << (16 - bits)) & 0xffff);
80 | };
81 |
82 |
83 |
84 |
85 | /* RC2 API */
86 |
87 | /**
88 | * Perform RC2 key expansion as per RFC #2268, section 2.
89 | *
90 | * @param key variable-length user key (between 1 and 128 bytes)
91 | * @param effKeyBits number of effective key bits (default: 128)
92 | * @return the expanded RC2 key (ByteBuffer of 128 bytes)
93 | */
94 | forge.rc2.expandKey = function(key, effKeyBits) {
95 | if(key.constructor == String) {
96 | key = forge.util.createBuffer(key);
97 | }
98 | effKeyBits = effKeyBits || 128;
99 |
100 | /* introduce variables that match the names used in RFC #2268 */
101 | var L = key;
102 | var T = key.length();
103 | var T1 = effKeyBits;
104 | var T8 = Math.ceil(T1 / 8);
105 | var TM = 0xff >> (T1 & 0x07);
106 | var i;
107 |
108 | for(i = T; i < 128; i ++) {
109 | L.putByte(piTable[(L.at(i - 1) + L.at(i - T)) & 0xff]);
110 | }
111 |
112 | L.setAt(128 - T8, piTable[L.at(128 - T8) & TM]);
113 |
114 | for(i = 127 - T8; i >= 0; i --) {
115 | L.setAt(i, piTable[L.at(i + 1) ^ L.at(i + T8)]);
116 | }
117 |
118 | return L;
119 | };
120 |
121 |
122 | /**
123 | * Creates a RC2 cipher object.
124 | *
125 | * @param key the symmetric key to use (as base for key generation).
126 | * @param bits the number of effective key bits.
127 | * @param encrypt false for decryption, true for encryption.
128 | *
129 | * @return the cipher.
130 | */
131 | var createCipher = function(key, bits, encrypt)
132 | {
133 | var _finish = false, _input = null, _output = null, _iv = null;
134 | var mixRound, mashRound;
135 | var i, j, K = [];
136 |
137 | /* Expand key and fill into K[] Array */
138 | key = forge.rc2.expandKey(key, bits);
139 | for(i = 0; i < 64; i ++) {
140 | K.push(key.getInt16Le());
141 | }
142 |
143 | if(encrypt) {
144 | /**
145 | * Perform one mixing round "in place".
146 | *
147 | * @param R Array of four words to perform mixing on.
148 | */
149 | mixRound = function(R) {
150 | for(i = 0; i < 4; i++) {
151 | R[i] += K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
152 | ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
153 | R[i] = rol(R[i], s[i]);
154 | j ++;
155 | }
156 | };
157 |
158 | /**
159 | * Perform one mashing round "in place".
160 | *
161 | * @param R Array of four words to perform mashing on.
162 | */
163 | mashRound = function(R) {
164 | for(i = 0; i < 4; i ++) {
165 | R[i] += K[R[(i + 3) % 4] & 63];
166 | }
167 | };
168 | } else {
169 | /**
170 | * Perform one r-mixing round "in place".
171 | *
172 | * @param R Array of four words to perform mixing on.
173 | */
174 | mixRound = function(R) {
175 | for(i = 3; i >= 0; i--) {
176 | R[i] = ror(R[i], s[i]);
177 | R[i] -= K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
178 | ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
179 | j --;
180 | }
181 | };
182 |
183 | /**
184 | * Perform one r-mashing round "in place".
185 | *
186 | * @param R Array of four words to perform mashing on.
187 | */
188 | mashRound = function(R) {
189 | for(i = 3; i >= 0; i--) {
190 | R[i] -= K[R[(i + 3) % 4] & 63];
191 | }
192 | };
193 | }
194 |
195 | /**
196 | * Run the specified cipher execution plan.
197 | *
198 | * This function takes four words from the input buffer, applies the IV on
199 | * it (if requested) and runs the provided execution plan.
200 | *
201 | * The plan must be put together in form of a array of arrays. Where the
202 | * outer one is simply a list of steps to perform and the inner one needs
203 | * to have two elements: the first one telling how many rounds to perform,
204 | * the second one telling what to do (i.e. the function to call).
205 | *
206 | * @param {Array} plan The plan to execute.
207 | */
208 | var runPlan = function(plan) {
209 | var R = [];
210 |
211 | /* Get data from input buffer and fill the four words into R */
212 | for(i = 0; i < 4; i ++) {
213 | var val = _input.getInt16Le();
214 |
215 | if(_iv !== null) {
216 | if(encrypt) {
217 | /* We're encrypting, apply the IV first. */
218 | val ^= _iv.getInt16Le();
219 | } else {
220 | /* We're decryption, keep cipher text for next block. */
221 | _iv.putInt16Le(val);
222 | }
223 | }
224 |
225 | R.push(val & 0xffff);
226 | }
227 |
228 | /* Reset global "j" variable as per spec. */
229 | j = encrypt ? 0 : 63;
230 |
231 | /* Run execution plan. */
232 | for(var ptr = 0; ptr < plan.length; ptr ++) {
233 | for(var ctr = 0; ctr < plan[ptr][0]; ctr ++) {
234 | plan[ptr][1](R);
235 | }
236 | }
237 |
238 | /* Write back result to output buffer. */
239 | for(i = 0; i < 4; i ++) {
240 | if(_iv !== null) {
241 | if(encrypt) {
242 | /* We're encrypting in CBC-mode, feed back encrypted bytes into
243 | IV buffer to carry it forward to next block. */
244 | _iv.putInt16Le(R[i]);
245 | } else {
246 | R[i] ^= _iv.getInt16Le();
247 | }
248 | }
249 |
250 | _output.putInt16Le(R[i]);
251 | }
252 | };
253 |
254 |
255 | /* Create cipher object */
256 | var cipher = null;
257 | cipher = {
258 | /**
259 | * Starts or restarts the encryption or decryption process, whichever
260 | * was previously configured.
261 | *
262 | * To use the cipher in CBC mode, iv may be given either as a string
263 | * of bytes, or as a byte buffer. For ECB mode, give null as iv.
264 | *
265 | * @param iv the initialization vector to use, null for ECB mode.
266 | * @param output the output the buffer to write to, null to create one.
267 | */
268 | start: function(iv, output) {
269 | if(iv) {
270 | /* CBC mode */
271 | if(key.constructor == String && iv.length == 8) {
272 | iv = forge.util.createBuffer(iv);
273 | }
274 | }
275 |
276 | _finish = false;
277 | _input = forge.util.createBuffer();
278 | _output = output || new forge.util.createBuffer();
279 | _iv = iv;
280 |
281 | cipher.output = _output;
282 | },
283 |
284 | /**
285 | * Updates the next block.
286 | *
287 | * @param input the buffer to read from.
288 | */
289 | update: function(input) {
290 | if(!_finish) {
291 | // not finishing, so fill the input buffer with more input
292 | _input.putBuffer(input);
293 | }
294 |
295 | while(_input.length() >= 8) {
296 | runPlan([
297 | [ 5, mixRound ],
298 | [ 1, mashRound ],
299 | [ 6, mixRound ],
300 | [ 1, mashRound ],
301 | [ 5, mixRound ]
302 | ]);
303 | }
304 | },
305 |
306 | /**
307 | * Finishes encrypting or decrypting.
308 | *
309 | * @param pad a padding function to use, null for PKCS#7 padding,
310 | * signature(blockSize, buffer, decrypt).
311 | *
312 | * @return true if successful, false on error.
313 | */
314 | finish: function(pad) {
315 | var rval = true;
316 |
317 | if(encrypt) {
318 | if(pad) {
319 | rval = pad(8, _input, !encrypt);
320 | } else {
321 | // add PKCS#7 padding to block (each pad byte is the
322 | // value of the number of pad bytes)
323 | var padding = (_input.length() == 8) ? 8 : (8 - _input.length());
324 | _input.fillWithByte(padding, padding);
325 | }
326 | }
327 |
328 | if(rval) {
329 | // do final update
330 | _finish = true;
331 | cipher.update();
332 | }
333 |
334 | if(!encrypt) {
335 | // check for error: input data not a multiple of block size
336 | rval = (_input.length() === 0);
337 | if(rval) {
338 | if(pad) {
339 | rval = pad(8, _output, !encrypt);
340 | } else {
341 | // ensure padding byte count is valid
342 | var len = _output.length();
343 | var count = _output.at(len - 1);
344 |
345 | if(count > len) {
346 | rval = false;
347 | } else {
348 | // trim off padding bytes
349 | _output.truncate(count);
350 | }
351 | }
352 | }
353 | }
354 |
355 | return rval;
356 | }
357 | };
358 |
359 | return cipher;
360 | };
361 |
362 |
363 | /**
364 | * Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
365 | * given symmetric key. The output will be stored in the 'output' member
366 | * of the returned cipher.
367 | *
368 | * The key and iv may be given as a string of bytes or a byte buffer.
369 | * The cipher is initialized to use 128 effective key bits.
370 | *
371 | * @param key the symmetric key to use.
372 | * @param iv the initialization vector to use.
373 | * @param output the buffer to write to, null to create one.
374 | *
375 | * @return the cipher.
376 | */
377 | forge.rc2.startEncrypting = function(key, iv, output) {
378 | var cipher = forge.rc2.createEncryptionCipher(key, 128);
379 | cipher.start(iv, output);
380 | return cipher;
381 | };
382 |
383 | /**
384 | * Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
385 | * given symmetric key.
386 | *
387 | * The key may be given as a string of bytes or a byte buffer.
388 | *
389 | * To start encrypting call start() on the cipher with an iv and optional
390 | * output buffer.
391 | *
392 | * @param key the symmetric key to use.
393 | *
394 | * @return the cipher.
395 | */
396 | forge.rc2.createEncryptionCipher = function(key, bits) {
397 | return createCipher(key, bits, true);
398 | };
399 |
400 | /**
401 | * Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
402 | * given symmetric key. The output will be stored in the 'output' member
403 | * of the returned cipher.
404 | *
405 | * The key and iv may be given as a string of bytes or a byte buffer.
406 | * The cipher is initialized to use 128 effective key bits.
407 | *
408 | * @param key the symmetric key to use.
409 | * @param iv the initialization vector to use.
410 | * @param output the buffer to write to, null to create one.
411 | *
412 | * @return the cipher.
413 | */
414 | forge.rc2.startDecrypting = function(key, iv, output) {
415 | var cipher = forge.rc2.createDecryptionCipher(key, 128);
416 | cipher.start(iv, output);
417 | return cipher;
418 | };
419 |
420 | /**
421 | * Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
422 | * given symmetric key.
423 | *
424 | * The key may be given as a string of bytes or a byte buffer.
425 | *
426 | * To start decrypting call start() on the cipher with an iv and optional
427 | * output buffer.
428 | *
429 | * @param key the symmetric key to use.
430 | *
431 | * @return the cipher.
432 | */
433 | forge.rc2.createDecryptionCipher = function(key, bits) {
434 | return createCipher(key, bits, false);
435 | };
436 | })();
437 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge/sha1.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Secure Hash Algorithm with 160-bit digest (SHA-1) implementation.
3 | *
4 | * This implementation is currently limited to message lengths (in bytes) that
5 | * are up to 32-bits in size.
6 | *
7 | * @author Dave Longley
8 | *
9 | * Copyright (c) 2010-2012 Digital Bazaar, Inc.
10 | */
11 | (function() {
12 |
13 | var sha1 = {};
14 |
15 | // define forge
16 | if(typeof(window) !== 'undefined') {
17 | var forge = window.forge = window.forge || {};
18 | }
19 | // define node.js module
20 | else if(typeof(module) !== 'undefined' && module.exports) {
21 | var forge = {
22 | util: require('./util')
23 | };
24 | module.exports = sha1;
25 | }
26 | forge.md = forge.md || {};
27 | forge.md.algorithms = forge.md.algorithms || {};
28 | forge.md.sha1 = forge.md.algorithms['sha1'] = sha1;
29 |
30 | // sha-1 padding bytes not initialized yet
31 | var _padding = null;
32 | var _initialized = false;
33 |
34 | /**
35 | * Initializes the constant tables.
36 | */
37 | var _init = function() {
38 | // create padding
39 | _padding = String.fromCharCode(128);
40 | _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
41 |
42 | // now initialized
43 | _initialized = true;
44 | };
45 |
46 | /**
47 | * Updates a SHA-1 state with the given byte buffer.
48 | *
49 | * @param s the SHA-1 state to update.
50 | * @param w the array to use to store words.
51 | * @param bytes the byte buffer to update with.
52 | */
53 | var _update = function(s, w, bytes) {
54 | // consume 512 bit (64 byte) chunks
55 | var t, a, b, c, d, e, f, i;
56 | var len = bytes.length();
57 | while(len >= 64) {
58 | // the w array will be populated with sixteen 32-bit big-endian words
59 | // and then extended into 80 32-bit words according to SHA-1 algorithm
60 | // and for 32-79 using Max Locktyukhin's optimization
61 |
62 | // initialize hash value for this chunk
63 | a = s.h0;
64 | b = s.h1;
65 | c = s.h2;
66 | d = s.h3;
67 | e = s.h4;
68 |
69 | // round 1
70 | for(i = 0; i < 16; ++i) {
71 | t = bytes.getInt32();
72 | w[i] = t;
73 | f = d ^ (b & (c ^ d));
74 | t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
75 | e = d;
76 | d = c;
77 | c = (b << 30) | (b >>> 2);
78 | b = a;
79 | a = t;
80 | }
81 | for(; i < 20; ++i) {
82 | t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
83 | t = (t << 1) | (t >>> 31);
84 | w[i] = t;
85 | f = d ^ (b & (c ^ d));
86 | t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
87 | e = d;
88 | d = c;
89 | c = (b << 30) | (b >>> 2);
90 | b = a;
91 | a = t;
92 | }
93 | // round 2
94 | for(; i < 32; ++i) {
95 | t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
96 | t = (t << 1) | (t >>> 31);
97 | w[i] = t;
98 | f = b ^ c ^ d;
99 | t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
100 | e = d;
101 | d = c;
102 | c = (b << 30) | (b >>> 2);
103 | b = a;
104 | a = t;
105 | }
106 | for(; i < 40; ++i) {
107 | t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
108 | t = (t << 2) | (t >>> 30);
109 | w[i] = t;
110 | f = b ^ c ^ d;
111 | t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
112 | e = d;
113 | d = c;
114 | c = (b << 30) | (b >>> 2);
115 | b = a;
116 | a = t;
117 | }
118 | // round 3
119 | for(; i < 60; ++i) {
120 | t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
121 | t = (t << 2) | (t >>> 30);
122 | w[i] = t;
123 | f = (b & c) | (d & (b ^ c));
124 | t = ((a << 5) | (a >>> 27)) + f + e + 0x8F1BBCDC + t;
125 | e = d;
126 | d = c;
127 | c = (b << 30) | (b >>> 2);
128 | b = a;
129 | a = t;
130 | }
131 | // round 4
132 | for(; i < 80; ++i) {
133 | t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
134 | t = (t << 2) | (t >>> 30);
135 | w[i] = t;
136 | f = b ^ c ^ d;
137 | t = ((a << 5) | (a >>> 27)) + f + e + 0xCA62C1D6 + t;
138 | e = d;
139 | d = c;
140 | c = (b << 30) | (b >>> 2);
141 | b = a;
142 | a = t;
143 | }
144 |
145 | // update hash state
146 | s.h0 += a;
147 | s.h1 += b;
148 | s.h2 += c;
149 | s.h3 += d;
150 | s.h4 += e;
151 |
152 | len -= 64;
153 | }
154 | };
155 |
156 | /**
157 | * Creates a SHA-1 message digest object.
158 | *
159 | * @return a message digest object.
160 | */
161 | sha1.create = function() {
162 | // do initialization as necessary
163 | if(!_initialized) {
164 | _init();
165 | }
166 |
167 | // SHA-1 state contains five 32-bit integers
168 | var _state = null;
169 |
170 | // input buffer
171 | var _input = forge.util.createBuffer();
172 |
173 | // used for word storage
174 | var _w = new Array(80);
175 |
176 | // message digest object
177 | var md = {
178 | algorithm: 'sha1',
179 | blockLength: 64,
180 | digestLength: 20,
181 | // length of message so far (does not including padding)
182 | messageLength: 0
183 | };
184 |
185 | /**
186 | * Starts the digest.
187 | */
188 | md.start = function() {
189 | md.messageLength = 0;
190 | _input = forge.util.createBuffer();
191 | _state = {
192 | h0: 0x67452301,
193 | h1: 0xEFCDAB89,
194 | h2: 0x98BADCFE,
195 | h3: 0x10325476,
196 | h4: 0xC3D2E1F0
197 | };
198 | };
199 | // start digest automatically for first time
200 | md.start();
201 |
202 | /**
203 | * Updates the digest with the given message input. The given input can
204 | * treated as raw input (no encoding will be applied) or an encoding of
205 | * 'utf8' maybe given to encode the input using UTF-8.
206 | *
207 | * @param msg the message input to update with.
208 | * @param encoding the encoding to use (default: 'raw', other: 'utf8').
209 | */
210 | md.update = function(msg, encoding) {
211 | if(encoding === 'utf8') {
212 | msg = forge.util.encodeUtf8(msg);
213 | }
214 |
215 | // update message length
216 | md.messageLength += msg.length;
217 |
218 | // add bytes to input buffer
219 | _input.putBytes(msg);
220 |
221 | // process bytes
222 | _update(_state, _w, _input);
223 |
224 | // compact input buffer every 2K or if empty
225 | if(_input.read > 2048 || _input.length() === 0) {
226 | _input.compact();
227 | }
228 | };
229 |
230 | /**
231 | * Produces the digest.
232 | *
233 | * @return a byte buffer containing the digest value.
234 | */
235 | md.digest = function() {
236 | /* Note: Here we copy the remaining bytes in the input buffer and
237 | add the appropriate SHA-1 padding. Then we do the final update
238 | on a copy of the state so that if the user wants to get
239 | intermediate digests they can do so. */
240 |
241 | /* Determine the number of bytes that must be added to the message
242 | to ensure its length is congruent to 448 mod 512. In other words,
243 | a 64-bit integer that gives the length of the message will be
244 | appended to the message and whatever the length of the message is
245 | plus 64 bits must be a multiple of 512. So the length of the
246 | message must be congruent to 448 mod 512 because 512 - 64 = 448.
247 |
248 | In order to fill up the message length it must be filled with
249 | padding that begins with 1 bit followed by all 0 bits. Padding
250 | must *always* be present, so if the message length is already
251 | congruent to 448 mod 512, then 512 padding bits must be added. */
252 |
253 | // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
254 | // _padding starts with 1 byte with first bit is set in it which
255 | // is byte value 128, then there may be up to 63 other pad bytes
256 | var len = md.messageLength;
257 | var padBytes = forge.util.createBuffer();
258 | padBytes.putBytes(_input.bytes());
259 | padBytes.putBytes(_padding.substr(0, 64 - ((len + 8) % 64)));
260 |
261 | /* Now append length of the message. The length is appended in bits
262 | as a 64-bit number in big-endian order. Since we store the length
263 | in bytes, we must multiply it by 8 (or left shift by 3). So here
264 | store the high 3 bits in the low end of the first 32-bits of the
265 | 64-bit number and the lower 5 bits in the high end of the second
266 | 32-bits. */
267 | padBytes.putInt32((len >>> 29) & 0xFF);
268 | padBytes.putInt32((len << 3) & 0xFFFFFFFF);
269 | var s2 = {
270 | h0: _state.h0,
271 | h1: _state.h1,
272 | h2: _state.h2,
273 | h3: _state.h3,
274 | h4: _state.h4
275 | };
276 | _update(s2, _w, padBytes);
277 | var rval = forge.util.createBuffer();
278 | rval.putInt32(s2.h0);
279 | rval.putInt32(s2.h1);
280 | rval.putInt32(s2.h2);
281 | rval.putInt32(s2.h3);
282 | rval.putInt32(s2.h4);
283 | return rval;
284 | };
285 |
286 | return md;
287 | };
288 |
289 | })();
290 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/forge/sha256.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Secure Hash Algorithm with 256-bit digest (SHA-256) implementation.
3 | *
4 | * See FIPS 180-2 for details.
5 | *
6 | * This implementation is currently limited to message lengths (in bytes) that
7 | * are up to 32-bits in size.
8 | *
9 | * @author Dave Longley
10 | *
11 | * Copyright (c) 2010-2012 Digital Bazaar, Inc.
12 | */
13 | (function() {
14 |
15 | var sha256 = {};
16 |
17 | // define forge
18 | if(typeof(window) !== 'undefined') {
19 | var forge = window.forge = window.forge || {};
20 | }
21 | else if(typeof(module) !== 'undefined' && module.exports) {
22 | var forge = {
23 | util: require('./util')
24 | };
25 | module.exports = sha256 = {};
26 | }
27 | forge.md = forge.md || {};
28 | forge.md.algorithms = forge.md.algorithms || {};
29 | forge.md.sha256 = forge.md.algorithms['sha256'] = sha256;
30 |
31 | // sha-256 padding bytes not initialized yet
32 | var _padding = null;
33 | var _initialized = false;
34 |
35 | // table of constants
36 | var _k = null;
37 |
38 | /**
39 | * Initializes the constant tables.
40 | */
41 | var _init = function() {
42 | // create padding
43 | _padding = String.fromCharCode(128);
44 | _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
45 |
46 | // create K table for SHA-256
47 | _k = [
48 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
49 | 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
50 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
51 | 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
52 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
53 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
54 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
55 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
56 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
57 | 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
58 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
59 | 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
60 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
61 | 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
62 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
63 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
64 |
65 | // now initialized
66 | _initialized = true;
67 | };
68 |
69 | /**
70 | * Updates a SHA-256 state with the given byte buffer.
71 | *
72 | * @param s the SHA-256 state to update.
73 | * @param w the array to use to store words.
74 | * @param bytes the byte buffer to update with.
75 | */
76 | var _update = function(s, w, bytes) {
77 | // consume 512 bit (64 byte) chunks
78 | var t1, t2, s0, s1, ch, maj, i, a, b, c, d, e, f, g, h;
79 | var len = bytes.length();
80 | while(len >= 64) {
81 | // the w array will be populated with sixteen 32-bit big-endian words
82 | // and then extended into 64 32-bit words according to SHA-256
83 | for(i = 0; i < 16; ++i) {
84 | w[i] = bytes.getInt32();
85 | }
86 | for(; i < 64; ++i) {
87 | // XOR word 2 words ago rot right 17, rot right 19, shft right 10
88 | t1 = w[i - 2];
89 | t1 =
90 | ((t1 >>> 17) | (t1 << 15)) ^
91 | ((t1 >>> 19) | (t1 << 13)) ^
92 | (t1 >>> 10);
93 | // XOR word 15 words ago rot right 7, rot right 18, shft right 3
94 | t2 = w[i - 15];
95 | t2 =
96 | ((t2 >>> 7) | (t2 << 25)) ^
97 | ((t2 >>> 18) | (t2 << 14)) ^
98 | (t2 >>> 3);
99 | // sum(t1, word 7 ago, t2, word 16 ago) modulo 2^32
100 | w[i] = (t1 + w[i - 7] + t2 + w[i - 16]) & 0xFFFFFFFF;
101 | }
102 |
103 | // initialize hash value for this chunk
104 | a = s.h0;
105 | b = s.h1;
106 | c = s.h2;
107 | d = s.h3;
108 | e = s.h4;
109 | f = s.h5;
110 | g = s.h6;
111 | h = s.h7;
112 |
113 | // round function
114 | for(i = 0; i < 64; ++i) {
115 | // Sum1(e)
116 | s1 =
117 | ((e >>> 6) | (e << 26)) ^
118 | ((e >>> 11) | (e << 21)) ^
119 | ((e >>> 25) | (e << 7));
120 | // Ch(e, f, g) (optimized the same way as SHA-1)
121 | ch = g ^ (e & (f ^ g));
122 | // Sum0(a)
123 | s0 =
124 | ((a >>> 2) | (a << 30)) ^
125 | ((a >>> 13) | (a << 19)) ^
126 | ((a >>> 22) | (a << 10));
127 | // Maj(a, b, c) (optimized the same way as SHA-1)
128 | maj = (a & b) | (c & (a ^ b));
129 |
130 | // main algorithm
131 | t1 = h + s1 + ch + _k[i] + w[i];
132 | t2 = s0 + maj;
133 | h = g;
134 | g = f;
135 | f = e;
136 | e = (d + t1) & 0xFFFFFFFF;
137 | d = c;
138 | c = b;
139 | b = a;
140 | a = (t1 + t2) & 0xFFFFFFFF;
141 | }
142 |
143 | // update hash state
144 | s.h0 = (s.h0 + a) & 0xFFFFFFFF;
145 | s.h1 = (s.h1 + b) & 0xFFFFFFFF;
146 | s.h2 = (s.h2 + c) & 0xFFFFFFFF;
147 | s.h3 = (s.h3 + d) & 0xFFFFFFFF;
148 | s.h4 = (s.h4 + e) & 0xFFFFFFFF;
149 | s.h5 = (s.h5 + f) & 0xFFFFFFFF;
150 | s.h6 = (s.h6 + g) & 0xFFFFFFFF;
151 | s.h7 = (s.h7 + h) & 0xFFFFFFFF;
152 | len -= 64;
153 | }
154 | };
155 |
156 | /**
157 | * Creates a SHA-256 message digest object.
158 | *
159 | * @return a message digest object.
160 | */
161 | sha256.create = function() {
162 | // do initialization as necessary
163 | if(!_initialized) {
164 | _init();
165 | }
166 |
167 | // SHA-256 state contains eight 32-bit integers
168 | var _state = null;
169 |
170 | // input buffer
171 | var _input = forge.util.createBuffer();
172 |
173 | // used for word storage
174 | var _w = new Array(64);
175 |
176 | // message digest object
177 | var md = {
178 | algorithm: 'sha256',
179 | blockLength: 64,
180 | digestLength: 32,
181 | // length of message so far (does not including padding)
182 | messageLength: 0
183 | };
184 |
185 | /**
186 | * Starts the digest.
187 | */
188 | md.start = function() {
189 | md.messageLength = 0;
190 | _input = forge.util.createBuffer();
191 | _state = {
192 | h0: 0x6A09E667,
193 | h1: 0xBB67AE85,
194 | h2: 0x3C6EF372,
195 | h3: 0xA54FF53A,
196 | h4: 0x510E527F,
197 | h5: 0x9B05688C,
198 | h6: 0x1F83D9AB,
199 | h7: 0x5BE0CD19
200 | };
201 | };
202 | // start digest automatically for first time
203 | md.start();
204 |
205 | /**
206 | * Updates the digest with the given message input. The given input can
207 | * treated as raw input (no encoding will be applied) or an encoding of
208 | * 'utf8' maybe given to encode the input using UTF-8.
209 | *
210 | * @param msg the message input to update with.
211 | * @param encoding the encoding to use (default: 'raw', other: 'utf8').
212 | */
213 | md.update = function(msg, encoding) {
214 | if(encoding === 'utf8') {
215 | msg = forge.util.encodeUtf8(msg);
216 | }
217 |
218 | // update message length
219 | md.messageLength += msg.length;
220 |
221 | // add bytes to input buffer
222 | _input.putBytes(msg);
223 |
224 | // process bytes
225 | _update(_state, _w, _input);
226 |
227 | // compact input buffer every 2K or if empty
228 | if(_input.read > 2048 || _input.length() === 0) {
229 | _input.compact();
230 | }
231 | };
232 |
233 | /**
234 | * Produces the digest.
235 | *
236 | * @return a byte buffer containing the digest value.
237 | */
238 | md.digest = function() {
239 | /* Note: Here we copy the remaining bytes in the input buffer and
240 | add the appropriate SHA-256 padding. Then we do the final update
241 | on a copy of the state so that if the user wants to get
242 | intermediate digests they can do so. */
243 |
244 | /* Determine the number of bytes that must be added to the message
245 | to ensure its length is congruent to 448 mod 512. In other words,
246 | a 64-bit integer that gives the length of the message will be
247 | appended to the message and whatever the length of the message is
248 | plus 64 bits must be a multiple of 512. So the length of the
249 | message must be congruent to 448 mod 512 because 512 - 64 = 448.
250 |
251 | In order to fill up the message length it must be filled with
252 | padding that begins with 1 bit followed by all 0 bits. Padding
253 | must *always* be present, so if the message length is already
254 | congruent to 448 mod 512, then 512 padding bits must be added. */
255 |
256 | // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
257 | // _padding starts with 1 byte with first bit is set in it which
258 | // is byte value 128, then there may be up to 63 other pad bytes
259 | var len = md.messageLength;
260 | var padBytes = forge.util.createBuffer();
261 | padBytes.putBytes(_input.bytes());
262 | padBytes.putBytes(_padding.substr(0, 64 - ((len + 8) % 64)));
263 |
264 | /* Now append length of the message. The length is appended in bits
265 | as a 64-bit number in big-endian order. Since we store the length
266 | in bytes, we must multiply it by 8 (or left shift by 3). So here
267 | store the high 3 bits in the low end of the first 32-bits of the
268 | 64-bit number and the lower 5 bits in the high end of the second
269 | 32-bits. */
270 | padBytes.putInt32((len >>> 29) & 0xFF);
271 | padBytes.putInt32((len << 3) & 0xFFFFFFFF);
272 | var s2 = {
273 | h0: _state.h0,
274 | h1: _state.h1,
275 | h2: _state.h2,
276 | h3: _state.h3,
277 | h4: _state.h4,
278 | h5: _state.h5,
279 | h6: _state.h6,
280 | h7: _state.h7
281 | };
282 | _update(s2, _w, padBytes);
283 | var rval = forge.util.createBuffer();
284 | rval.putInt32(s2.h0);
285 | rval.putInt32(s2.h1);
286 | rval.putInt32(s2.h2);
287 | rval.putInt32(s2.h3);
288 | rval.putInt32(s2.h4);
289 | rval.putInt32(s2.h5);
290 | rval.putInt32(s2.h6);
291 | rval.putInt32(s2.h7);
292 | return rval;
293 | };
294 |
295 | return md;
296 | };
297 |
298 | })();
299 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/form2js.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2010 Maxim Vasiliev
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | *
22 | * @author Maxim Vasiliev
23 | * Date: 09.09.2010
24 | * Time: 19:02:33
25 | */
26 |
27 |
28 | var form2js = (function()
29 | {
30 | "use strict";
31 |
32 | /**
33 | * Returns form values represented as Javascript object
34 | * "name" attribute defines structure of resulting object
35 | *
36 | * @param rootNode {Element|String} root form element (or it's id) or array of root elements
37 | * @param delimiter {String} structure parts delimiter defaults to '.'
38 | * @param skipEmpty {Boolean} should skip empty text values, defaults to true
39 | * @param nodeCallback {Function} custom function to get node value
40 | * @param useIdIfEmptyName {Boolean} if true value of id attribute of field will be used if name of field is empty
41 | */
42 | function form2js(rootNode, delimiter, skipEmpty, nodeCallback, useIdIfEmptyName)
43 | {
44 | if (typeof skipEmpty == 'undefined' || skipEmpty == null) skipEmpty = true;
45 | if (typeof delimiter == 'undefined' || delimiter == null) delimiter = '.';
46 | if (arguments.length < 5) useIdIfEmptyName = false;
47 |
48 | rootNode = typeof rootNode == 'string' ? document.getElementById(rootNode) : rootNode;
49 |
50 | var formValues = [],
51 | currNode,
52 | i = 0;
53 |
54 | /* If rootNode is array - combine values */
55 | if (rootNode.constructor == Array || (typeof NodeList != "undefined" && rootNode.constructor == NodeList))
56 | {
57 | while(currNode = rootNode[i++])
58 | {
59 | formValues = formValues.concat(getFormValues(currNode, nodeCallback, useIdIfEmptyName));
60 | }
61 | }
62 | else
63 | {
64 | formValues = getFormValues(rootNode, nodeCallback, useIdIfEmptyName);
65 | }
66 |
67 | return processNameValues(formValues, skipEmpty, delimiter);
68 | }
69 |
70 | /**
71 | * Processes collection of { name: 'name', value: 'value' } objects.
72 | * @param nameValues
73 | * @param skipEmpty if true skips elements with value == '' or value == null
74 | * @param delimiter
75 | */
76 | function processNameValues(nameValues, skipEmpty, delimiter)
77 | {
78 | var result = {},
79 | arrays = {},
80 | i, j, k, l,
81 | value,
82 | nameParts,
83 | currResult,
84 | arrNameFull,
85 | arrName,
86 | arrIdx,
87 | namePart,
88 | name,
89 | _nameParts;
90 |
91 | for (i = 0; i < nameValues.length; i++)
92 | {
93 | value = nameValues[i].value;
94 |
95 | if (skipEmpty && (value === '' || value === null)) continue;
96 |
97 | name = nameValues[i].name;
98 | _nameParts = name.split(delimiter);
99 | nameParts = [];
100 | currResult = result;
101 | arrNameFull = '';
102 |
103 | for(j = 0; j < _nameParts.length; j++)
104 | {
105 | namePart = _nameParts[j].split('][');
106 | if (namePart.length > 1)
107 | {
108 | for(k = 0; k < namePart.length; k++)
109 | {
110 | if (k == 0)
111 | {
112 | namePart[k] = namePart[k] + ']';
113 | }
114 | else if (k == namePart.length - 1)
115 | {
116 | namePart[k] = '[' + namePart[k];
117 | }
118 | else
119 | {
120 | namePart[k] = '[' + namePart[k] + ']';
121 | }
122 |
123 | arrIdx = namePart[k].match(/([a-z_]+)?\[([a-z_][a-z0-9_]+?)\]/i);
124 | if (arrIdx)
125 | {
126 | for(l = 1; l < arrIdx.length; l++)
127 | {
128 | if (arrIdx[l]) nameParts.push(arrIdx[l]);
129 | }
130 | }
131 | else{
132 | nameParts.push(namePart[k]);
133 | }
134 | }
135 | }
136 | else
137 | nameParts = nameParts.concat(namePart);
138 | }
139 |
140 | for (j = 0; j < nameParts.length; j++)
141 | {
142 | namePart = nameParts[j];
143 |
144 | if (namePart.indexOf('[]') > -1 && j == nameParts.length - 1)
145 | {
146 | arrName = namePart.substr(0, namePart.indexOf('['));
147 | arrNameFull += arrName;
148 |
149 | if (!currResult[arrName]) currResult[arrName] = [];
150 | currResult[arrName].push(value);
151 | }
152 | else if (namePart.indexOf('[') > -1)
153 | {
154 | arrName = namePart.substr(0, namePart.indexOf('['));
155 | arrIdx = namePart.replace(/(^([a-z_]+)?\[)|(\]$)/gi, '');
156 |
157 | /* Unique array name */
158 | arrNameFull += '_' + arrName + '_' + arrIdx;
159 |
160 | /*
161 | * Because arrIdx in field name can be not zero-based and step can be
162 | * other than 1, we can't use them in target array directly.
163 | * Instead we're making a hash where key is arrIdx and value is a reference to
164 | * added array element
165 | */
166 |
167 | if (!arrays[arrNameFull]) arrays[arrNameFull] = {};
168 | if (arrName != '' && !currResult[arrName]) currResult[arrName] = [];
169 |
170 | if (j == nameParts.length - 1)
171 | {
172 | if (arrName == '')
173 | {
174 | currResult.push(value);
175 | arrays[arrNameFull][arrIdx] = currResult[currResult.length - 1];
176 | }
177 | else
178 | {
179 | currResult[arrName].push(value);
180 | arrays[arrNameFull][arrIdx] = currResult[arrName][currResult[arrName].length - 1];
181 | }
182 | }
183 | else
184 | {
185 | if (!arrays[arrNameFull][arrIdx])
186 | {
187 | if ((/^[a-z_]+\[?/i).test(nameParts[j+1])) currResult[arrName].push({});
188 | else currResult[arrName].push([]);
189 |
190 | arrays[arrNameFull][arrIdx] = currResult[arrName][currResult[arrName].length - 1];
191 | }
192 | }
193 |
194 | currResult = arrays[arrNameFull][arrIdx];
195 | }
196 | else
197 | {
198 | arrNameFull += namePart;
199 |
200 | if (j < nameParts.length - 1) /* Not the last part of name - means object */
201 | {
202 | if (!currResult[namePart]) currResult[namePart] = {};
203 | currResult = currResult[namePart];
204 | }
205 | else
206 | {
207 | currResult[namePart] = value;
208 | }
209 | }
210 | }
211 | }
212 |
213 | return result;
214 | }
215 |
216 | function getFormValues(rootNode, nodeCallback, useIdIfEmptyName)
217 | {
218 | var result = extractNodeValues(rootNode, nodeCallback, useIdIfEmptyName);
219 | return result.length > 0 ? result : getSubFormValues(rootNode, nodeCallback, useIdIfEmptyName);
220 | }
221 |
222 | function getSubFormValues(rootNode, nodeCallback, useIdIfEmptyName)
223 | {
224 | var result = [],
225 | currentNode = rootNode.firstChild;
226 |
227 | while (currentNode)
228 | {
229 | result = result.concat(extractNodeValues(currentNode, nodeCallback, useIdIfEmptyName));
230 | currentNode = currentNode.nextSibling;
231 | }
232 |
233 | return result;
234 | }
235 |
236 | function extractNodeValues(node, nodeCallback, useIdIfEmptyName) {
237 | var callbackResult, fieldValue, result, fieldName = getFieldName(node, useIdIfEmptyName);
238 |
239 | callbackResult = nodeCallback && nodeCallback(node);
240 |
241 | if (callbackResult && callbackResult.name) {
242 | result = [callbackResult];
243 | }
244 | else if (fieldName != '' && node.nodeName.match(/INPUT|TEXTAREA/i)) {
245 | fieldValue = getFieldValue(node);
246 | result = [ { name: fieldName, value: fieldValue} ];
247 | }
248 | else if (fieldName != '' && node.nodeName.match(/SELECT/i)) {
249 | fieldValue = getFieldValue(node);
250 | result = [ { name: fieldName.replace(/\[\]$/, ''), value: fieldValue } ];
251 | }
252 | else {
253 | result = getSubFormValues(node, nodeCallback, useIdIfEmptyName);
254 | }
255 |
256 | return result;
257 | }
258 |
259 | function getFieldName(node, useIdIfEmptyName)
260 | {
261 | if (node.name && node.name != '') return node.name;
262 | else if (useIdIfEmptyName && node.id && node.id != '') return node.id;
263 | else return '';
264 | }
265 |
266 |
267 | function getFieldValue(fieldNode)
268 | {
269 | if (fieldNode.disabled) return null;
270 |
271 | switch (fieldNode.nodeName) {
272 | case 'INPUT':
273 | case 'TEXTAREA':
274 | switch (fieldNode.type.toLowerCase()) {
275 | case 'radio':
276 | case 'checkbox':
277 | if (fieldNode.checked && fieldNode.value === "true") return true;
278 | if (!fieldNode.checked && fieldNode.value === "true") return false;
279 | if (fieldNode.checked) return fieldNode.value;
280 | break;
281 |
282 | case 'button':
283 | case 'reset':
284 | case 'submit':
285 | case 'image':
286 | return '';
287 | break;
288 |
289 | default:
290 | return fieldNode.value;
291 | break;
292 | }
293 | break;
294 |
295 | case 'SELECT':
296 | return getSelectedOptionValue(fieldNode);
297 | break;
298 |
299 | default:
300 | break;
301 | }
302 |
303 | return null;
304 | }
305 |
306 | function getSelectedOptionValue(selectNode)
307 | {
308 | var multiple = selectNode.multiple,
309 | result = [],
310 | options,
311 | i, l;
312 |
313 | if (!multiple) return selectNode.value;
314 |
315 | for (options = selectNode.getElementsByTagName("option"), i = 0, l = options.length; i < l; i++)
316 | {
317 | if (options[i].selected) result.push(options[i].value);
318 | }
319 |
320 | return result;
321 | }
322 |
323 | return form2js;
324 |
325 | })();
--------------------------------------------------------------------------------
/vendor/assets/javascripts/jquery.toObject.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2010 Maxim Vasiliev
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | *
22 | * @author Maxim Vasiliev
23 | * Date: 29.06.11
24 | * Time: 20:09
25 | */
26 |
27 | (function($){
28 |
29 | /**
30 | * jQuery wrapper for form2object()
31 | * Extracts data from child inputs into javascript object
32 | */
33 | $.fn.toObject = function(options)
34 | {
35 | var result = [],
36 | settings = {
37 | mode: 'first', // what to convert: 'all' or 'first' matched node
38 | delimiter: ".",
39 | skipEmpty: true,
40 | nodeCallback: null,
41 | useIdIfEmptyName: false
42 | };
43 |
44 | if (options)
45 | {
46 | $.extend(settings, options);
47 | }
48 |
49 | switch(settings.mode)
50 | {
51 | case 'first':
52 | return form2js(this.get(0), settings.delimiter, settings.skipEmpty, settings.nodeCallback, settings.useIdIfEmptyName);
53 | break;
54 | case 'all':
55 | this.each(function(){
56 | result.push(form2js(this, settings.delimiter, settings.skipEmpty, settings.nodeCallback, settings.useIdIfEmptyName));
57 | });
58 | return result;
59 | break;
60 | case 'combine':
61 | return form2js(Array.prototype.slice.call(this), settings.delimiter, settings.skipEmpty, settings.nodeCallback, settings.useIdIfEmptyName);
62 | break;
63 | }
64 | }
65 |
66 | })(jQuery);
--------------------------------------------------------------------------------
/vendor/assets/swf/ZeroClipboard.swf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/swordfish/1b38b02bf1c5e980881d10b27679eed9c1a9262f/vendor/assets/swf/ZeroClipboard.swf
--------------------------------------------------------------------------------