u2f 0.0.0 → 0.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 75a50469828893782345c5b90dfc2bf0272fa3c2
4
- data.tar.gz: 466e51009f4f2c670302d67930fc66e33c80c49d
3
+ metadata.gz: 047cac5bb30fd3ec7ab0d032ae465e6d9e9cadc0
4
+ data.tar.gz: 041fe8a755ec15ddd949523f61ed0863d646aae5
5
5
  SHA512:
6
- metadata.gz: a412287c1b11d1c85d966a3a7e68dfb7da43c7df73da08f67f06b1ace63dc28bfde8a83fcb6a9d6c748cecf4243552986d0abce203718f317add402c171cb3b0
7
- data.tar.gz: e761c7a2a0a04de96a2008342fa6a6fc33d04fb743d327691df51a0f3dd13a12d86aa411ac1f88ae4eb99bbe3e7350099d4ff342663cce5846a520af2f0fef12
6
+ metadata.gz: 829483f4451930d2eb42bab1f95b68b8c96b128b9068079592074c4f4e3365bc920c9f5e0539a534091ce253a19f375ef7521de2e35ae206f57d2b0dded41fa5
7
+ data.tar.gz: 43719723ce28854b37528e208cf5bdfe8d2ed69479e088af45c41388b46c3ab4a2c34b17ba3b5b1c6812e77b5d355360216d3929e54320562604c59dea0fc735
data/README.md CHANGED
@@ -1,3 +1,206 @@
1
1
  # Ruby U2F
2
2
 
