1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
# Ruby U2F
[](http://badge.fury.io/rb/u2f)
[](https://gemnasium.com/castle/ruby-u2f)
[](https://hakiri.io/github/castle/ruby-u2f/master)
[](https://travis-ci.org/castle/ruby-u2f)
[](https://codeclimate.com/github/castle/ruby-u2f)
[](https://coveralls.io/r/castle/ruby-u2f)
Provides functionality for working with the server side aspects of the U2F
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).
## What is U2F?
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/).
## Working example application
Check out the [example](https://github.com/castle/ruby-u2f/tree/master/example) directory for a fully working Padrino server demonstrating U2F.
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/).
You'll need Google Chrome 41 or later to use U2F.
## Installation
Add the `u2f` gem to your `Gemfile`
```ruby
gem 'u2f'
```
## Usage
The U2F library has two major tasks:
- **Register** new devices.
- **Authenticate** previously registered devices.
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.
You'll need an instance of `U2F:U2F`, which is conveniently placed in an [instance method](https://github.com/castle/ruby-u2f/blob/master/example/app/helpers/helpers.rb) on the controller. The initializer takes an **App ID** as argument.
```ruby
def u2f
@u2f ||= U2F::U2F.new(request.base_url)
end
```
**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.
### Registration
Generate the requests which will be sent to the U2F device.
```ruby
# registrations_controller.rb
def new
# Generate one for each version of U2F, currently only `U2F_V2`
@registration_requests = u2f.registration_requests
# Store challenges. We need them for the verification step
session[:challenges] = @registration_requests.map(&:challenge)
# Fetch existing Registrations from your db and generate SignRequests
key_handles = Registration.map(&:key_handle)
@sign_requests = u2f.authentication_requests(key_handles)
render 'registrations/new'
end
```
Render a form that will be automatically posted when the U2F device reponds.
```html
<!-- registrations/new.html -->
<form action="/registrations" method="post">
<input type="hidden" name="response">
</form>
```
```javascript
// render requests from server into Javascript format
var registerRequests = <%= @registration_requests.as_json.to_json.html_safe %>;
var signRequests = <%= @sign_requests.as_json.to_json.html_safe %>;
u2f.register(registerRequests, signRequests, function(registerResponse) {
var form, reg;
if (registerResponse.errorCode) {
return alert("Registration error: " + registerResponse.errorCode);
}
form = document.forms[0];
response = document.querySelector('[name=response]');
response.value = JSON.stringify(registerResponse);
form.submit();
});
```
Catch the response on your server, verify it, and store a reference to it in your database.
```ruby
# registrations_controller.rb
def create
response = U2F::RegisterResponse.load_from_json(params[:response])
reg = begin
u2f.register!(session[:challenges], response)
rescue U2F::Error => e
return "Unable to register: <%= e.class.name %>"
ensure
session.delete(:challenges)
end
# save a reference to your database
Registration.create!(certificate: reg.certificate,
key_handle: reg.key_handle,
public_key: reg.public_key,
counter: reg.counter)
'Registered!'
end
```
### Authentication
Generate the requests which will be sent to the U2F device.
```ruby
# authentications_controller.rb
def new
# Fetch existing Registrations from your db
key_handles = Registration.map(&:key_handle)
return 'Need to register first' if key_handles.empty?
# Generate SignRequests
@sign_requests = u2f.authentication_requests(key_handles)
# Store challenges. We need them for the verification step
session[:challenges] = @sign_requests.map(&:challenge)
render 'authentications/new'
end
```
Render a form that will be automatically posted when the U2F device reponds.
```html
<!-- registrations/new.html -->
<form action="/authentications" method="post">
<input type="hidden" name="response">
</form>
```
```javascript
// render requests from server into Javascript format
var signRequests = <%= @sign_requests.as_json.to_json.html_safe %>;
u2f.sign(signRequests, function(signResponse) {
var form, reg;
if (signResponse.errorCode) {
return alert("Authentication error: " + signResponse.errorCode);
}
form = document.forms[0];
response = document.querySelector('[name=response]');
response.value = JSON.stringify(signResponse);
form.submit();
});
```
Catch the response on your server, verify it, and bump the counter in your database reference.
```ruby
# authentications_controller.rb
def create
response = U2F::SignResponse.load_from_json(params[:response])
registration = Registration.first(key_handle: response.key_handle)
return 'Need to register first' unless registration
begin
u2f.authenticate!(session[:challenges], response,
Base64.decode64(registration.public_key),
registration.counter)
rescue U2F::Error => e
return "Unable to authenticate: <%= e.class.name %>"
ensure
session.delete(:challenges)
end
registration.update(counter: response.counter)
'Authenticated!'
end
```
## License
MIT License. Copyright (c) 2015 by Johan Brissmyr and Sebastian Wallin
|