touchpass 0.0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module Touchpass
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+ source_root File.expand_path("../../templates", __FILE__)
9
+
10
+ desc "Install Touchpass files (migrations, initializers, locales)."
11
+
12
+ def create_migration_file
13
+ # no db config for relying partiues
14
+ end
15
+
16
+ def copy_initializer_files
17
+ copy_file "config/initializers/devise.rb", "config/initializers/devise.rb"
18
+ copy_file "config/initializers/touchpass.rb", "config/initializers/touchpass.rb"
19
+ end
20
+
21
+ def copy_locale_files
22
+ copy_file "config/locales/devise.en.yml", "config/locales/devise.en.yml"
23
+ end
24
+
25
+ private
26
+ def self.next_migration_number(dirname)
27
+ if ActiveRecord::Base.timestamped_migrations
28
+ Time.new.utc.strftime("%Y%m%d%H%M%S")
29
+ else
30
+ "%.3d" % (current_migration_number(dirname) + 1)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,431 @@
1
+ # Geodica Touchpass
2
+ # (C) Copyright 2009-2012 Geodica, a Carpadium Pty Ltd Venture
3
+ # All rights reserved
4
+
5
+ require 'rubygems'
6
+ require 'thor'
7
+ require 'httparty'
8
+ require 'openssl'
9
+ require 'fileutils'
10
+ require 'base64'
11
+ require 'pp'
12
+ require 'cgi'
13
+ require 'json'
14
+
15
+ module Touchpass
16
+
17
+ # Provide a simple Ruby interface to a Touchpass verification server
18
+ class Client
19
+
20
+ attr_accessor :hostname, :debug, :api_key
21
+
22
+ API_KEY_HEADER = "X-TouchPass-ApiKey"
23
+ ENVIRONMENT = ENV['RAILS_ENV']
24
+
25
+ ## Host and port to use for service invocations, default to the main Touchpass server
26
+ DFT_HOSTNAME = ENVIRONMENT == 'production' ? "https://touchpass.geodica.com" : "https://localhost:3000"
27
+
28
+ # Default response output to json
29
+ DFT_OUTPUT = "json"
30
+
31
+ # Default debug state (on: dev|test, off: prod)
32
+ DFT_DEBUG = ENV['TPC_DEBUG'] ? true : false
33
+
34
+ # Create a new Touchpass client instance and default the hostname and output
35
+ def initialize(hostname=DFT_HOSTNAME, debug=DFT_DEBUG)
36
+ @hostname = hostname.nil? ? DFT_HOSTNAME : hostname
37
+ @debug = debug
38
+ @output = DFT_OUTPUT # always use json internally
39
+ end
40
+
41
+ # Register a new relying party with the Touchpass server
42
+ # needs: :email, :username, :password
43
+ def register_party(params)
44
+ http_options = standard_http_options(params, %W{email username password})
45
+ response = submit_request("post", "#{@hostname}/parties.#{@output}", http_options)
46
+ set_api_key_from_response(response)
47
+ response
48
+ end
49
+
50
+ # Authenticate a party to obtain the API key
51
+ # needs: :login, :password
52
+ def authenticate_party(params)
53
+ http_options = standard_http_options(params, %W{login password})
54
+
55
+ response = submit_request(
56
+ "post",
57
+ "#{@hostname}/parties/authenticate.#{@output}",
58
+ http_options)
59
+ set_api_key_from_response(response)
60
+ response
61
+ end
62
+
63
+ # Get details for a party
64
+ # needs: :api_key; uses: :id OR :username
65
+ def get_party(params)
66
+ http_options = standard_http_options(params)
67
+
68
+ if !params[:username].nil?
69
+ response = submit_request("get", "#{@hostname}/#{params[:username]}.#{@output}", http_options)
70
+ elsif !params[:id].nil?
71
+ response = submit_request("get", "#{@hostname}/parties/#{params[:id]}.#{@output}", http_options)
72
+ else
73
+ response = format_response({"error" => "Either username or id must be provided."})
74
+ end
75
+ response
76
+ end
77
+
78
+ # Update details for a party
79
+ # needs: :api_key; uses: :username, :id, :email, :first_name, :last_name
80
+ def update_party(params)
81
+ id = params[:id]
82
+ username = params[:username]
83
+ http_options = standard_http_options(params, %W{username email first_name last_name})
84
+
85
+ if !username.nil?
86
+ response = submit_request("put", "#{@hostname}/#{username}.#{@output}", http_options)
87
+ elsif !id.nil?
88
+ response = submit_request("put", "#{@hostname}/parties/#{id}.#{@output}", http_options)
89
+ else
90
+ response = format_response({"error" => "Either username or id must be provided."})
91
+ end
92
+ response
93
+ end
94
+
95
+ # Register a new device for a party
96
+ # needs: :api_key, :username, :udid, :devicename; uses: :messaging_type, :messaging_value
97
+ def register_device(params)
98
+
99
+ # Generate private & public key for this device
100
+ crypt = KeyFileCreator.new(params[:udid])
101
+ crypt.generate_keys
102
+
103
+ # Collect attributes of new device
104
+ device_attributes = params.merge({
105
+ :udid => params[:udid],
106
+ :name => params[:name],
107
+ :pub_key => crypt.public_key,
108
+ :messaging_type => params[:messaging_type],
109
+ :messaging_value => params[:messaging_value],
110
+ })
111
+ http_options = standard_http_options(device_attributes, %W{udid name pub_key messaging_type messaging_value})
112
+
113
+ # Provision the new device with the Touchpass server
114
+ new_device = submit_request("post", "#{@hostname}/#{params[:username]}/devices.#{@output}", http_options)
115
+
116
+ # We made the key files for the new device earlier (see
117
+ # KeyFileCreator above) but at that stage we didn't know the
118
+ # :deviceid of the newly provisioned device. So now we need to
119
+ # move the cert directory around based on the :deviceid returned
120
+ # from TouchPass.
121
+ begin
122
+ devices = submit_request("post", "#{@hostname}/#{params[:username]}/devices.json", http_options)
123
+ raise devices["error"] if devices["error"]
124
+ raise "unknown device id" unless devices["id"]
125
+ device_id = devices["id"].to_s
126
+
127
+ orig_file = crypt.keys_path
128
+ crypt.id = device_id
129
+ new_file = crypt.keys_path
130
+ puts "Renaming directory #{orig_file} to #{new_file}" if @debug
131
+ result = File.rename(orig_file, new_file)
132
+ rescue Exception => e
133
+ puts "Exception: #{e.message}"
134
+ puts "error: updating key file location based on device id: udid:#{params[:udid]}, device_id:#{device_id}"
135
+ end
136
+
137
+ # Return details of the newly provisioned device
138
+ new_device
139
+ end
140
+
141
+ # Get all devices for a party
142
+ # needs: :username, :api_key
143
+ def get_devices(params)
144
+ # Just return the device list for the specified user
145
+ http_options = standard_http_options(params)
146
+ devices = submit_request("get", "#{@hostname}/#{params[:username]}/devices.#{@output}", http_options)
147
+
148
+ # Look for errors, and just build an empty device array hashed from 'devices'
149
+ if !devices['devices'].nil? or !devices['error'].nil?
150
+ devices
151
+ else
152
+ { 'devices' => [] }
153
+ end
154
+ end
155
+
156
+ # Get details for a specific device, returns an 'errors' hash if the device cannot be found
157
+ # needs: :username, :api_key, :id
158
+ def get_device(params)
159
+ # Return the device for the specified user and device id
160
+ http_options = standard_http_options(params)
161
+ submit_request("get", "#{@hostname}/#{params[:username]}/devices/#{params[:id]}.#{@output}", http_options)
162
+ end
163
+
164
+ # Update details of a device, returns an 'errors' hash if the device cannot be found
165
+ # needs: :username, :id, api_key; uses: :name, :messaging_type, :messaging_value, :update_pub_key
166
+ def update_device(params)
167
+ update_pub_key = params[:update_pub_key] # TODO: Regenerate and update pub_key
168
+
169
+ http_options = standard_http_options(params, %W{name messaging_type messaging_value})
170
+
171
+ submit_request("put", "#{@hostname}/#{params[:username]}/devices/#{params[:id]}.#{@output}", http_options)
172
+ end
173
+
174
+ # Remove device, returns an 'errors' hash if the device cannot be found
175
+ # needs: :username, :id, :api_key
176
+ def remove_device(params)
177
+ http_options = standard_http_options(params)
178
+ submit_request("delete", "#{@hostname}/#{params[:username]}/devices/#{params[:id]}.#{@output}", http_options)
179
+ end
180
+
181
+ # Create a new verification
182
+ # needs: :to_party, :api_key; uses: :message, :address (for LV), :resolution (for LV)
183
+ def create_verification(params)
184
+ http_options = standard_http_options(params, %W{to_party})
185
+ message = params[:message]
186
+ address = params[:address]
187
+
188
+ # Get the list of verifying party devices so we can encrypt data for them using each device's public key
189
+ response = submit_request("get", "#{@hostname}/#{params[:to_party]}/devices.json",
190
+ standard_http_options(params))
191
+ vp_devices = response["devices"]
192
+
193
+ # Generate token
194
+ token = Crypt.salt
195
+ hashed_token = Crypt.encrypt(token)
196
+ http_options[:body][:claimed_token] = hashed_token
197
+
198
+ if !vp_devices.nil? and (vp_devices.is_a? Array and !vp_devices.empty?)
199
+
200
+ # Encrypt message using each verifying party's (to_party) device public keys
201
+ if !message.nil?
202
+ vp_devices.each_with_index do |device, index|
203
+ device_id = device["id"]
204
+ pub_key_str = device["pub_key"]
205
+ public_key = Crypt.read_key(pub_key_str)
206
+ crypted_message = Base64.encode64(public_key.public_encrypt(message))
207
+ http_options[:body]["crypted_messages[#{index}][device_id]"] = device_id
208
+ http_options[:body]["crypted_messages[#{index}][value]"] = crypted_message
209
+ end
210
+ end
211
+
212
+ # Encrypt token using each verifying party's (to_party) device public keys
213
+ crypted_tokens = []
214
+ vp_devices.each_with_index do |device, index|
215
+ device_id = device["id"]
216
+ pub_key_str = device["pub_key"]
217
+ public_key = Crypt.read_key(pub_key_str)
218
+ crypted_token = Base64.encode64(public_key.public_encrypt(token)) # per-transaction token generated by Crypt.salt above
219
+ http_options[:body]["crypted_tokens[#{index}][device_id]"] = device_id
220
+ http_options[:body]["crypted_tokens[#{index}][value]"] = crypted_token
221
+ end
222
+ end
223
+
224
+ # Encrypt prp-hash using verifying_party (to_party) devices
225
+ if !address.nil? # For location verification
226
+ http_options[:body][:location_verification] = true
227
+
228
+ # Convert address to PRP
229
+ resolution = options[:resolution] || "LOCAL" # Resolution used to calculate PRP
230
+ http_options[:body][:resolution] = resolution
231
+ prp = Proximity::PRP.new(:address => params[:address], :resolution => resolution)
232
+ # puts prp.print_bbox # for debugging
233
+
234
+ # Generate random salt
235
+ salt = Crypt.salt
236
+ # puts "Using salt: #{salt}" # for debugging
237
+
238
+ # Encrypt PRP using salt
239
+ claimed_prp = prp.encrypt(salt)
240
+ http_options[:body][:claimed_prp] = claimed_prp
241
+
242
+ # Encrypt salt using verifiying_party (to_party) devices
243
+ crypted_salts = []
244
+ vp_devices.each_with_index do |device, index|
245
+ device_id = device["id"]
246
+ pub_key_str = device["pub_key"]
247
+ public_key = Crypt.read_key(pub_key_str)
248
+ crypted_salt = Base64.encode64(public_key.public_encrypt(salt))
249
+ http_options[:body]["crypted_salts[#{index}][device_id]"] = device_id
250
+ http_options[:body]["crypted_salts[#{index}][value]"] = crypted_salt
251
+ end
252
+ end
253
+
254
+ submit_request("post", "#{@hostname}/verifications.#{@output}", http_options)
255
+ end
256
+
257
+ # Get a page of verifications for a particular party
258
+ # needs: :api_key; uses: :username, :party_id
259
+ def get_verifications(params)
260
+ http_options = standard_http_options(params)
261
+ response = nil
262
+ if !params[:username].nil?
263
+ response = submit_request("get", "#{@hostname}/#{params[:username]}/verifications.#{@output}", http_options)
264
+ elsif !params[:party_id].nil?
265
+ http_options[:body][:party_id] = params[:party_id]
266
+ response = submit_request("get", "#{@hostname}/verifications.#{@output}", http_options)
267
+ else
268
+ response = {"error" => "Either username or id must be provided."}
269
+ end
270
+ response
271
+ end
272
+
273
+ # Get details of a specific verification, will include decrypted message if :device_id is provided
274
+ # needs: :api_key, :id; uses: :device_id
275
+ def get_verification(params)
276
+ http_options = standard_http_options(params)
277
+ response = submit_request("get", "#{@hostname}/verifications/#{params[:id]}.json", http_options)
278
+
279
+ # TODO: as this stands, it will not decrypt the message unless a :device_id is provided. However,
280
+ # there is currently no way to pass in the keys to use - it will just try to use the local keys
281
+ # stored in #{local_key_path}/:device_id. So, we need to fix this up at some point.
282
+ decrypted_message = nil
283
+ if response.has_key?("crypted_messages") and !response["crypted_messages"].empty?
284
+ if !params[:device_id].nil?
285
+ decrypted_message = decrypt_salt(response["crypted_messages"], params[:device_id], self.api_key)
286
+ end
287
+ end
288
+ # return verification plus decrypted message (if available)
289
+ response[:decrypted_message] = decrypted_message
290
+ response
291
+ end
292
+
293
+ # Update Verification
294
+ # needs: :api_key, :id, :device_id; uses: :address
295
+ def update_verification(params)
296
+ # Call show verification to get the crypted salts and resolution for the verification
297
+ # Note: crypted salts and resolution are also returned in the list verifications response
298
+ http_options = standard_http_options(params)
299
+
300
+ verification = submit_request("get", "#{@hostname}/verifications/#{params[:id]}.json", http_options)
301
+ crypted_tokens = verification["crypted_tokens"]
302
+ crypted_salts = verification["crypted_salts"]
303
+ resolution = verification["resolution"]
304
+
305
+ token = decrypt_salt(crypted_tokens, params[:device_id], self.api_key)
306
+ hashed_token = Crypt.encrypt(token)
307
+
308
+ http_options = standard_http_options(params, %W{id})
309
+ http_options[:body][:verified_token] = hashed_token
310
+
311
+ # Generate verified PRP for location verification
312
+ if !params[:address].nil?
313
+ prp = Proximity::PRP.new(:address => params[:address], :resolution => resolution)
314
+ # puts prp.print_bbox # for debugging
315
+
316
+ salt = decrypt_salt(crypted_salts, params[:device_id], self.api_key)
317
+ # puts "Using salt: #{salt}"
318
+ verified_prp = prp.encrypt(salt)
319
+
320
+ http_options[:body][:verified_prp] = verified_prp
321
+ # puts "Verified PRP: #{verified_prp}"
322
+ end
323
+ submit_request("put", "#{@hostname}/verifications/#{params[:id]}.#{@output}",
324
+ http_options)
325
+ end
326
+
327
+ # Cancel an existing verification
328
+ # needs: :api_key, :id
329
+ def cancel_verification(params)
330
+ submit_request("put", "#{@hostname}/verifications/#{params[:id]}/cancel.#{@output}",
331
+ standard_http_options(params))
332
+ end
333
+
334
+ # Reject a verification
335
+ # needs: :api_keu, :id
336
+ def reject_verification(params)
337
+ submit_request("put", "#{@hostname}/verifications/#{params[:id]}/reject.#{@output}",
338
+ standard_http_options(params))
339
+ end
340
+
341
+ private
342
+
343
+ # Decrypt salt for a specific device and api_key
344
+ def decrypt_salt(crypted_salts, device_id, api_key)
345
+ return nil if crypted_salts.nil?
346
+
347
+ # Find crypted salt associated with the device
348
+ crypted_salt = crypted_salts.detect{|cs| cs["device_id"] == device_id.to_i}
349
+ return nil if crypted_salt.nil?
350
+ device_id = crypted_salt["device_id"].to_s
351
+ ct = crypted_salt["value"]
352
+
353
+ # Load the private key
354
+ crypt = KeyFileCreator.new(device_id)
355
+ private_key = crypt.private_key()
356
+
357
+ # Decrypt the crypted salt
358
+ salt = private_key.private_decrypt(Base64.decode64(ct))
359
+ return salt
360
+ end
361
+
362
+ # Returns true if the repsonse contains errors
363
+ def errors?(response)
364
+ response.nil? || response['error'] || response['errors']
365
+ end
366
+
367
+ def error_message(response)
368
+ msg = ""
369
+ if response['error']
370
+ msg = response['error']
371
+ elsif response['errors'].kind_of(Array)
372
+ # TODO
373
+ msg = response['errors'].inspect
374
+ end
375
+ msg
376
+ end
377
+
378
+ def standard_http_options(params, body_params = [])
379
+ http_options = { :body => {}, :headers => api_key_header(params) }
380
+
381
+ body_params.each do |param_name|
382
+ param_name = param_name.to_sym
383
+ next if param_name == :api_key # this is now sent as a header field
384
+ http_options[:body][param_name] = params[param_name] unless params[param_name].nil?
385
+ end
386
+
387
+ http_options
388
+ end
389
+
390
+ def set_api_key_from_response(response)
391
+ self.api_key = response["api_key"] if response && response["api_key"]
392
+ end
393
+
394
+ def api_key_header(params)
395
+ self.api_key ? { API_KEY_HEADER => self.api_key } : {}
396
+ end
397
+
398
+ # Submit the request to the server and process the response (for errors)
399
+ def submit_request(http_verb, request, params)
400
+
401
+ if @debug
402
+ puts "DEBUG: "
403
+ puts "DEBUG: hostname: #{@hostname}"
404
+ puts "DEBUG: output: #{@output}"
405
+ puts "DEBUG: request: #{http_verb.upcase} #{request}"
406
+ puts "DEBUG: params: #{params}"
407
+ end
408
+
409
+ case http_verb
410
+ when "put"
411
+ response = HTTParty.put(request, params)
412
+ when "delete"
413
+ response = HTTParty.delete(request, params)
414
+ when "post"
415
+ response = HTTParty.post(request, params)
416
+ else
417
+ response = HTTParty.get(request, :query => params[:body],
418
+ :headers => params[:headers])
419
+ end
420
+
421
+ if @debug
422
+ puts "DEBUG: response: #{response}"
423
+ puts "DEBUG: "
424
+ end
425
+
426
+ response.to_hash
427
+ end
428
+
429
+ end # class Touchpass::Client
430
+
431
+ end # module Touchpass
@@ -0,0 +1,51 @@
1
+
2
+ module Touchpass
3
+ class Crypt
4
+ # Encryption method
5
+ # Options: :md5, :sha1, :sha256, :sha512
6
+ CRYPTO_PROVIDER = :sha256
7
+
8
+ def self.salt(length=10)
9
+ seeds = ('a'..'z').to_a
10
+ seeds.concat( ('A'..'Z').to_a )
11
+ seeds.concat( (0..9).to_a )
12
+ seeds.concat ['/', '.']
13
+ seeds.compact!
14
+
15
+ salt_string = '$1$'
16
+ length.times { salt_string << seeds[ rand(seeds.size) ].to_s }
17
+
18
+ salt_string
19
+ end
20
+
21
+ def self.encrypt(string)
22
+ crypto_provider = nil
23
+ case CRYPTO_PROVIDER
24
+ when :md5
25
+ require 'digest/md5' unless defined?(Digest::MD5)
26
+ crypto_provider = Digest::MD5
27
+ when :sha1
28
+ require 'digest/sha1' unless defined?(Digest::SHA1)
29
+ crypto_provider = Digest::SHA1
30
+ when :sha256
31
+ require 'digest/sha2' unless defined?(Digest::SHA2)
32
+ crypto_provider = Digest::SHA256
33
+ when :sha512
34
+ require 'digest/sha2' unless defined?(Digest::SHA2)
35
+ crypto_provider = Digest::SHA512
36
+ end
37
+ return crypto_provider.hexdigest(string)
38
+ end
39
+
40
+ def self.read_key(pem)
41
+ begin
42
+ return OpenSSL::PKey::RSA.new(pem)
43
+ rescue
44
+ puts "Unable to read key."
45
+ end
46
+ return nil
47
+ end
48
+ end
49
+
50
+ end
51
+
@@ -0,0 +1,18 @@
1
+ module Touchpass
2
+ module Rp
3
+ class Device
4
+ include HTTParty
5
+ #debug_output
6
+
7
+ def initialize
8
+ self.class.base_uri Touchpass.base_uri
9
+ end
10
+
11
+ def get_all(user)
12
+ http_options = { :body => {}, :headers => { Touchpass::Client::API_KEY_HEADER => Touchpass.api_key } }
13
+ response = self.class.get("/#{user}/devices.json", http_options)
14
+ return response.parsed_response["devices"]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,56 @@
1
+ module Touchpass
2
+
3
+ # Used to help with creation of public / private key files for a device (by clients)
4
+ class KeyFileCreator
5
+
6
+ DEFAULT_KEYS_PATH = File.join(ENV['HOME'], ".touchpass", "certs")
7
+ PRIVATE_KEY = "private.pem"
8
+ PUBLIC_KEY = "public.pem"
9
+
10
+ class << self
11
+ attr_accessor :keys_path
12
+ end
13
+
14
+ attr_accessor :public_key, :private_key, :id
15
+
16
+ def initialize(id)
17
+ @id = id.to_s
18
+ end
19
+
20
+ # Use openssl command to generate key pair.
21
+ # RSA public key PEM generated by Ruby’s OpenSSL::PKey::RSA are unreadable by OpenSSL.
22
+ # See: http://barelyenough.org/blog/2008/04/fun-with-public-keys/
23
+ def generate_keys
24
+ directory = keys_path
25
+ prepare_directories directory
26
+ `openssl genrsa -out #{directory}/#{PRIVATE_KEY} 1024 > /dev/null 2>&1`
27
+ `openssl rsa -in #{directory}/#{PRIVATE_KEY} -out #{directory}/#{PUBLIC_KEY} -outform PEM -pubout > /dev/null 2>&1`
28
+ end
29
+
30
+ def private_key
31
+ pem_file = File.join(keys_path, PRIVATE_KEY)
32
+ return self.read_key(pem_file)
33
+ end
34
+
35
+ def public_key
36
+ pem_file = File.join(keys_path, PUBLIC_KEY)
37
+ return self.read_key(pem_file)
38
+ end
39
+
40
+ def read_key(pem_file)
41
+ return OpenSSL::PKey::RSA.new(File.read(pem_file))
42
+ end
43
+
44
+ def keys_path
45
+ File.join(self.class.keys_path, @id)
46
+ end
47
+
48
+ private
49
+ # Create target directory
50
+ def prepare_directories(dir)
51
+ FileUtils.makedirs dir
52
+ end
53
+ end
54
+ end
55
+
56
+ Touchpass::KeyFileCreator.keys_path = Touchpass::KeyFileCreator::DEFAULT_KEYS_PATH