two_factor_auth 0.1.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.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +634 -0
  3. data/Rakefile +34 -0
  4. data/app/assets/javascripts/two_factor_auth/application.js +13 -0
  5. data/app/assets/stylesheets/two_factor_auth/application.css +15 -0
  6. data/app/controllers/two_factor_auth/authentications_controller.rb +32 -0
  7. data/app/controllers/two_factor_auth/registrations_controller.rb +29 -0
  8. data/app/controllers/two_factor_auth/trusted_facets_controller.rb +10 -0
  9. data/app/controllers/two_factor_auth/two_factor_auth_controller.rb +19 -0
  10. data/app/helpers/two_factor_auth/application_helper.rb +21 -0
  11. data/app/helpers/two_factor_auth/authentications_helper.rb +17 -0
  12. data/app/helpers/two_factor_auth/registrations_helper.rb +17 -0
  13. data/app/models/two_factor_auth/authentication_client_data.rb +11 -0
  14. data/app/models/two_factor_auth/authentication_request.rb +30 -0
  15. data/app/models/two_factor_auth/authentication_response.rb +49 -0
  16. data/app/models/two_factor_auth/authentication_verifier.rb +68 -0
  17. data/app/models/two_factor_auth/client_data.rb +57 -0
  18. data/app/models/two_factor_auth/registration.rb +18 -0
  19. data/app/models/two_factor_auth/registration_request.rb +33 -0
  20. data/app/models/two_factor_auth/registration_response.rb +91 -0
  21. data/app/models/two_factor_auth/registration_verifier.rb +91 -0
  22. data/app/views/layouts/two_factor_auth/application.html.erb +16 -0
  23. data/app/views/two_factor_auth/authentications/new.html.erb +30 -0
  24. data/app/views/two_factor_auth/registrations/new.html.erb +26 -0
  25. data/config/routes.rb +3 -0
  26. data/lib/generators/templates/README +6 -0
  27. data/lib/generators/templates/initializer.rb +38 -0
  28. data/lib/generators/templates/migration.rb +15 -0
  29. data/lib/generators/two_factor_auth/install_generator.rb +32 -0
  30. data/lib/tasks/two_factor_auth_tasks.rake +13 -0
  31. data/lib/two_factor_auth/authentication_hook.rb +18 -0
  32. data/lib/two_factor_auth/engine.rb +5 -0
  33. data/lib/two_factor_auth/registration_hook.rb +17 -0
  34. data/lib/two_factor_auth/version.rb +3 -0
  35. data/lib/two_factor_auth.rb +155 -0
  36. data/test/controllers/two_factor_auth/authentications_controller_test.rb +70 -0
  37. data/test/controllers/two_factor_auth/registrations_controller_test.rb +57 -0
  38. data/test/controllers/two_factor_auth/trusted_facets_controller_test.rb +17 -0
  39. data/test/dummy/README.rdoc +28 -0
  40. data/test/dummy/Rakefile +6 -0
  41. data/test/dummy/app/assets/javascripts/application.js +13 -0
  42. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  43. data/test/dummy/app/controllers/application_controller.rb +5 -0
  44. data/test/dummy/app/controllers/secrets_controller.rb +3 -0
  45. data/test/dummy/app/helpers/application_helper.rb +2 -0
  46. data/test/dummy/app/models/user.rb +8 -0
  47. data/test/dummy/app/views/layouts/application.html.erb +16 -0
  48. data/test/dummy/app/views/secrets/index.html.erb +10 -0
  49. data/test/dummy/bin/bundle +3 -0
  50. data/test/dummy/bin/rails +4 -0
  51. data/test/dummy/bin/rake +4 -0
  52. data/test/dummy/config/application.rb +24 -0
  53. data/test/dummy/config/boot.rb +5 -0
  54. data/test/dummy/config/database.yml +25 -0
  55. data/test/dummy/config/environment.rb +5 -0
  56. data/test/dummy/config/environments/development.rb +37 -0
  57. data/test/dummy/config/environments/production.rb +78 -0
  58. data/test/dummy/config/environments/test.rb +39 -0
  59. data/test/dummy/config/initializers/assets.rb +8 -0
  60. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  61. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  62. data/test/dummy/config/initializers/devise.rb +259 -0
  63. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  64. data/test/dummy/config/initializers/inflections.rb +16 -0
  65. data/test/dummy/config/initializers/mime_types.rb +4 -0
  66. data/test/dummy/config/initializers/session_store.rb +3 -0
  67. data/test/dummy/config/initializers/two_factor_auth.rb +38 -0
  68. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  69. data/test/dummy/config/locales/devise.en.yml +60 -0
  70. data/test/dummy/config/locales/en.yml +23 -0
  71. data/test/dummy/config/routes.rb +5 -0
  72. data/test/dummy/config/secrets.yml +22 -0
  73. data/test/dummy/config.ru +4 -0
  74. data/test/dummy/db/development.sqlite3 +0 -0
  75. data/test/dummy/db/migrate/20141026231953_devise_create_users.rb +42 -0
  76. data/test/dummy/db/migrate/20141224135949_create_two_factor_auth_registrations.rb +15 -0
  77. data/test/dummy/db/schema.rb +50 -0
  78. data/test/dummy/db/test.sqlite3 +0 -0
  79. data/test/dummy/log/development.log +198 -0
  80. data/test/dummy/log/test.log +3490 -0
  81. data/test/dummy/public/404.html +67 -0
  82. data/test/dummy/public/422.html +67 -0
  83. data/test/dummy/public/500.html +66 -0
  84. data/test/dummy/public/favicon.ico +0 -0
  85. data/test/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  86. data/test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  87. data/test/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  88. data/test/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  89. data/test/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  90. data/test/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  91. data/test/helpers/two_factor_auth/authentication_helper_test.rb +54 -0
  92. data/test/helpers/two_factor_auth/registrations_helper_test.rb +34 -0
  93. data/test/integration/navigation_test.rb +10 -0
  94. data/test/lib/two_factor_auth_test.rb +169 -0
  95. data/test/models/two_factor_auth/authentication_request_test.rb +35 -0
  96. data/test/models/two_factor_auth/authentication_response_test.rb +44 -0
  97. data/test/models/two_factor_auth/authentication_verifier_test.rb +83 -0
  98. data/test/models/two_factor_auth/client_data_test.rb +79 -0
  99. data/test/models/two_factor_auth/registration_request_test.rb +29 -0
  100. data/test/models/two_factor_auth/registration_response_test.rb +87 -0
  101. data/test/models/two_factor_auth/registration_verifier_test.rb +96 -0
  102. data/test/test_helper.rb +43 -0
  103. metadata +351 -0
