u2f 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 60022b213b355bdb9a7c8e30d115cd460990d5bb
4
- data.tar.gz: c59e0bce1c57789b4fe71f12bfd74576382e78ce
3
+ metadata.gz: 4181871c3a8f8591e810fca9f378c917d3c1817f
4
+ data.tar.gz: 537078ef16ddaf5392b56e5e7c072f6d908a8e71
5
5
  SHA512:
6
- metadata.gz: 02a8053ff4803e1d9f8ebb0c4fd7b9ea36beebcad129d6b4842ca7b4db13a1f4b5611418b41c4571351f470c5641fc1a0200cd40c0d1cd2d9f10c168a50180c4
7
- data.tar.gz: 537e239d9577a020d3a796dbf9840aa243b0432274fe29ed40e02b2ece1c75384dadd18ee26417d7153126e40dc903c9f5d7b6e7844696fc0ba40a95a60fc210
6
+ metadata.gz: 1f7a5df9ff90a60b12d979e3e4b23f708dc846b8962083fcc12528c362223f7d4512b972802b79b065d90f1a6b3d120ed7a2c1a7cb4878e43a75d3b6bd017b2e
7
+ data.tar.gz: 9bceaa3d4c0ed8529a69d903731fbbcd0fe616d4d976af5c321c56b8b73337437e6c017bc82dfde83f2c4bcb55035ef44be42918f6028365da808b786e97843a
data/README.md CHANGED
@@ -19,6 +19,8 @@ U2F is an open 2-factor authentication standard that enables keychain devices, m
19
19
 