3
- Work in progress.
3
+ [![Gem Version](https://badge.fury.io/rb/u2f.png)](http://badge.fury.io/rb/u2f)
4
+ [![Build Status](https://travis-ci.org/userbin/ruby-u2f.png)](https://travis-ci.org/userbin/ruby-u2f)
5
+ [![Code Climate](https://codeclimate.com/github/userbin/ruby-u2f/badges/gpa.svg)](https://codeclimate.com/github/userbin/ruby-u2f)
6
+ [![Coverage Status](https://img.shields.io/coveralls/userbin/ruby-u2f.svg)](https://coveralls.io/r/userbin/ruby-u2f)
7
+ [![security](https://hakiri.io/github/userbin/ruby-u2f/master.svg)](https://hakiri.io/github/userbin/ruby-u2f/master)
8
+
9
+ Provides functionality for working with the server side aspects of the U2F
10
+ protocol as defined in the [FIDO specifications](http://fidoalliance.org/specifications/download). To read more about U2F and how to use a U2F library, visit [developers.yubico.com/U2F](http://developers.yubico.com/U2F).
11
+
12
+ ## What is U2F?
13
+
14
+ U2F is an open 2-factor authentication standard that enables keychain devices, mobile phones and other devices to securely access any number of web-based services — instantly and with no drivers or client software needed. The U2F specifications were initially developed by Google, with contribution from Yubico and NXP, and are today hosted by the [FIDO Alliance](https://fidoalliance.org/).
15
+
16
+ ## Working example application
17
+
18
+ Check out the [example](https://github.com/userbin/ruby-u2f/tree/master/example) directory for a fully working Padrino server demonstrating U2F.
19
+
20
+ ## Installation
21
+
22
+ Add the `u2f` gem to your `Gemfile`
23
+
24
+ ```ruby
25
+ gem 'u2f'
26
+ ```
27
+
28
+ Currently, you need Google Chrome and the [FIDO U2F extension](https://chrome.google.com/webstore/detail/fido-u2f-universal-2nd-fa/pfboblefjcgdjicmnffhdgionmgcdmne) to enable U2F. To access the extension’s JavaScript API, add the script to the `<head>` section.
29
+
30
+ ```html
31
+ <script src="chrome-extension://pfboblefjcgdjicmnffhdgionmgcdmne/u2f-api.js"></script>
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ The U2F library has two major tasks:
37
+
38
+ - **Register** new devices.
39
+ - **Authenticate** previously registered devices.
40
+
41
+ Each task starts by generating a challenge on the server, which is rendered to a web view, read by the browser API:s and transmitted to the plugged in U2F devices for verification. The U2F device responds and triggers a callback in the browser, and a form is posted back to your server where you verify the challenge and store the U2F device information to your database.
42
+
43
+ You'll need an instance of `U2F:U2F`, which is conveniently placed in an [instance method](https://github.com/userbin/ruby-u2f/blob/master/example/app/helpers/helpers.rb) on the controller. The initializer takes an **App ID** as argument.
44
+
45
+ ```ruby
46
+ def u2f
47
+ @u2f ||= U2F::U2F.new(request.base_url)
48
+ end
49
+ ```
50
+
51
+ **Important:** A U2F client (e.g. Chrome) will compare the App ID with the current URI, so make sure it's the right format including schema and port, e.g. `https://demo.example.com:3000`. Check out the [App ID specification](https://developers.yubico.com/U2F/App_ID.html) for more details.
52
+
53
+ ### Registration
54
+
55
+ Generate the requests which will be sent to the U2F device.
56
+
57
+ ```ruby
58
+ # registrations_controller.rb
59
+ def new
60
+ # Generate one for each version of U2F, currently only `U2F_V2`
61
+ @registration_requests = u2f.registration_requests
62
+
63
+ # Store challenges. We need them for the verification step
64
+ session[:challenges] = @registration_requests.map(&:challenge)
65
+
66
+ # Fetch existing Registrations from your db and generate SignRequests
67
+ key_handles = Registration.map(&:key_handle)
68
+ @sign_requests = u2f.authentication_requests(key_handles)
69
+
70
+ render 'registrations/new'
71
+ end
72
+ ```
73
+
74
+ Render a form that will be automatically posted when the U2F device reponds.
75
+
76
+ ```html
77
+ <!-- registrations/new.html -->
78
+ <form action="/registrations" method="post">
79
+ <input type="hidden" name="response">
80
+ </form>
81
+ ```
82
+
83
+ ```javascript
84
+ // render requests from server into Javascript format
85
+ var registerRequests = <%= @registration_requests.to_json.html_safe %>;
86
+ var signRequests = <%= @sign_requests.to_json.html_safe %>;
87
+
88
+ u2f.register(registerRequests, signRequests, function(registerResponse) {
89
+ var form, reg;
90
+
91
+ if (registerResponse.errorCode) {
92
+ return alert("Registration error: " + registerResponse.errorCode);
93
+ }
94
+
95
+ form = document.forms[0];
96
+ response = document.querySelector('[name=response]');
97
+
98
+ response.value = JSON.stringify(registerResponse);
99
+
100
+ form.submit();
101
+ });
102
+ ```
103
+
104
+ Catch the response on your server, verify it, and store a reference to it in your database.
105
+
106
+ ```ruby
107
+ # registrations_controller.rb
108
+ def create
109
+ response = U2F::RegisterResponse.load_from_json(params[:response])
110
+
111
+ reg = begin
112
+ u2f.register!(session[:challenges], response)
113
+ rescue U2F::Error => e
114
+ return "Unable to register: <%= e.class.name %>"
115
+ ensure
116
+ session.delete(:challenges)
117
+ end
118
+
119
+ # save a reference to your database
120
+ Registration.create!(certificate: reg.certificate,
121
+ key_handle: reg.key_handle,
122
+ public_key: reg.public_key,
123
+ counter: reg.counter)
124
+
125
+ 'Registered!'
126
+ end
127
+ ```
128
+
129
+ ### Authentication
130
+
131
+ Generate the requests which will be sent to the U2F device.
132
+
133
+ ```ruby
134
+ # authentications_controller.rb
135
+ def new
136
+ # Fetch existing Registrations from your db
137
+ key_handles = Registration.map(&:key_handle)
138
+ return 'Need to register first' if key_handles.empty?
139
+
140
+ # Generate SignRequests
141
+ @sign_requests = u2f.authentication_requests(key_handles)
142
+
143
+ # Store challenges. We need them for the verification step
144
+ session[:challenges] = @sign_requests.map(&:challenge)
145
+
146
+ render 'authentications/new'
147
+ end
148
+ ```
149
+
150
+ Render a form that will be automatically posted when the U2F device reponds.
151
+
152
+ ```html
153
+ <!-- registrations/new.html -->
154
+ <form action="/authentications" method="post">
155
+ <input type="hidden" name="response">
156
+ </form>
157
+ ```
158
+
159
+ ```javascript
160
+ // render requests from server into Javascript format
161
+ var signRequests = <%= @sign_requests.to_json.html_safe %>;
162
+
163
+ u2f.sign(signRequests, function(signResponse) {
164
+ var form, reg;
165
+
166
+ if (signResponse.errorCode) {
167
+ return alert("Authentication error: " + signResponse.errorCode);
168
+ }
169
+
170
+ form = document.forms[0];
171
+ response = document.querySelector('[name=response]');
172
+
173
+ response.value = JSON.stringify(signResponse);
174
+
175
+ form.submit();
176
+ });
177
+ ```
178
+
179
+ Catch the response on your server, verify it, and bump the counter in your database reference.
180
+
181
+ ```ruby
182
+ # authentications_controller.rb
183
+ def create
184
+ response = U2F::SignResponse.load_from_json(params[:response])
185
+
186
+ registration = Registration.first(key_handle: response.key_handle)
187
+ return 'Need to register first' unless registration
188
+
189
+ begin
190
+ u2f.authenticate!(session[:challenges], response,
191
+ registration.public_key, registration.counter)
192
+ rescue U2F::Error => e
193
+ return "Unable to authenticate: <%= e.class.name %>"
194
+ ensure
195
+ session.delete(:challenges)
196
+ end
197
+
198
+ registration.update(counter: response.counter)
199
+
200
+ 'Authenticated!'
201
+ end
202
+ ```
203
+
204
+ ## License
205
+
206
+ MIT License. Copyright (c) 2014 by Johan Brissmyr and Sebastian Wallin
data/lib/u2f.rb CHANGED
@@ -1,2 +1,17 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'openssl'
4
+
5
+ require 'u2f/client_data'
6
+ require 'u2f/collection'
7
+ require 'u2f/errors'
8
+ require 'u2f/request_base'
9
+ require 'u2f/register_request'
10
+ require 'u2f/register_response'
11
+ require 'u2f/registration'
12
+ require 'u2f/sign_request'
13
+ require 'u2f/sign_response'
14
+ require 'u2f/u2f'
15
+
1
16
  module U2F
2
17
  end
@@ -0,0 +1,26 @@
1
+ module U2F
2
+ ##
3
+ # A representation of ClientData, chapter 7
4
+ # http://fidoalliance.org/specs/fido-u2f-raw-message-formats-v1.0-rd-20141008.pdf
5
+ class ClientData
6
+ attr_accessor :typ, :challenge, :origin
7
+ alias_method :type, :typ
8
+
9
+ def registration?
10
+ typ == 'navigator.id.finishEnrollment'
11
+ end
12
+
13
+ def authentication?
14
+ typ == 'navigator.id.getAssertion'
15
+ end
16
+
17
+ def self.load_from_json(json)
18
+ client_data = ::JSON.parse(json)
19
+ instance = new
20
+ instance.typ = client_data['typ']
21
+ instance.challenge = client_data['challenge']
22
+ instance.origin = client_data['origin']
23
+ instance
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module U2F
2
+ class Collection < Array
3
+ def initialize(array)
4
+ super([*array])
5
+ end
6
+
7
+ def to_json
8
+ "[#{map { |a| a.to_json }.join(',')}]"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module U2F
2
+ class Error < StandardError;end
3
+ class UnmatchedChallengeError < Error; end
4
+ class ClientDataTypeError < Error; end
5
+ class PublicKeyDecodeError < Error; end
6
+ class AttestationDecodeError < Error; end
7
+ class AttestationVerificationError < Error; end
8
+ class AttestationSignatureError < Error; end
9
+ class NoMatchingRequestError < Error; end
10
+ class NoMatchingRegistrationError < Error; end
11
+ class CounterToLowError < Error; end
12
+ class AuthenticationFailedError < Error; end
13
+ class UserNotPresentError < Error;end
14
+ end
@@ -0,0 +1,10 @@
1
+ module U2F
2
+ class RegisterRequest
3
+ include RequestBase
4
+
5
+ def initialize(challenge, app_id)
6
+ @challenge = challenge
7
+ @app_id = app_id
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,134 @@
1
+ module U2F
2
+ ##
3
+ # Representation of a U2F registration response.
4
+ # See chapter 4.3:
5
+ # http://fidoalliance.org/specs/fido-u2f-raw-message-formats-v1.0-rd-20141008.pdf
6
+ class RegisterResponse
7
+ attr_accessor :client_data, :client_data_json, :registration_data_raw
8
+
9
+ PUBLIC_KEY_OFFSET = 1
10
+ PUBLIC_KEY_LENGTH = 65
11
+ KEY_HANDLE_LENGTH_LENGTH = 1
12
+ KEY_HANDLE_LENGTH_OFFSET = PUBLIC_KEY_OFFSET + PUBLIC_KEY_LENGTH
13
+ KEY_HANDLE_OFFSET = KEY_HANDLE_LENGTH_OFFSET + KEY_HANDLE_LENGTH_LENGTH
14
+
15
+ def self.load_from_json(json)
16
+ # TODO: validate
17
+ data = JSON.parse(json)
18
+ instance = new
19
+ instance.client_data_json =
20
+ Base64.urlsafe_decode64(data['clientData'])
21
+ instance.client_data =
22
+ ClientData.load_from_json(instance.client_data_json)
23
+ instance.registration_data_raw =
24
+ Base64.urlsafe_decode64(data['registrationData'])
25
+ instance
26
+ end
27
+
28
+ ##
29
+ # The attestation certificate in Base64 encoded X.509 DER format
30
+ def certificate
31
+ Base64.strict_encode64(certificate_raw)
32
+ end
33
+
34
+ ##
35
+ # Length of the attestation certificate
36
+ def certificate_length
37
+ # http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One#Example_encoded_in_DER
38
+ #
39
+ # Do some quick parsing of the certificate DER format.
40
+ # First bytes (TLV) could be ex:
41
+ # T 0x30: SEQUENCE Tag
42
+ # L 0x82: Length (2 length bytes)
43
+ # 0x02 0xe2: Two bytes indicated by the L byte.
44
+ # Makes up the data length 738 (which makes 742 in total)
45
+
46
+ t_byte = certificate_bytes(0)
47
+
48
+ fail AttestationDecodeError unless t_byte == "\x30"
49
+
50
+ l_byte = certificate_bytes(1).unpack('c').first # 8-bit signed integer
51
+ # If the L-byte has MSB set to 1 (ie. < 0) the value will tell how many
52
+ # following bytes is used to describe the total length. Otherwise it will
53
+ # describe the data length
54
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/bb648641(v=vs.85).aspx
55
+
56
+ nbr_length_bytes = 0
57
+ cert_length = if l_byte < 0
58
+ nbr_length_bytes = l_byte + 0x80 # last 7-bits is the number of bytes
59
+ length_bytes = certificate_bytes(2, nbr_length_bytes).unpack('C*')
60
+ length_bytes.reverse.each_with_index.inject(0) do |sum, (val, idx)|
61
+ sum + (val << (8*idx))
62
+ end
63
+ else
64
+ l_byte
65
+ end
66
+
67
+ cert_length + nbr_length_bytes + 2 # Make up for the T and L bytes them selves
68
+ end
69
+
70
+ ##
71
+ # The attestation certificate in X.509 DER format
72
+ def certificate_raw
73
+ certificate_bytes(0, certificate_length)
74
+ end
75
+
76
+ ##
77
+ # Returns the key handle from registration data, URL safe base64 encoded
78
+ def key_handle
79
+ Base64.urlsafe_encode64(key_handle_raw)
80
+ end
81
+
82
+ def key_handle_raw
83
+ registration_data_raw.byteslice(KEY_HANDLE_OFFSET, key_handle_length)
84
+ end
85
+
86
+ ##
87
+ # Returns the length of the key handle, extracted from the registration data
88
+ def key_handle_length
89
+ registration_data_raw.byteslice(KEY_HANDLE_LENGTH_OFFSET).unpack('C').first
90
+ end
91
+
92
+ ##
93
+ # Returns the public key, extracted from the registration data
94
+ def public_key
95
+ # Base64 encode without linefeeds
96
+ Base64.strict_encode64(public_key_raw)
97
+ end
98
+
99
+ def public_key_raw
100
+ registration_data_raw.byteslice(PUBLIC_KEY_OFFSET, PUBLIC_KEY_LENGTH)
101
+ end
102
+
103
+ ##
104
+ # Returns the signature, extracted from the registration data
105
+ def signature
106
+ registration_data_raw.byteslice(
107
+ (KEY_HANDLE_OFFSET + key_handle_length + certificate_length)..-1)
108
+ end
109
+
110
+ ##
111
+ # Verifies the registration data agains the app id
112
+ def verify(app_id)
113
+ # Chapter 4.3 in
114
+ # http://fidoalliance.org/specs/fido-u2f-raw-message-formats-v1.0-rd-20141008.pdf
115
+ data = [
116
+ "\x00",
117
+ Digest::SHA256.digest(app_id),
118
+ Digest::SHA256.digest(client_data_json),
119
+ key_handle_raw,
120
+ public_key_raw
121
+ ].join
122
+
123
+ cert = OpenSSL::X509::Certificate.new(certificate_raw)
124
+ cert.public_key.verify(OpenSSL::Digest::SHA256.new, signature, data)
125
+ end
126
+
127
+ private
128
+
129
+ def certificate_bytes(offset, length = 1)
130
+ base_offset = KEY_HANDLE_OFFSET + key_handle_length
131
+ registration_data_raw.byteslice(base_offset + offset, length)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,16 @@
1
+ module U2F
2
+ ##
3
+ # A representation of a registered U2F device
4
+ class Registration
5
+ attr_accessor :key_handle, :public_key, :certificate, :counter
6
+ def initialize(key_handle, public_key, certificate)
7
+ @key_handle = key_handle
8
+ @public_key = public_key
9
+ @certificate = certificate
10
+ end
11
+
12
+ def counter
13
+ @counter.nil? ? 0 : @counter
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ module U2F
2
+ module RequestBase
3
+ attr_accessor :version, :challenge, :app_id
4
+
5
+ def as_json
6
+ {
7
+ version: version,
8
+ challenge: challenge,
9
+ appId: app_id
10
+ }
11
+ end
12
+
13
+ def to_json
14
+ ::JSON.dump(as_json)
15
+ end
16
+
17
+ def version
18
+ 'U2F_V2'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ module U2F
2
+ class SignRequest
3
+ include RequestBase
4
+ attr_accessor :key_handle
5
+
6
+ def initialize(key_handle, challenge, app_id)
7
+ @key_handle = key_handle
8
+ @challenge = challenge
9
+ @app_id = app_id
10
+ end
11
+
12
+ def as_json
13
+ super.merge(keyHandle: key_handle)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,52 @@
1
+ module U2F
2
+ class SignResponse
3
+ attr_accessor :client_data, :client_data_json, :key_handle, :signature_data
4
+
5
+ def self.load_from_json(json)
6
+ data = ::JSON.parse(json)
7
+ instance = new
8
+ instance.client_data_json =
9
+ Base64.urlsafe_decode64(data['clientData'])
10
+ instance.client_data =
11
+ ClientData.load_from_json(instance.client_data_json)
12
+ instance.key_handle = data['keyHandle']
13
+ instance.signature_data =
14
+ Base64.urlsafe_decode64(data['signatureData'])
15
+ instance
16
+ end
17
+
18
+ ##
19
+ # Counter value that the U2F token increments every time it performs an
20
+ # authentication operation
21
+ def counter
22
+ signature_data[1..4].unpack('N').first
23
+ end
24
+
25
+ ##
26
+ # signature is to be verified using the public key obtained during
27
+ # registration.
28
+ def signature
29
+ signature_data.byteslice(5..-1)
30
+ end
31
+
32
+ ##
33
+ # If user presence was verified
34
+ def user_present?
35
+ signature_data[0].unpack('C').first == 1
36
+ end
37
+
38
+ ##
39
+ # Verifies the response against an app id and the public key of the
40
+ # registered device
41
+ def verify(app_id, public_key_pem)
42
+ data = [
43
+ Digest::SHA256.digest(app_id),
44
+ signature_data.byteslice(0, 5),
45
+ Digest::SHA256.digest(client_data_json)
46
+ ].join
47
+
48
+ public_key = OpenSSL::PKey.read(public_key_pem)
49
+ public_key.verify(OpenSSL::Digest::SHA256.new, signature, data)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,113 @@
1
+ module U2F
2
+ class U2F
3
+ attr_accessor :app_id
4
+ def initialize(app_id)
5
+ @app_id = app_id
6
+ end
7
+
8
+ ##
9
+ # Generate data to be sent to the U2F device before authenticating
10
+ def authentication_requests(key_handles)
11
+ key_handles = [key_handles] unless key_handles.is_a? Array
12
+ sign_requests = key_handles.map do |key_handle|
13
+ SignRequest.new(key_handle, challenge, app_id)
14
+ end
15
+ Collection.new(sign_requests)
16
+ end
17
+
18
+ ##
19
+ # Authenticate a response from the U2F device
20
+ def authenticate!(challenges, response, registration_public_key,
21
+ registration_counter)
22
+ # Handle both single and Array input
23
+ challenges = [challenges] unless challenges.is_a? Array
24
+
25
+ # TODO: check that it's the correct key_handle as well
26
+ unless challenges.include?(response.client_data.challenge)
27
+ fail NoMatchingRequestError
28
+ end
29
+
30
+ fail ClientDataTypeError unless response.client_data.authentication?
31
+
32
+ pem = U2F.public_key_pem(registration_public_key)
33
+
34
+ fail AuthenticationFailedError unless response.verify(app_id, pem)
35
+
36
+ fail UserNotPresentError unless response.user_present?
37
+
38
+ unless response.counter > registration_counter
39
+ fail CounterToLowError
40
+ end
41
+ end
42
+
43
+ ##
44
+ # Generates a 32 byte long random U2F challenge
45
+ def challenge
46
+ Base64.urlsafe_encode64(SecureRandom.random_bytes(32))
47
+ end
48
+
49
+ ##
50
+ # Generate data to be used when registering a U2F device
51
+ def registration_requests
52
+ # TODO: generate a request for each supported version
53
+ Collection.new(RegisterRequest.new(challenge, @app_id))
54
+ end
55
+
56
+ ##
57
+ # Authenticate the response from the U2F device when registering
58
+ # Returns a registration object
59
+ def register!(challenges, response)
60
+ challenges = [challenges] unless challenges.is_a? Array
61
+ challenge = challenges.detect do |chg|
62
+ chg == response.client_data.challenge
63
+ end
64
+
65
+ fail UnmatchedChallengeError unless challenge
66
+
67
+ fail ClientDataTypeError unless response.client_data.registration?
68
+
69
+ # Validate public key
70
+ U2F.public_key_pem(response.public_key_raw)
71
+
72
+ unless U2F.validate_certificate(response.certificate_raw)
73
+ fail AttestationVerificationError
74
+ end
75
+
76
+ fail AttestationSignatureError unless response.verify(app_id)
77
+
78
+ registration = Registration.new(
79
+ response.key_handle,
80
+ response.public_key,
81
+ response.certificate
82
+ )
83
+ registration
84
+ end
85
+
86
+ ##
87
+ # Convert a binary public key to PEM format
88
+ def self.public_key_pem(key)
89
+ fail PublicKeyDecodeError unless key.length == 65 || key[0] == "\x04"
90
+ # http://tools.ietf.org/html/rfc5480
91
+ der = OpenSSL::ASN1::Sequence([
92
+ OpenSSL::ASN1::Sequence([
93
+ OpenSSL::ASN1::ObjectId('1.2.840.10045.2.1'), # id-ecPublicKey
94
+ OpenSSL::ASN1::ObjectId('1.2.840.10045.3.1.7') # secp256r1
95
+ ]),
96
+ OpenSSL::ASN1::BitString(key)
97
+ ]).to_der
98
+
99
+ pem = "-----BEGIN PUBLIC KEY-----\r\n" +
100
+ Base64.strict_encode64(der).scan(/.{1,64}/).join("\r\n") +
101
+ "\r\n-----END PUBLIC KEY-----"
102
+ pem
103
+ end
104
+
105
+ def self.validate_certificate(certificate_raw)
106
+ # TODO
107
+ return true
108
+ # cacert = OpenSSL::X509::Certificate.new()
109
+ # cert = OpenSSL::X509::Certificate.new(certificate_raw)
110
+ # cert.verify(cacert.public_key)
111
+ end
112
+ end
113
+ end
@@ -1,3 +1,3 @@
1
1
  module U2F
2
- VERSION = "0.0.0"
2
+ VERSION = "0.0.1"
3
3
  end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe U2F::ClientData do
4
+ let(:type) { '' }
5
+ let(:registration_type) { 'navigator.id.finishEnrollment' }
6
+ let(:authentication_type) { 'navigator.id.getAssertion' }
7
+
8
+ let(:client_data) do
9
+ cd = U2F::ClientData.new
10
+ cd.typ = type
11
+ cd
12
+ end
13
+
14
+ describe '#registration?' do
15
+ subject { client_data.registration? }
16
+ context 'for correct type' do
17
+ let(:type) { registration_type }
18
+ it { is_expected.to be_truthy }
19
+ end
20
+
21
+ context 'for incorrect type' do
22
+ let(:type) { authentication_type }
23
+ it { is_expected.to be_falsey }
24
+ end
25
+ end
26
+
27
+ describe '#authentication?' do
28
+ subject { client_data.authentication? }
29
+ context 'for correct type' do
30
+ let(:type) { authentication_type }
31
+ it { is_expected.to be_truthy }
32
+ end
33
+
34
+ context 'for incorrect type' do
35
+ let(:type) { registration_type }
36
+ it { is_expected.to be_falsey }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe U2F::Collection do
4
+ let(:collection) { U2F::Collection.new(input) }
5
+ describe '#to_json' do
6
+ subject { collection.to_json }
7
+ context 'with single object' do
8
+ let(:input) { 'one' }
9
+ it do
10
+ is_expected.to match_json_expression(['one'])
11
+ end
12
+ end
13
+ context 'with single object' do
14
+ let(:input) { ['one', 'two'] }
15
+ it do
16
+ is_expected.to match_json_expression(['one', 'two'])
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe U2F::RegisterRequest do
4
+ let(:app_id) { 'http://example.com' }
5
+ let(:challenge) { 'fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g' }
6
+
7
+ let(:sign_request) do
8
+ U2F::RegisterRequest.new(challenge, app_id)
9
+ end
10
+
11
+ describe '#to_json' do
12
+ subject { sign_request.to_json }
13
+ it do
14
+ is_expected.to match_json_expression(
15
+ version: String,
16
+ appId: String,
17
+ challenge: String
18
+ )
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper.rb'
2
+
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(:app_id) { 'http://demo.example.com' }
18
+ let(:challenge) { 'yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8' }
19
+
20
+ let(:registration_request) { U2F::RegisterRequest.new(challenge, app_id) }
21
+
22
+ let(:register_response) do
23
+ U2F::RegisterResponse.load_from_json(registration_data_json)
24
+ end
25
+
26
+ describe '#certificate' do
27
+ subject { register_response.certificate }
28
+ it { is_expected.to eq certificate }
29
+ end
30
+
31
+ describe '#client_data' do
32
+ context 'challenge' do
33
+ subject { register_response.client_data.challenge }
34
+ it { is_expected.to eq challenge }
35
+ end
36
+ end
37
+
38
+ describe '#key_handle' do
39
+ subject { register_response.key_handle }
40
+ it { is_expected.to eq key_handle }
41
+ end
42
+
43
+ describe '#key_handle_length' do
44
+ subject { register_response.key_handle_length }
45
+ it { is_expected.to eq Base64.urlsafe_decode64(key_handle).length }
46
+ end
47
+
48
+ describe '#public_key' do
49
+ subject { register_response.public_key }
50
+ it { is_expected.to eq public_key }
51
+ end
52
+
53
+ describe '#verify' do
54
+ subject { register_response.verify(app_id) }
55
+ it { is_expected.to be_truthy }
56
+ end
57
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe U2F::SignRequest do
4
+ let(:app_id) { 'http://example.com' }
5
+ let(:challenge) { 'fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g' }
6
+ let(:key_handle) do
7
+ 'CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w=='
8
+ end
9
+ let(:sign_request) do
10
+ U2F::SignRequest.new(key_handle, challenge, app_id)
11
+ end
12
+
13
+ describe '#to_json' do
14
+ subject { sign_request.to_json }
15
+ it do
16
+ is_expected.to match_json_expression(
17
+ version: String,
18
+ appId: String,
19
+ challenge: String,
20
+ keyHandle: String
21
+ )
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper.rb'
2
+
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
10
+
11
+ describe '#counter' do
12
+ subject { sign_response.counter }
13
+ it { is_expected.to be 4 }
14
+ end
15
+
16
+ describe '#user_present?' do
17
+ subject { sign_response.user_present? }
18
+ it { is_expected.to be true }
19
+ end
20
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+
3
+ describe U2F do
4
+ let(:app_id) { 'http://demo.example.com' }
5
+ 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
+ Base64.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
22
+ let(:registration) do
23
+ U2F::Registration.new(key_handle, public_key, certificate)
24
+ end
25
+ let(:register_response) do
26
+ U2F::RegisterResponse.load_from_json(registration_data_json)
27
+ end
28
+ let(:response) do
29
+ U2F::SignResponse.load_from_json json_response
30
+ end
31
+ let(:sign_request) do
32
+ U2F::SignRequest.new(key_handle, challenge, app_id)
33
+ end
34
+
35
+ describe '#authentication_requests' do
36
+ let(:requests) { u2f.authentication_requests(key_handle) }
37
+ it 'returns an array of requests' do
38
+ expect(requests).to be_an Array
39
+ requests.each { |r| expect(r).to be_a U2F::SignRequest }
40
+ end
41
+ end
42
+
43
+ describe '#authenticate!' do
44
+ let(:counter) { registration.counter }
45
+ let(:reg_public_key) { registration.public_key }
46
+ let (:u2f_authenticate) do
47
+ u2f.authenticate!(challenge, response, reg_public_key, counter)
48
+ end
49
+ context 'with correct parameters' do
50
+ it 'does not raise an error' do
51
+ expect { u2f_authenticate }.to_not raise_error
52
+ end
53
+ end
54
+
55
+ context 'with incorrect challenge' do
56
+ let(:challenge) { 'incorrect' }
57
+ it 'raises NoMatchingRequestError' do
58
+ expect { u2f_authenticate }.to raise_error(U2F::NoMatchingRequestError)
59
+ end
60
+ end
61
+
62
+ context 'with incorrect counter' do
63
+ let(:counter) { 1000 }
64
+ it 'raises CounterToLowError' do
65
+ expect { u2f_authenticate }.to raise_error(U2F::CounterToLowError)
66
+ end
67
+ end
68
+ context 'with incorrect counter' do
69
+ let(:reg_public_key) { "\x00" }
70
+ it 'raises CounterToLowError' do
71
+ expect { u2f_authenticate }.to raise_error(U2F::PublicKeyDecodeError)
72
+ end
73
+ end
74
+ end
75
+
76
+ describe '#registration_requests' do
77
+ let(:requests) { u2f.registration_requests }
78
+ it 'returns an array of requests' do
79
+ expect(requests).to be_an Array
80
+ requests.each { |r| expect(r).to be_a U2F::RegisterRequest }
81
+ end
82
+ end
83
+
84
+ describe '#register!' do
85
+ let(:challenge) { 'yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8' }
86
+ context 'with correct registration data' do
87
+ it 'returns a registration' do
88
+ reg = nil
89
+ expect {
90
+ reg = u2f.register!(challenge, register_response)
91
+ }.to_not raise_error
92
+ expect(reg.key_handle).to eq key_handle
93
+ end
94
+
95
+ it 'accepts an array of challenges' do
96
+ reg = u2f.register!(['another-challenge', challenge], register_response)
97
+ expect(reg).to be_a U2F::Registration
98
+ end
99
+ end
100
+
101
+ context 'with unknown challenge' do
102
+ let(:challenge) { 'non-matching' }
103
+ it 'raises an UnmatchedChallengeError' do
104
+ expect {
105
+ u2f.register!(challenge, register_response)
106
+ }.to raise_error(U2F::UnmatchedChallengeError)
107
+ end
108
+ end
109
+ end
110
+
111
+ describe '::public_key_pem' do
112
+ context 'with correct key' do
113
+ it 'wraps the result' do
114
+ pem = U2F::U2F.public_key_pem public_key
115
+ expect(pem).to start_with '-----BEGIN PUBLIC KEY-----'
116
+ expect(pem).to end_with '-----END PUBLIC KEY-----'
117
+ end
118
+ end
119
+
120
+ context 'with incorrect key' do
121
+ let(:public_key) { Base64.urlsafe_decode64('NW5jdzdnODV3dm9nNzU4d2duNTd3') }
122
+ it 'fails when key is to short' do
123
+ expect {
124
+ U2F::U2F.public_key_pem public_key
125
+ }.to raise_error(U2F::PublicKeyDecodeError)
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,5 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'json_expressions/rspec'
5
+ require 'u2f'
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.0.0
4
+ version: 0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johan Brissmyr
@@ -9,8 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-10-31 00:00:00.000000000 Z
12
+ date: 2014-11-05 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: rspec
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -25,6 +39,48 @@ dependencies:
25
39
  - - ">="
26
40
  - !ruby/object:Gem::Version
27
41
  version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: json_expressions
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rubocop
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: coveralls
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
28
84
  description: U2F library
29
85
  email:
30
86
  - brissmyr@gmail.com
@@ -35,7 +91,25 @@ extra_rdoc_files: []
35
91
  files:
36
92
  - README.md
37
93
  - lib/u2f.rb
94
+ - lib/u2f/client_data.rb
95
+ - lib/u2f/collection.rb
96
+ - lib/u2f/errors.rb
97
+ - lib/u2f/register_request.rb
98
+ - lib/u2f/register_response.rb
99
+ - lib/u2f/registration.rb
100
+ - lib/u2f/request_base.rb
101
+ - lib/u2f/sign_request.rb
102
+ - lib/u2f/sign_response.rb
103
+ - lib/u2f/u2f.rb
38
104
  - lib/version.rb
105
+ - spec/lib/client_data_spec.rb
106
+ - spec/lib/collection_spec.rb
107
+ - spec/lib/register_request_spec.rb
108
+ - spec/lib/register_response_spec.rb
109
+ - spec/lib/sign_request_spec.rb
110
+ - spec/lib/sign_response_spec.rb
111
+ - spec/lib/u2f_spec.rb
112
+ - spec/spec_helper.rb
39
113
  homepage: https://github.com/userbin/ruby-u2f
40
114
  licenses:
41
115
  - MIT
@@ -60,4 +134,12 @@ rubygems_version: 2.2.2
60
134
  signing_key:
61
135
  specification_version: 4
62
136
  summary: U2F library
63
- test_files: []
137
+ test_files:
138
+ - spec/lib/client_data_spec.rb
139
+ - spec/lib/collection_spec.rb
140
+ - spec/lib/register_request_spec.rb
141
+ - spec/lib/register_response_spec.rb
142
+ - spec/lib/sign_request_spec.rb
143
+ - spec/lib/sign_response_spec.rb
144
+ - spec/lib/u2f_spec.rb
145
+ - spec/spec_helper.rb