u2f 0.1.0 → 0.2.0

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.
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