20
20
  Check out the [example](https://github.com/castle/ruby-u2f/tree/master/example) directory for a fully working Padrino server demonstrating U2F.
21
21
 
22
+ There is another demo application available using the [Cuba](https://github.com/soveran/cuba) framework: [cuba-u2f-demo](https://github.com/badboy/cuba-u2f-demo) and a [blog post explaining the protocol and the implementation](http://fnordig.de/2015/03/06/u2f-demo-application/).
23
+
22
24
  ## Installation
23
25
 
24
26
  Add the `u2f` gem to your `Gemfile`
@@ -84,8 +86,8 @@ Render a form that will be automatically posted when the U2F device reponds.
84
86
 
85
87
  ```javascript
86
88
  // render requests from server into Javascript format
87
- var registerRequests = <%= @registration_requests.to_json.html_safe %>;
88
- var signRequests = <%= @sign_requests.to_json.html_safe %>;
89
+ var registerRequests = <%= @registration_requests.as_json.to_json.html_safe %>;
90
+ var signRequests = <%= @sign_requests.as_json.to_json.html_safe %>;
89
91
 
90
92
  u2f.register(registerRequests, signRequests, function(registerResponse) {
91
93
  var form, reg;
@@ -160,7 +162,7 @@ Render a form that will be automatically posted when the U2F device reponds.
160
162
 
161
163
  ```javascript
162
164
  // render requests from server into Javascript format
163
- var signRequests = <%= @sign_requests.to_json.html_safe %>;
165
+ var signRequests = <%= @sign_requests.as_json.to_json.html_safe %>;
164
166
 
165
167
  u2f.sign(signRequests, function(signResponse) {
166
168
  var form, reg;
data/lib/u2f.rb CHANGED
@@ -11,7 +11,9 @@ require 'u2f/register_response'
11
11
  require 'u2f/registration'
12
12
  require 'u2f/sign_request'
13
13
  require 'u2f/sign_response'
14
+ require 'u2f/fake_u2f'
14
15
  require 'u2f/u2f'
15
16
 
16
17
  module U2F
18
+ DIGEST = OpenSSL::Digest::SHA256
17
19
  end
@@ -3,15 +3,18 @@ module U2F
3
3
  # A representation of ClientData, chapter 7
4
4
  # http://fidoalliance.org/specs/fido-u2f-raw-message-formats-v1.0-rd-20141008.pdf
5
5
  class ClientData
6
+ REGISTRATION_TYP = "navigator.id.finishEnrollment".freeze
7
+ AUTHENTICATION_TYP = "navigator.id.getAssertion".freeze
8
+
6
9
  attr_accessor :typ, :challenge, :origin
7
10
  alias_method :type, :typ
8
11
 
9
12
  def registration?
10
- typ == 'navigator.id.finishEnrollment'
13
+ typ == REGISTRATION_TYP
11
14
  end
12
15
 
13
16
  def authentication?
14
- typ == 'navigator.id.getAssertion'
17
+ typ == AUTHENTICATION_TYP
15
18
  end
16
19
 
17
20
  def self.load_from_json(json)
@@ -0,0 +1,194 @@
1
+ class U2F::FakeU2F
2
+ CURVE_NAME = "prime256v1".freeze
3
+
4
+ attr_accessor :app_id, :counter, :key_handle_raw, :cert_subject
5
+
6
+ # Initialize a new FakeU2F device for use in tests.
7
+ #
8
+ # app_id - The appId/origin this is being tested against.
9
+ # options - A Hash of optional parameters (optional).
10
+ # :counter - The initial counter for this device.
11
+ # :key_handle - The raw key-handle this device should use.
12
+ # :cert_subject - The subject field for the certificate generated
13
+ # for this device.
14
+ #
15
+ # Returns nothing.
16
+ def initialize(app_id, options = {})
17
+ @app_id = app_id
18
+ @counter = options.fetch(:counter, 0)
19
+ @key_handle_raw = options.fetch(:key_handle, SecureRandom.random_bytes(32))
20
+ @cert_subject = options.fetch(:cert_subject, "/CN=U2FTest")
21
+ end
22
+
23
+ # A registerResponse hash as returned by the u2f.register JavaScript API.
24
+ #
25
+ # challenge - The challenge to sign.
26
+ # error - Boolean. Whether to return an error response (optional).
27
+ #
28
+ # Returns a JSON encoded Hash String.
29
+ def register_response(challenge, error = false)
30
+ if error
31
+ JSON.dump(:errorCode => 4)
32
+ else
33
+ client_data_json = client_data(U2F::ClientData::REGISTRATION_TYP, challenge)
34
+ JSON.dump(
35
+ :registrationData => reg_registration_data(client_data_json),
36
+ :clientData => U2F.urlsafe_encode64(client_data_json)
37
+ )
38
+ end
39
+ end
40
+
41
+ # A SignResponse hash as returned by the u2f.sign JavaScript API.
42
+ #
43
+ # challenge - The challenge to sign.
44
+ #
45
+ # Returns a JSON encoded Hash String.
46
+ def sign_response(challenge)
47
+ client_data_json = client_data(U2F::ClientData::AUTHENTICATION_TYP, challenge)
48
+ JSON.dump(
49
+ :clientData => U2F.urlsafe_encode64(client_data_json),
50
+ :keyHandle => U2F.urlsafe_encode64(key_handle_raw),
51
+ :signatureData => auth_signature_data(client_data_json)
52
+ )
53
+ end
54
+
55
+ # The appId specific public key as returned in the registrationData field of
56
+ # a RegisterResponse Hash.
57
+ #
58
+ # Returns a binary formatted EC public key String.
59
+ def origin_public_key_raw
60
+ [origin_key.public_key.to_bn.to_s(16)].pack('H*')
61
+ end
62
+
63
+ # The raw device attestation certificate as returned in the registrationData
64
+ # field of a RegisterResponse Hash.
65
+ #
66
+ # Returns a DER formatted certificate String.
67
+ def cert_raw
68
+ cert.to_der
69
+ end
70
+
71
+ private
72
+
73
+ # The registrationData field returns in a RegisterResponse Hash.
74
+ #
75
+ # client_data_json - The JSON encoded clientData String.
76
+ #
77
+ # Returns a url-safe base64 encoded binary String.
78
+ def reg_registration_data(client_data_json)
79
+ U2F.urlsafe_encode64(
80
+ [
81
+ 5,
82
+ origin_public_key_raw,
83
+ key_handle_raw.bytesize,
84
+ key_handle_raw,
85
+ cert_raw,
86
+ reg_signature(client_data_json)
87
+ ].pack("CA65CA#{key_handle_raw.bytesize}A#{cert_raw.bytesize}A*")
88
+ )
89
+ end
90
+
91
+ # The signature field of a registrationData field of a RegisterResponse.
92
+ #
93
+ # client_data_json - The JSON encoded clientData String.
94
+ #
95
+ # Returns an ECDSA signature String.
96
+ def reg_signature(client_data_json)
97
+ payload = [
98
+ "\x00",
99
+ U2F::DIGEST.digest(app_id),
100
+ U2F::DIGEST.digest(client_data_json),
101
+ key_handle_raw,
102
+ origin_public_key_raw
103
+ ].join
104
+ cert_key.sign(U2F::DIGEST.new, payload)
105
+ end
106
+
107
+ # The signatureData field of a SignResponse Hash.
108
+ #
109
+ # client_data_json - The JSON encoded clientData String.
110
+ #
111
+ # Returns a url-safe base64 encoded binary String.
112
+ def auth_signature_data(client_data_json)
113
+ ::U2F.urlsafe_encode64(
114
+ [
115
+ 1, # User present
116
+ self.counter += 1,
117
+ auth_signature(client_data_json)
118
+ ].pack("CNA*")
119
+ )
120
+ end
121
+
122
+ # The signature field of a signatureData field of a SignResponse Hash.
123
+ #
124
+ # client_data_json - The JSON encoded clientData String.
125
+ #
126
+ # Returns an ECDSA signature String.
127
+ def auth_signature(client_data_json)
128
+ data = [
129
+ U2F::DIGEST.digest(app_id),
130
+ 1, # User present
131
+ counter,
132
+ U2F::DIGEST.digest(client_data_json)
133
+ ].pack("A32CNA32")
134
+
135
+ origin_key.sign(U2F::DIGEST.new, data)
136
+ end
137
+
138
+ # The clientData hash as returned by registration and authentication
139
+ # responses.
140
+ #
141
+ # typ - The String value for the 'typ' field.
142
+ # challenge - The String url-safe base64 encoded challenge parameter.
143
+ #
144
+ # Returns a JSON encoded Hash String.
145
+ def client_data(typ, challenge)
146
+ JSON.dump(
147
+ :challenge => challenge,
148
+ :origin => app_id,
149
+ :typ => typ
150
+ )
151
+ end
152
+
153
+ # The appId-specific public/private key.
154
+ #
155
+ # Returns a OpenSSL::PKey::EC instance.
156
+ def origin_key
157
+ @origin_key ||= generate_ec_key
158
+ end
159
+
160
+ # The self-signed device attestation certificate.
161
+ #
162
+ # Returns a OpenSSL::X509::Certificate instance.
163
+ def cert
164
+ @cert ||= OpenSSL::X509::Certificate.new.tap do |c|
165
+ c.subject = c.issuer = OpenSSL::X509::Name.parse(cert_subject)
166
+ c.not_before = Time.now
167
+ c.not_after = Time.now + 365 * 24 * 60 * 60
168
+ c.public_key = cert_key
169
+ c.serial = 0x1
170
+ c.version = 0x0
171
+ c.sign cert_key, U2F::DIGEST.new
172
+ end
173
+ end
174
+
175
+ # The public key used for signing the device certificate.
176
+ #
177
+ # Returns a OpenSSL::PKey::EC instance.
178
+ def cert_key
179
+ @cert_key ||= generate_ec_key
180
+ end
181
+
182
+ # Generate an eliptic curve public/private key.
183
+ #
184
+ # Returns a OpenSSL::PKey::EC instance.
185
+ def generate_ec_key
186
+ OpenSSL::PKey::EC.new().tap do |ec|
187
+ ec.group = OpenSSL::PKey::EC::Group.new(CURVE_NAME)
188
+ ec.generate_key
189
+ # https://bugs.ruby-lang.org/issues/8177
190
+ ec.define_singleton_method(:private?) { private_key? }
191
+ ec.define_singleton_method(:public?) { public_key? }
192
+ end
193
+ end
194
+ end
@@ -89,13 +89,13 @@ module U2F
89
89
  # http://fidoalliance.org/specs/fido-u2f-raw-message-formats-v1.0-rd-20141008.pdf
90
90
  data = [
91
91
  "\x00",
92
- Digest::SHA256.digest(app_id),
93
- Digest::SHA256.digest(client_data_json),
92
+ ::U2F::DIGEST.digest(app_id),
93
+ ::U2F::DIGEST.digest(client_data_json),
94
94
  key_handle_raw,
95
95
  public_key_raw
96
96
  ].join
97
97
 
98
- parsed_certificate.public_key.verify(OpenSSL::Digest::SHA256.new, signature, data)
98
+ parsed_certificate.public_key.verify(::U2F::DIGEST.new, signature, data)
99
99
  end
100
100
 
101
101
  private
@@ -2,7 +2,7 @@ module U2F
2
2
  module RequestBase
3
3
  attr_accessor :version, :challenge, :app_id
4
4
 
5
- def as_json
5
+ def as_json(options = {})
6
6
  {
7
7
  version: version,
8
8
  challenge: challenge,
@@ -9,7 +9,7 @@ module U2F
9
9
  @app_id = app_id
10
10
  end
11
11
 
12
- def as_json
12
+ def as_json(options = {})
13
13
  super.merge(keyHandle: key_handle)
14
14
  end
15
15
  end
@@ -40,13 +40,13 @@ module U2F
40
40
  # registered device
41
41
  def verify(app_id, public_key_pem)
42
42
  data = [
43
- Digest::SHA256.digest(app_id),
43
+ ::U2F::DIGEST.digest(app_id),
44
44
  signature_data.byteslice(0, 5),
45
- Digest::SHA256.digest(client_data_json)
45
+ ::U2F::DIGEST.digest(client_data_json)
46
46
  ].join
47
47
 
48
48
  public_key = OpenSSL::PKey.read(public_key_pem)
49
- public_key.verify(OpenSSL::Digest::SHA256.new, signature, data)
49
+ public_key.verify(::U2F::DIGEST.new, signature, data)
50
50
  end
51
51
  end
52
52
  end
@@ -140,7 +140,7 @@ module U2F
140
140
  # - +PublicKeyDecodeError+:: if the +key+ argument is incorrect
141
141
  #
142
142
  def self.public_key_pem(key)
143
- fail PublicKeyDecodeError unless key.length == 65 || key[0] == "\x04"
143
+ fail PublicKeyDecodeError unless key.length == 65 && key[0] == "\x04"
144
144
  # http://tools.ietf.org/html/rfc5480
145
145
  der = OpenSSL::ASN1::Sequence([
146
146
  OpenSSL::ASN1::Sequence([
@@ -1,3 +1,3 @@
1
1
  module U2F
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -2,8 +2,8 @@ require 'spec_helper.rb'
2
2
 
3
3
  describe U2F::ClientData do
4
4
  let(:type) { '' }
5
- let(:registration_type) { 'navigator.id.finishEnrollment' }
6
- let(:authentication_type) { 'navigator.id.getAssertion' }
5
+ let(:registration_type) { U2F::ClientData::REGISTRATION_TYP }
6
+ let(:authentication_type) { U2F::ClientData::AUTHENTICATION_TYP }
7
7
 
8
8
  let(:client_data) do
9
9
  cd = U2F::ClientData.new
@@ -1,32 +1,18 @@
1
1
  require 'spec_helper.rb'
2
2
 
3
3
  describe U2F::RegisterResponse do
4
- let(:key_handle) do
5
- 'CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w'
6
- end
7
- let(:public_key) do
8
- 'BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y/yaFORPUe3c='
9
- end
10
- let(:certificate) do
11
- 'MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp/VRZHOwd2NZNzpnB9ePNKvUaWCGK/gN+cynnYFdwJ75iSgMVYb/RnFcdPwnsBzBU68hbhTnu/FvJxWo7rZJ2q7qXpA10eLVXJr4/4oSXEk9I/0IIHqOP98Ck/fAoI5gYI7ygndyqoPJ/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh/h7oKEKamCWk19dJp5jHQmumkHlvQhH/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg/0J+xOb4zl6a1z65nae4OTj7628/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg=='
12
- end
13
- let(:registration_data_json) do
14
- '{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'
15
- end
16
-
17
- let(:registration_data_without_padding) {
18
- "{\"registrationData\":\"BQT2UXxw7PXHmN5nCj1M3Lq_sibfqQehZbuUV1Vxr1l0J1Gdcv7FEvnPofmrSN44_pz8-XAj7pOpqB79rOphJPf2QM8nt8Jtyyj9_XmZWZTQMg2UVHvrin_Jc4tMHY9QmyCNDmSU9_Bhb-Ei4u5GPgLrpF1TaEYQCqUHboqDKt4x524wggIbMIIBBaADAgECAgR1o_Z1MAsGCSqGSIb3DQEBCzAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTk3MzY3OTczMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBmjfkNqa2mXzVh2ZxuES5coCvvENxDMDLmfd-0ACG0Fu7wR4ZTjKd9KAuidySpfona5csGmlM0Te_Zu35h_wwujEjAQMA4GCisGAQQBgsQKAQIEADALBgkqhkiG9w0BAQsDggEBAb0tuI0-CzSxBg4cAlyD6UyT4cKyJZGVhWdtPgj_mWepT3Tu9jXtdgA5F3jfZtTc2eGxuS-PPvqRAkZd40AXgM8A0YaXPwlT4s0RUTY9Y8aAQzQZeAHuZk3lKKd_LUCg5077dzdt90lC5eVTEduj6cOnHEqnOr2Cv75FuiQXX7QkGQxtoD-otgvhZ2Fjk29o7Iy9ik7ewHGXOfoVw_ruGWi0YfXBTuqEJ6H666vvMN4BZWHtzhC0k5ceQslB9Xdntky-GQgDqNkkBf32GKwAFT9JJrkO2BfsB-wfBrTiHr0AABYNTNKTceA5dtR3UVpI492VUWQbY3YmWUUfKTI7fM4wRgIhAIfEKaF0w43L3RJHXp8qeRKw8Ek0CVcZ6pvBsH3Wo3F1AiEA5w89AFOBrjoSsnuGdUgB4AGxc5bRnV-p8jGUNoVSUwI\",\"version\":\"U2F_V2\",\"challenge\":\"oqDO4u_tTvhm1LhFDVYhFwywQF0PzFsXPgjD-5lKGDY\",\"appId\":\"http://localhost:3000\",\"clientData\":\"eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCIsImNoYWxsZW5nZSI6Im9xRE80dV90VHZobTFMaEZEVlloRnd5d1FGMFB6RnNYUGdqRC01bEtHRFk9Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwiY2lkX3B1YmtleSI6IiJ9\"}"
19
- }
20
-
21
- let(:error_response) {
22
- "{\"errorCode\":4}"
23
- }
24
-
25
4
  let(:app_id) { 'http://demo.example.com' }
26
- let(:challenge) { 'yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8' }
27
-
5
+ let(:challenge) { U2F.urlsafe_encode64(SecureRandom.random_bytes(32)) }
6
+ let(:device) { U2F::FakeU2F.new(app_id) }
7
+ let(:key_handle) { U2F.urlsafe_encode64(device.key_handle_raw) }
8
+ let(:public_key) { Base64.strict_encode64(device.origin_public_key_raw) }
9
+ let(:certificate) { Base64.strict_encode64(device.cert_raw) }
10
+ let(:registration_data_json) { device.register_response(challenge) }
11
+ let(:registration_data_json_without_padding) do
12
+ device.register_response(challenge).gsub(" ", "")
13
+ end
14
+ let(:error_response) { device.register_response(challenge, error = true) }
28
15
  let(:registration_request) { U2F::RegisterRequest.new(challenge, app_id) }
29
-
30
16
  let(:register_response) do
31
17
  U2F::RegisterResponse.load_from_json(registration_data_json)
32
18
  end
@@ -43,7 +29,7 @@ describe U2F::RegisterResponse do
43
29
  end
44
30
 
45
31
  context 'with unpadded response' do
46
- let(:registration_data_json) { registration_data_without_padding }
32
+ let(:registration_data_json) { registration_data_json_without_padding }
47
33
  it 'does not raise "invalid base64" exception' do
48
34
  expect {
49
35
  register_response
@@ -1,16 +1,15 @@
1
1
  require 'spec_helper.rb'
2
2
 
3
3
  describe U2F::SignResponse do
4
- let(:json_response) do
5
- '{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w==" }'
6
- end
7
- let(:sign_response) do
8
- U2F::SignResponse.load_from_json json_response
9
- end
4
+ let(:app_id) { 'http://demo.example.com' }
5
+ let(:challenge) { U2F.urlsafe_encode64(SecureRandom.random_bytes(32)) }
6
+ let(:device) { U2F::FakeU2F.new(app_id) }
7
+ let(:json_response) { device.sign_response(challenge) }
8
+ let(:sign_response) { U2F::SignResponse.load_from_json json_response }
10
9
 
11
10
  describe '#counter' do
12
11
  subject { sign_response.counter }
13
- it { is_expected.to be 4 }
12
+ it { is_expected.to be device.counter }
14
13
  end
15
14
 
16
15
  describe '#user_present?' do
@@ -1,35 +1,27 @@
1
- require 'spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe U2F do
4
4
  let(:app_id) { 'http://demo.example.com' }
5
+ let(:device_challenge) { U2F.urlsafe_encode64(SecureRandom.random_bytes(32)) }
6
+ let(:auth_challenge) { device_challenge }
5
7
  let(:u2f) { U2F::U2F.new(app_id) }
6
- let(:challenge) { 'fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g' }
7
- let(:key_handle) do
8
- 'CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w'
9
- end
10
- let(:certificate) do
11
- "MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg"
12
- end
13
- let(:registration_data_json) do
14
- '{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'
15
- end
16
- let(:public_key) do
17
- U2F.urlsafe_decode64("BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c")
18
- end
19
- let(:json_response) do
20
- '{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'
21
- end
8
+ let(:device) { U2F::FakeU2F.new(app_id) }
9
+ let(:key_handle) { U2F.urlsafe_encode64(device.key_handle_raw) }
10
+ let(:certificate) { Base64.strict_encode64(device.cert_raw) }
11
+ let(:public_key) { device.origin_public_key_raw }
12
+ let(:register_response_json) { device.register_response(device_challenge) }
13
+ let(:sign_response_json) { device.sign_response(device_challenge) }
22
14
  let(:registration) do
23
15
  U2F::Registration.new(key_handle, public_key, certificate)
24
16
  end
25
17
  let(:register_response) do
26
- U2F::RegisterResponse.load_from_json(registration_data_json)
18
+ U2F::RegisterResponse.load_from_json(register_response_json)
27
19
  end
28
- let(:response) do
29
- U2F::SignResponse.load_from_json json_response
20
+ let(:sign_response) do
21
+ U2F::SignResponse.load_from_json sign_response_json
30
22
  end
31
23
  let(:sign_request) do
32
- U2F::SignRequest.new(key_handle, challenge, app_id)
24
+ U2F::SignRequest.new(key_handle, auth_challenge, app_id)
33
25
  end
34
26
 
35
27
  describe '#authentication_requests' do
@@ -44,7 +36,7 @@ describe U2F do
44
36
  let(:counter) { registration.counter }
45
37
  let(:reg_public_key) { registration.public_key }
46
38
  let (:u2f_authenticate) do
47
- u2f.authenticate!(challenge, response, reg_public_key, counter)
39
+ u2f.authenticate!(auth_challenge, sign_response, reg_public_key, counter)
48
40
  end
49
41
  context 'with correct parameters' do
50
42
  it 'does not raise an error' do
@@ -53,7 +45,7 @@ describe U2F do
53
45
  end
54
46
 
55
47
  context 'with incorrect challenge' do
56
- let(:challenge) { 'incorrect' }
48
+ let(:auth_challenge) { 'incorrect' }
57
49
  it 'raises NoMatchingRequestError' do
58
50
  expect { u2f_authenticate }.to raise_error(U2F::NoMatchingRequestError)
59
51
  end
@@ -82,27 +74,26 @@ describe U2F do
82
74
  end
83
75
 
84
76
  describe '#register!' do
85
- let(:challenge) { 'yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8' }
86
77
  context 'with correct registration data' do
87
78
  it 'returns a registration' do
88
79
  reg = nil
89
80
  expect {
90
- reg = u2f.register!(challenge, register_response)
81
+ reg = u2f.register!(auth_challenge, register_response)
91
82
  }.to_not raise_error
92
83
  expect(reg.key_handle).to eq key_handle
93
84
  end
94
85
 
95
86
  it 'accepts an array of challenges' do
96
- reg = u2f.register!(['another-challenge', challenge], register_response)
87
+ reg = u2f.register!(['another-challenge', auth_challenge], register_response)
97
88
  expect(reg).to be_a U2F::Registration
98
89
  end
99
90
  end
100
91
 
101
92
  context 'with unknown challenge' do
102
- let(:challenge) { 'non-matching' }
93
+ let(:auth_challenge) { 'non-matching' }
103
94
  it 'raises an UnmatchedChallengeError' do
104
95
  expect {
105
- u2f.register!(challenge, register_response)
96
+ u2f.register!(auth_challenge, register_response)
106
97
  }.to raise_error(U2F::UnmatchedChallengeError)
107
98
  end
108
99
  end
@@ -117,8 +108,17 @@ describe U2F do
117
108
  end
118
109
  end
119
110
 
120
- context 'with incorrect key' do
121
- let(:public_key) { U2F.urlsafe_decode64('NW5jdzdnODV3dm9nNzU4d2duNTd3') }
111
+ context 'with invalid key' do
112
+ let(:public_key) { U2F.urlsafe_decode64('YV6FVSmH0ObY1cBRCsYJZ/CXF1gKsL+DW46rMfpeymtDZted2Ut2BraszUK1wg1+YJ4Bxt6r24WHNUYqKgeaSq8=') }
113
+ it 'fails when first byte of the key is not 0x04' do
114
+ expect {
115
+ U2F::U2F.public_key_pem public_key
116
+ }.to raise_error(U2F::PublicKeyDecodeError)
117
+ end
118
+ end
119
+
120
+ context 'with truncated key' do
121
+ let(:public_key) { U2F.urlsafe_decode64('BJhSPkR3Rmgl') }
122
122
  it 'fails when key is to short' do
123
123
  expect {
124
124
  U2F::U2F.public_key_pem public_key
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: u2f
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johan Brissmyr
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-02-20 00:00:00.000000000 Z
12
+ date: 2015-06-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -107,6 +107,7 @@ files:
107
107
  - lib/u2f.rb
108
108
  - lib/u2f/client_data.rb
109
109
  - lib/u2f/errors.rb
110
+ - lib/u2f/fake_u2f.rb
110
111
  - lib/u2f/register_request.rb
111
112
  - lib/u2f/register_response.rb
112
113
  - lib/u2f/registration.rb