@@ -0,0 +1,79 @@
1
+ require 'test_helper'
2
+
3
+ module TwoFactorAuth
4
+ describe ClientData do
5
+ #parallelize_me!
6
+
7
+ let(:clientData) { "eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCIsImNoYWxsZW5nZSI6IjVmeGF6cWFuUkh0ZDdBdEVIQkd4eERwU2o2bWRCRjI2WEY0eGRBOW03SnciLCJvcmlnaW4iOiJodHRwOi8vbG9jYWwuZmlkb2xvZ2luLmNvbTozMDAwIiwiY2lkX3B1YmtleSI6IiJ9" }
8
+ let(:client_data) { ClientData.new(encoded: clientData, correct_typ: 'navigator.id.finishEnrollment') }
9
+
10
+ describe "decomposing attrs" do
11
+ it "extracts the typ" do
12
+ client_data.typ.must_equal "navigator.id.finishEnrollment"
13
+ end
14
+
15
+ it "extracts the challenge" do
16
+ client_data.challenge.must_equal "5fxazqanRHtd7AtEHBGxxDpSj6mdBF26XF4xdA9m7Jw"
17
+ end
18
+
19
+ it "extracts the origin" do
20
+ client_data.origin.must_equal "http://local.fidologin.com:3000"
21
+ end
22
+ end
23
+
24
+ describe "validations" do
25
+ it "is valid with all fields valid" do
26
+ client_data.valid?.must_equal true
27
+ end
28
+
29
+ # I can't see a nice way to break these checks out of decompose_attrs and,
30
+ # because ActiveModel is all about mutability, it flushes errors when
31
+ # .valid? is called.
32
+ #it "is not if encoding is invalid" do
33
+ # cd = ClientData.new(encoded: "bad encoded data", correct_typ: 'navigator.id.finishEnrollment')
34
+ # cd.valid?.must_equal false
35
+ # cd.errors[:encoded].wont_be_empty
36
+ #end
37
+ #
38
+ #it "is not if json is invalid" do
39
+ # cd = ClientData.new(encoded: TwoFactorAuth.websafe_base64_encode("{blah"), correct_typ: 'navigator.id.finishEnrollment')
40
+ # cd.valid?.must_equal false
41
+ # cd.errors[:json].wont_be_empty
42
+ #end
43
+
44
+ it "is not if a key is added" do
45
+ client_data.attrs[:extra] = "pair"
46
+ client_data.valid?.must_equal false
47
+ client_data.errors[:attrs].wont_be_empty
48
+ end
49
+
50
+ it "is not if a key is missing" do
51
+ client_data.attrs.delete('origin')
52
+ client_data.valid?.must_equal false
53
+ client_data.errors[:attrs].wont_be_empty
54
+ end
55
+
56
+ it "accepts two possible values for correct_typ" do
57
+ cd = ClientData.new(encoded: clientData, correct_typ: 'navigator.id.finishEnrollment')
58
+ cd.valid?
59
+ cd.errors[:correct_typ].must_be_empty
60
+ cd = ClientData.new(encoded: clientData, correct_typ: 'navigator.id.getAssertion')
61
+ cd.valid?
62
+ cd.errors[:correct_typ].must_be_empty
63
+ end
64
+
65
+ it "raises on construction without a correct_typ" do
66
+ Proc.new {
67
+ ClientData.new(encoded: clientData)
68
+ }.must_raise ArgumentError
69
+ end
70
+
71
+ it "is not if typ is incorrect" do
72
+ client_data.typ = "invalid typ"
73
+ client_data.valid?.must_equal false
74
+ client_data.errors[:typ].wont_be_empty
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,29 @@
1
+ require 'test_helper'
2
+
3
+ module TwoFactorAuth
4
+ describe RegistrationRequest do
5
+ let(:app_id) { 'http://twofactorauth.example.com/' }
6
+
7
+ it "holds app id" do
8
+ rr = RegistrationRequest.new('http://id.example.com/')
9
+ rr.app_id.must_equal 'http://id.example.com/'
10
+ end
11
+
12
+ it "creates random challenges" do
13
+ rr1 = RegistrationRequest.new(app_id)
14
+ rr2 = RegistrationRequest.new(app_id)
15
+ rr1.challenge.wont_equal rr2.challenge
16
+ end
17
+
18
+ it "uses the challenge given" do
19
+ encoded_challenge = TwoFactorAuth.websafe_base64_encode('0' * 32)
20
+ rr = RegistrationRequest.new(app_id, [], encoded_challenge)
21
+ rr.challenge.must_equal encoded_challenge
22
+ end
23
+
24
+ it "does not return Signs without KeyHandles" do
25
+ rr = RegistrationRequest.new(app_id)
26
+ rr.signs.must_equal []
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,87 @@
1
+ require 'test_helper'
2
+
3
+ module TwoFactorAuth
4
+ describe RegistrationResponse do
5
+ #parallelize_me!
6
+
7
+ let(:registrationData) { "BQQqdFC3zhANYW9DmErAjFQYZjBExK22PLx-ViMOch04-wZ990aqOcF2gxS5gzSUDKzpPGXpliMk3UoXgYlC2QNuQGbQ4E5v_UrLCzT58SXg902p9JXmLboTF42QkuZXIbdea_97h96lVovJ7xrA-iWTrZiOSRVZoBZsTrCW64XMrUcwggIcMIIBBqADAgECAgQk26tAMAsGCSqGSIb3DQEBCzAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKzEpMCcGA1UEAwwgWXViaWNvIFUyRiBFRSBTZXJpYWwgMTM1MDMyNzc4ODgwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQCsJS-NH1HeUHEd46-xcpN7SpHn6oeb-w5r-veDCBwy1vUvWnJanjjv4dR_rV5G436ysKUAXUcsVe5fAnkORo2oxIwEDAOBgorBgEEAYLECgEBBAAwCwYJKoZIhvcNAQELA4IBAQCjY64OmDrzC7rxLIst81pZvxy7ShsPy2jEhFWEkPaHNFhluNsCacNG5VOITCxWB68OonuQrIzx70MfcqwYnbIcgkkUvxeIpVEaM9B7TI40ZHzp9h4VFqmps26QCkAgYfaapG4SxTK5k_lCPvqqTPmjtlS03d7ykkpUj9WZlVEN1Pf02aTVIZOHPHHJuH6GhT6eLadejwxtKDBTdNTv3V4UlvjDOQYQe9aL1jUNqtLDeBHso8pDvJMLc0CX3vadaI2UVQxM-xip4kuGouXYj0mYmaCbzluBDFNsrzkNyL3elg3zMMrKvAUhoYMjlX_-vKWcqQsgsQ0JtSMcWMJ-umeDMEUCIQCPkI4L_gHM88JrqJj_ZNRghQyC0gJyCC9RBrnfI2mDTwIgPOuEiD1AOfRaGO_EaHi-z4XyIGDhkG8-BYH-syVY5_o" }
8
+ let(:response) { RegistrationResponse.new(encoded: registrationData) }
9
+
10
+ describe "decomposing fields" do
11
+
12
+ it "extracts reserved byte" do
13
+ response.reserved_byte.must_equal [5].pack('C*')
14
+ response.reserved_byte.length.must_equal 1
15
+ end
16
+
17
+ it "extracts user's public key" do
18
+ response.public_key.must_equal "\x04*tP\xB7\xCE\x10\raoC\x98J\xC0\x8CT\x18f0D\xC4\xAD\xB6<\xBC~V#\x0Er\x1D8\xFB\x06}\xF7F\xAA9\xC1v\x83\x14\xB9\x834\x94\f\xAC\xE9<e\xE9\x96\#$\xDDJ\x17\x81\x89B\xD9\x03n".force_encoding('ASCII-8BIT')
19
+ response.public_key.length.must_equal 65
20
+ end
21
+
22
+ it "extracts key handle" do
23
+ response.key_handle.must_equal "f\xD0\xE0No\xFDJ\xCB\v4\xF9\xF1%\xE0\xF7M\xA9\xF4\x95\xE6-\xBA\x13\x17\x8D\x90\x92\xE6W!\xB7^k\xFF{\x87\xDE\xA5V\x8B\xC9\xEF\x1A\xC0\xFA%\x93\xAD\x98\x8EI\x15Y\xA0\x16lN\xB0\x96\xEB\x85\xCC\xADG".force_encoding('ASCII-8BIT')
24
+ response.key_handle.length.must_equal 64
25
+ end
26
+
27
+ it "extracts attestation certificate" do
28
+ response.certificate.must_equal "0\x82\x02\x1C0\x82\x01\x06\xA0\x03\x02\x01\x02\x02\x04$\xDB\xAB@0\v\x06\t*\x86H\x86\xF7\r\x01\x01\v0.1,0*\x06\x03U\x04\x03\x13#Yubico U2F Root CA Serial 4572006310 \x17\r140801000000Z\x18\x0F20500904000000Z0+1)0'\x06\x03U\x04\x03\f Yubico U2F EE Serial 135032778880Y0\x13\x06\a*\x86H\xCE=\x02\x01\x06\b*\x86H\xCE=\x03\x01\a\x03B\x00\x04\x02\xB0\x94\xBE4}GyA\xC4w\x8E\xBE\xC5\xCAM\xED*G\x9F\xAA\x1Eo\xEC9\xAF\xEB\xDE\f p\xCB[\xD4\xBDi\xC9jx\xE3\xBF\x87Q\xFE\xB5y\e\x8D\xFA\xCA\xC2\x94\x01u\x1C\xB1W\xB9|\t\xE49\x1A6\xA3\x120\x100\x0E\x06\n+\x06\x01\x04\x01\x82\xC4\n\x01\x01\x04\x000\v\x06\t*\x86H\x86\xF7\r\x01\x01\v\x03\x82\x01\x01\x00\xA3c\xAE\x0E\x98:\xF3\v\xBA\xF1,\x8B-\xF3ZY\xBF\x1C\xBBJ\e\x0F\xCBh\xC4\x84U\x84\x90\xF6\x874Xe\xB8\xDB\x02i\xC3F\xE5S\x88L,V\a\xAF\x0E\xA2{\x90\xAC\x8C\xF1\xEFC\x1Fr\xAC\x18\x9D\xB2\x1C\x82I\x14\xBF\x17\x88\xA5Q\x1A3\xD0{L\x8E4d|\xE9\xF6\x1E\x15\x16\xA9\xA9\xB3n\x90\n@ a\xF6\x9A\xA4n\x12\xC52\xB9\x93\xF9B>\xFA\xAAL\xF9\xA3\xB6T\xB4\xDD\xDE\xF2\x92JT\x8F\xD5\x99\x95Q\r\xD4\xF7\xF4\xD9\xA4\xD5!\x93\x87<q\xC9\xB8~\x86\x85>\x9E-\xA7^\x8F\fm(0St\xD4\xEF\xDD^\x14\x96\xF8\xC39\x06\x10{\xD6\x8B\xD65\r\xAA\xD2\xC3x\x11\xEC\xA3\xCAC\xBC\x93\vs@\x97\xDE\xF6\x9Dh\x8D\x94U\fL\xFB\x18\xA9\xE2K\x86\xA2\xE5\xD8\x8FI\x98\x99\xA0\x9B\xCE[\x81\fSl\xAF9\r\xC8\xBD\xDE\x96\r\xF30\xCA\xCA\xBC\x05!\xA1\x83#\x95\x7F\xFE\xBC\xA5\x9C\xA9\v \xB1\r\t\xB5#\x1CX\xC2~\xBAg\x83".force_encoding('ASCII-8BIT')
29
+ response.certificate.length.must_equal 544
30
+ end
31
+
32
+ it "extracts signature" do
33
+ response.signature.must_equal "0E\x02!\x00\x8F\x90\x8E\v\xFE\x01\xCC\xF3\xC2k\xA8\x98\xFFd\xD4`\x85\f\x82\xD2\x02r\b/Q\x06\xB9\xDF#i\x83O\x02 <\xEB\x84\x88=@9\xF4Z\x18\xEF\xC4hx\xBE\xCF\x85\xF2 `\xE1\x90o>\x05\x81\xFE\xB3%X\xE7\xFA".force_encoding('ASCII-8BIT')
34
+ response.signature.length.must_equal 71
35
+ end
36
+
37
+ it "starts with registrationData a known size" do
38
+ sum = TwoFactorAuth.websafe_base64_decode(registrationData).length
39
+ sum.must_equal 746
40
+ end
41
+
42
+ it "extracts all data to fields" do
43
+ # length of fields + byte for key handle length = raw
44
+ sum = TwoFactorAuth.websafe_base64_decode(registrationData).length
45
+ (
46
+ response.reserved_byte.length +
47
+ response.public_key.length +
48
+ 1 +
49
+ response.key_handle.length +
50
+ response.certificate.length +
51
+ response.signature.length
52
+ ).must_equal sum
53
+ end
54
+ end
55
+
56
+ describe "#certificate_public_key" do
57
+ it "extracts the certificate's public key" do
58
+ response.certificate_public_key.to_bn.to_i.must_equal 53772106386491294167326085516082733333742865191976757260691005884697072590710730429546268855611153149813381544656650324644681905213198659862106126975310390
59
+ end
60
+ end
61
+
62
+ describe "validations" do
63
+ it "is valid with all fields valid" do
64
+ response.valid?.must_equal true
65
+ end
66
+
67
+ it "is not if reserved byte is incorrect" do
68
+ response.reserved_byte = 1.chr
69
+ response.valid?.must_equal false
70
+ response.errors[:reserved_byte].wont_be_empty
71
+ end
72
+
73
+ it "is not if vertificate is not valid" do
74
+ response.certificate = "not actually a cert"
75
+ response.valid?.must_equal false
76
+ response.errors[:certificate].wont_be_empty
77
+ end
78
+
79
+ it "is not if public key is not valid" do
80
+ response.public_key = "invalid pk"
81
+ response.valid?.must_equal false
82
+ response.errors[:public_key].wont_be_empty
83
+ end
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,96 @@
1
+ require 'test_helper'
2
+
3
+ module TwoFactorAuth
4
+ describe RegistrationVerifier do
5
+ #parallelize_me!
6
+
7
+ let(:app_id) { "http://local.fidologin.com:3000" }
8
+ let(:request) { RegistrationRequest.new(app_id, [], "5fxazqanRHtd7AtEHBGxxDpSj6mdBF26XF4xdA9m7Jw") }
9
+ let(:client_data) { ClientData.new encoded: "eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCIsImNoYWxsZW5nZSI6IjVmeGF6cWFuUkh0ZDdBdEVIQkd4eERwU2o2bWRCRjI2WEY0eGRBOW03SnciLCJvcmlnaW4iOiJodHRwOi8vbG9jYWwuZmlkb2xvZ2luLmNvbTozMDAwIiwiY2lkX3B1YmtleSI6IiJ9", correct_typ: 'navigator.id.finishEnrollment' }
10
+ let(:response) { RegistrationResponse.new encoded: "BQQqdFC3zhANYW9DmErAjFQYZjBExK22PLx-ViMOch04-wZ990aqOcF2gxS5gzSUDKzpPGXpliMk3UoXgYlC2QNuQGbQ4E5v_UrLCzT58SXg902p9JXmLboTF42QkuZXIbdea_97h96lVovJ7xrA-iWTrZiOSRVZoBZsTrCW64XMrUcwggIcMIIBBqADAgECAgQk26tAMAsGCSqGSIb3DQEBCzAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKzEpMCcGA1UEAwwgWXViaWNvIFUyRiBFRSBTZXJpYWwgMTM1MDMyNzc4ODgwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQCsJS-NH1HeUHEd46-xcpN7SpHn6oeb-w5r-veDCBwy1vUvWnJanjjv4dR_rV5G436ysKUAXUcsVe5fAnkORo2oxIwEDAOBgorBgEEAYLECgEBBAAwCwYJKoZIhvcNAQELA4IBAQCjY64OmDrzC7rxLIst81pZvxy7ShsPy2jEhFWEkPaHNFhluNsCacNG5VOITCxWB68OonuQrIzx70MfcqwYnbIcgkkUvxeIpVEaM9B7TI40ZHzp9h4VFqmps26QCkAgYfaapG4SxTK5k_lCPvqqTPmjtlS03d7ykkpUj9WZlVEN1Pf02aTVIZOHPHHJuH6GhT6eLadejwxtKDBTdNTv3V4UlvjDOQYQe9aL1jUNqtLDeBHso8pDvJMLc0CX3vadaI2UVQxM-xip4kuGouXYj0mYmaCbzluBDFNsrzkNyL3elg3zMMrKvAUhoYMjlX_-vKWcqQsgsQ0JtSMcWMJ-umeDMEUCIQCPkI4L_gHM88JrqJj_ZNRghQyC0gJyCC9RBrnfI2mDTwIgPOuEiD1AOfRaGO_EaHi-z4XyIGDhkG8-BYH-syVY5_o" }
11
+ let(:verifier) { RegistrationVerifier.new({
12
+ login: 'login placeholder',
13
+ request: request,
14
+ client_data: client_data,
15
+ response: response,
16
+ }) }
17
+
18
+ describe '#application_parameter' do
19
+ it "is the sha256 hash of the app id" do
20
+ verifier.application_parameter.must_equal "Nh\xA3j\x7F\xB2\xEE\xD6D\x1F\x18\t\xF1\xAEL\t\x7F\fjo\n\x80\xDE\xAD{\x9E\x13\x04\xAE\xFD.\xD6".force_encoding('ASCII-8bit')
21
+ end
22
+ end
23
+
24
+ describe '#challenge_parameter' do
25
+ it "is the sha256 hash of the base64 decoded json string" do
26
+ verifier.challenge_parameter.must_equal "\x05\xB4\xF4\x06\xDA\xF3So\xD9\x96\xB4XCSw\x93\xBFq\xDDS\xEE\x13NT\xB1\xFD\x88m\xF7\xCF\xB8|".force_encoding('ASCII-8bit')
27
+ end
28
+ end
29
+
30
+ describe '#digest' do
31
+ it "combines fields to spec" do
32
+ verifier.digest.must_equal "\xFD\xED*TyGh\b\xD9\x01\x9A\x01ih\xF8/\x00\xA8\x98\x06\xD7~\x00s\x0F\x82h\xAA=\xD1\xAF\xA0".force_encoding('ASCII-8BIT')
33
+ end
34
+ end
35
+
36
+ describe 'validation' do
37
+ it 'is if response matches and signs request' do
38
+ verifier.valid?.must_equal true
39
+ end
40
+
41
+ it 'is not if client_data is invalid' do
42
+ verifier.client_data = ClientData.new encoded: "foo", correct_typ: 'navigator.id.finishEnrollment'
43
+ verifier.valid?.must_equal false
44
+ verifier.errors[:client_data].wont_be_empty
45
+ end
46
+
47
+ it 'is not if response is invalid' do
48
+ verifier.response = RegistrationResponse.new encoded: "foo"
49
+ verifier.valid?.must_equal false
50
+ verifier.errors[:response].wont_be_empty
51
+ end
52
+
53
+ it 'is not if client challenge is different' do
54
+ verifier.client_data.challenge = "wrong challenge"
55
+ verifier.valid?.must_equal false
56
+ verifier.errors[:client_data].wont_be_empty
57
+ end
58
+
59
+ it 'is not if client origin is different' do
60
+ verifier.client_data.origin = "http://wrong.origin"
61
+ verifier.valid?.must_equal false
62
+ verifier.errors[:client_data].wont_be_empty
63
+ end
64
+
65
+ it 'is not if signature is invalid' do
66
+ verifier.response.signature = "bad sig"
67
+ verifier.valid?.must_equal false
68
+ verifier.errors[:response].wont_be_empty
69
+ end
70
+ end
71
+
72
+ describe "#registration_attributes returns attributes worth persisting for registration" do
73
+ let(:attrs) { verifier.registration_attributes }
74
+
75
+ it "has login" do
76
+ attrs[:login].must_equal "login placeholder"
77
+ end
78
+
79
+ it "starts counter at zero" do
80
+ attrs[:counter].must_equal 0
81
+ end
82
+
83
+ it "has key handle" do
84
+ attrs[:key_handle].must_equal "f\xD0\xE0No\xFDJ\xCB\v4\xF9\xF1%\xE0\xF7M\xA9\xF4\x95\xE6-\xBA\x13\x17\x8D\x90\x92\xE6W!\xB7^k\xFF{\x87\xDE\xA5V\x8B\xC9\xEF\x1A\xC0\xFA%\x93\xAD\x98\x8EI\x15Y\xA0\x16lN\xB0\x96\xEB\x85\xCC\xADG".force_encoding('ASCII-8BIT')
85
+ end
86
+
87
+ it "has user's public key" do
88
+ attrs[:public_key].must_equal "\x04*tP\xB7\xCE\x10\raoC\x98J\xC0\x8CT\x18f0D\xC4\xAD\xB6<\xBC~V#\x0Er\x1D8\xFB\x06}\xF7F\xAA9\xC1v\x83\x14\xB9\x834\x94\f\xAC\xE9<e\xE9\x96\#$\xDDJ\x17\x81\x89B\xD9\x03n".force_encoding('ASCII-8BIT')
89
+ end
90
+
91
+ it "has attestation certificate" do
92
+ attrs[:certificate].must_equal "0\x82\x02\x1C0\x82\x01\x06\xA0\x03\x02\x01\x02\x02\x04$\xDB\xAB@0\v\x06\t*\x86H\x86\xF7\r\x01\x01\v0.1,0*\x06\x03U\x04\x03\x13#Yubico U2F Root CA Serial 4572006310 \x17\r140801000000Z\x18\x0F20500904000000Z0+1)0'\x06\x03U\x04\x03\f Yubico U2F EE Serial 135032778880Y0\x13\x06\a*\x86H\xCE=\x02\x01\x06\b*\x86H\xCE=\x03\x01\a\x03B\x00\x04\x02\xB0\x94\xBE4}GyA\xC4w\x8E\xBE\xC5\xCAM\xED*G\x9F\xAA\x1Eo\xEC9\xAF\xEB\xDE\f p\xCB[\xD4\xBDi\xC9jx\xE3\xBF\x87Q\xFE\xB5y\e\x8D\xFA\xCA\xC2\x94\x01u\x1C\xB1W\xB9|\t\xE49\x1A6\xA3\x120\x100\x0E\x06\n+\x06\x01\x04\x01\x82\xC4\n\x01\x01\x04\x000\v\x06\t*\x86H\x86\xF7\r\x01\x01\v\x03\x82\x01\x01\x00\xA3c\xAE\x0E\x98:\xF3\v\xBA\xF1,\x8B-\xF3ZY\xBF\x1C\xBBJ\e\x0F\xCBh\xC4\x84U\x84\x90\xF6\x874Xe\xB8\xDB\x02i\xC3F\xE5S\x88L,V\a\xAF\x0E\xA2{\x90\xAC\x8C\xF1\xEFC\x1Fr\xAC\x18\x9D\xB2\x1C\x82I\x14\xBF\x17\x88\xA5Q\x1A3\xD0{L\x8E4d|\xE9\xF6\x1E\x15\x16\xA9\xA9\xB3n\x90\n@ a\xF6\x9A\xA4n\x12\xC52\xB9\x93\xF9B>\xFA\xAAL\xF9\xA3\xB6T\xB4\xDD\xDE\xF2\x92JT\x8F\xD5\x99\x95Q\r\xD4\xF7\xF4\xD9\xA4\xD5!\x93\x87<q\xC9\xB8~\x86\x85>\x9E-\xA7^\x8F\fm(0St\xD4\xEF\xDD^\x14\x96\xF8\xC39\x06\x10{\xD6\x8B\xD65\r\xAA\xD2\xC3x\x11\xEC\xA3\xCAC\xBC\x93\vs@\x97\xDE\xF6\x9Dh\x8D\x94U\fL\xFB\x18\xA9\xE2K\x86\xA2\xE5\xD8\x8FI\x98\x99\xA0\x9B\xCE[\x81\fSl\xAF9\r\xC8\xBD\xDE\x96\r\xF30\xCA\xCA\xBC\x05!\xA1\x83#\x95\x7F\xFE\xBC\xA5\x9C\xA9\v \xB1\r\t\xB5#\x1CX\xC2~\xBAg\x83".force_encoding('ASCII-8BIT')
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,43 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path("../dummy/config/environment", __FILE__)
3
+ require "rails/test_help"
4
+ require "minitest/rails"
5
+ require 'devise'
6
+
7
+ Rails.backtrace_cleaner.remove_silencers!
8
+ # Load support files
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
10
+ require_relative 'dummy/app/models/user'
11
+
12
+ class ActiveSupport::TestCase
13
+ ActiveRecord::Migration.check_pending!
14
+
15
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
16
+ #
17
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
18
+ # -- they do not yet inherit this setting
19
+ fixtures :all
20
+ end
21
+
22
+ class ActionController::TestCase
23
+ include Devise::TestHelpers
24
+ include Warden::Test::Helpers
25
+ Warden.test_mode!
26
+
27
+ def teardown
28
+ Warden.test_reset!
29
+ end
30
+
31
+ def register_as user, registration
32
+ sign_in user
33
+ assert_equal registration.login, user
34
+ end
35
+
36
+ def authenticate_as user, registration
37
+ sign_in user
38
+ assert_equal registration.login, user
39
+ controller.user_session['two_factor_auth_authenticated'] = Time.now
40
+ end
41
+
42
+ attr_reader :controller
43
+ end