touchpass 0.0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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