webauthn 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +24 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +3 -1
- data/README.md +77 -6
- data/Rakefile +5 -1
- data/bin/console +1 -0
- data/lib/webauthn.rb +22 -2
- data/lib/webauthn/attestation_statement.rb +21 -0
- data/lib/webauthn/attestation_statement/base.rb +19 -0
- data/lib/webauthn/attestation_statement/fido_u2f.rb +61 -0
- data/lib/webauthn/attestation_statement/none.rb +13 -0
- data/lib/webauthn/authenticator_attestation_response.rb +79 -0
- data/lib/webauthn/authenticator_data.rb +82 -0
- data/lib/webauthn/authenticator_data/attested_credential_data.rb +65 -0
- data/lib/webauthn/authenticator_data/attested_credential_data/public_key_u2f.rb +43 -0
- data/lib/webauthn/client_data.rb +46 -0
- data/lib/webauthn/version.rb +4 -2
- data/webauthn.gemspec +17 -5
- metadata +63 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 171f428eb4d325429c9a5e94fb5c217b4449fd1cd0d337a1e826f8c63754405b
|
4
|
+
data.tar.gz: 0fa1366a9247171005f68fed977ad7bab8d4dbb89485a21be3a4017aa22d034f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd05fe7063e2532255cc2f8e79c5faa72a4e625f35d3e56d2a21320c0d3ce16bd21483578af8ce22d65951cee8de19c765b8245132a407d95c5b82809444f649
|
7
|
+
data.tar.gz: ed40d6cc3da9d6c331b95180cabefd781df08f3bba653501d6a7248cc567a82615a4fa94429e46b68e09764458af9b108a4d8bd201c721154efbaef09e89d1b0
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.5
|
3
|
+
DisabledByDefault: true
|
4
|
+
|
5
|
+
Bundler:
|
6
|
+
Enabled: true
|
7
|
+
|
8
|
+
Gemspec:
|
9
|
+
Enabled: true
|
10
|
+
|
11
|
+
Layout:
|
12
|
+
Enabled: true
|
13
|
+
|
14
|
+
Performance:
|
15
|
+
Enabled: true
|
16
|
+
|
17
|
+
Security:
|
18
|
+
Enabled: true
|
19
|
+
|
20
|
+
Style/FrozenStringLiteralComment:
|
21
|
+
Enabled: true
|
22
|
+
|
23
|
+
Style/RedundantFreeze:
|
24
|
+
Enabled: true
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [Unreleased]
|
4
|
+
|
5
|
+
## [v0.1.0] - 2018-05-25
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
- `WebAuthn.credential_creation_options` to initiate registration
|
10
|
+
- `WebAuthn::AuthenticatorAttestationResponse.valid?` can be used to validate fido-u2f attestations returned by the browser
|
11
|
+
|
12
|
+
[Unreleased]: https://github.com/cedarcode/webauthn-ruby/compare/v0.1.0...HEAD/
|
13
|
+
[v0.1.0]: https://github.com/cedarcode/webauthn-ruby/compare/v0.0.0...v0.1.0/
|
data/Gemfile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source "https://rubygems.org"
|
2
4
|
|
3
|
-
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
6
|
|
5
7
|
# Specify your gem's dependencies in webauthn.gemspec
|
6
8
|
gemspec
|
data/README.md
CHANGED
@@ -1,8 +1,31 @@
|
|
1
|
-
#
|
1
|
+
# WebAuthn
|
2
2
|
|
3
|
-
|
3
|
+
Easily implement WebAuthn in your ruby web server
|
4
4
|
|
5
|
-
|
5
|
+
[![Gem](https://img.shields.io/gem/v/webauthn.svg?style=flat-square)](https://rubygems.org/gems/webauthn)
|
6
|
+
[![Travis](https://img.shields.io/travis/cedarcode/webauthn-ruby.svg?style=flat-square)](https://travis-ci.org/cedarcode/webauthn-ruby)
|
7
|
+
|
8
|
+
## WARNING: This gem is in the early development phase. Use on production at your own risk.
|
9
|
+
|
10
|
+
## What is WebAuthn?
|
11
|
+
|
12
|
+
- [WebAuthn article with Google IO 2018 talk](https://developers.google.com/web/updates/2018/05/webauthn)
|
13
|
+
- [Web Authentication API draft article by Mozilla](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)
|
14
|
+
- [W3C Draft Recommendation](https://w3c.github.io/webauthn/)
|
15
|
+
|
16
|
+
## Prerequisites
|
17
|
+
|
18
|
+
### User Agent compatibility
|
19
|
+
|
20
|
+
So far, the only browser that have web authentication support are:
|
21
|
+
- Mozilla Firefox Quantum 60+ (Enabled by default).
|
22
|
+
- Google Chrome 65+ (Disabled by default, go to chrome://flags to enable Web Authentication API feature). Note: it is enabled by default in 67+ as stated [here](https://www.chromestatus.com/feature/5669923372138496).
|
23
|
+
|
24
|
+
### Authenticator devices
|
25
|
+
|
26
|
+
These [USB keys from Yubico](https://www.yubico.com/product/security-key-by-yubico/) were used as authenticator devices during the development of this gem.
|
27
|
+
Firefox states ([Firefox 60 release notes](https://www.mozilla.org/en-US/firefox/60.0/releasenotes/)) they only support USB FIDO2 or FIDO U2F enabled devices in their current implementation (version 60).
|
28
|
+
It's up to the gem's user to verify user agent compatibility if any other devise wants to be used as the authenticator component.
|
6
29
|
|
7
30
|
## Installation
|
8
31
|
|
@@ -22,17 +45,65 @@ Or install it yourself as:
|
|
22
45
|
|
23
46
|
## Usage
|
24
47
|
|
25
|
-
|
48
|
+
### Registration
|
49
|
+
|
50
|
+
#### Initiation phase
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
credential_creation_options = WebAuthn.credential_creation_options
|
54
|
+
|
55
|
+
# Store the newly generated challenge somewhere so you can have it
|
56
|
+
# for the validation phase.
|
57
|
+
#
|
58
|
+
# You can read it from the resulting options:
|
59
|
+
credential_creation_options[:challenge]
|
60
|
+
|
61
|
+
# Send `credential_creation_options` to the browser, so that they can be used
|
62
|
+
# to call `navigator.credentials.create({ "publicKey": credentialCreationOptions })`
|
63
|
+
```
|
64
|
+
|
65
|
+
#### Validation phase
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
attestation_object = "..." # As came from the browser
|
69
|
+
client_data_json = "..." # As came from the browser
|
70
|
+
|
71
|
+
attestation_response = WebAuthn::AuthenticatorAttestationResponse.new(
|
72
|
+
attestation_object: attestation_object,
|
73
|
+
client_data_json: client_data_json
|
74
|
+
)
|
75
|
+
|
76
|
+
# This value needs to match `window.location.origin` evaluated by
|
77
|
+
# the User Agent as part of the validation phase.
|
78
|
+
original_origin = "https://www.example.com"
|
79
|
+
|
80
|
+
if attestation_response.valid?(original_challenge, original_origin)
|
81
|
+
# Register the new user along with it's new `credential_id` for future authentications
|
82
|
+
# Access the new user credential by invoking `attestation_response.credential_id`
|
83
|
+
else
|
84
|
+
# Handle error
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
### Authentication
|
89
|
+
|
90
|
+
#### Initiation phase
|
91
|
+
|
92
|
+
*Currently under development*
|
93
|
+
|
94
|
+
#### Validation phase
|
95
|
+
|
96
|
+
*Currently under development*
|
26
97
|
|
27
98
|
## Development
|
28
99
|
|
29
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake
|
100
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests and code-style checks. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
101
|
|
31
102
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
103
|
|
33
104
|
## Contributing
|
34
105
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
106
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/cedarcode/webauthn-ruby.
|
36
107
|
|
37
108
|
## License
|
38
109
|
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
data/lib/webauthn.rb
CHANGED
@@ -1,5 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webauthn/authenticator_attestation_response"
|
1
4
|
require "webauthn/version"
|
2
5
|
|
3
|
-
|
4
|
-
|
6
|
+
require "securerandom"
|
7
|
+
require "base64"
|
8
|
+
require "json"
|
9
|
+
|
10
|
+
module WebAuthn
|
11
|
+
ES256_ALGORITHM = { type: "public-key", alg: -7 }.freeze
|
12
|
+
RP_NAME = "web-server"
|
13
|
+
USER_ID = "1"
|
14
|
+
USER_NAME = "web-user"
|
15
|
+
CREATE_TYPE = "webauthn.create"
|
16
|
+
|
17
|
+
def self.credential_creation_options
|
18
|
+
{
|
19
|
+
challenge: Base64.urlsafe_encode64(SecureRandom.random_bytes(16)),
|
20
|
+
pubKeyCredParams: [ES256_ALGORITHM],
|
21
|
+
rp: { name: RP_NAME },
|
22
|
+
user: { name: USER_NAME, displayName: USER_NAME, id: Base64.urlsafe_encode64(USER_ID) }
|
23
|
+
}
|
24
|
+
end
|
5
25
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WebAuthn
|
4
|
+
module AttestationStatement
|
5
|
+
ATTESTATION_FORMAT_NONE = "none"
|
6
|
+
ATTESTATION_FORMAT_FIDO_U2F = "fido-u2f"
|
7
|
+
|
8
|
+
def self.from(format, statement)
|
9
|
+
case format
|
10
|
+
when ATTESTATION_FORMAT_NONE
|
11
|
+
require "webauthn/attestation_statement/none"
|
12
|
+
WebAuthn::AttestationStatement::None.new(statement)
|
13
|
+
when ATTESTATION_FORMAT_FIDO_U2F
|
14
|
+
require "webauthn/attestation_statement/fido_u2f"
|
15
|
+
WebAuthn::AttestationStatement::FidoU2f.new(statement)
|
16
|
+
else
|
17
|
+
raise "Unsupported attestation format '#{attestation_format}'"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WebAuthn
|
4
|
+
module AttestationStatement
|
5
|
+
class Base
|
6
|
+
def initialize(statement)
|
7
|
+
@statement = statement
|
8
|
+
end
|
9
|
+
|
10
|
+
def valid?(*args)
|
11
|
+
raise NotImpelementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :statement
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
require "webauthn/attestation_statement/base"
|
5
|
+
|
6
|
+
module WebAuthn
|
7
|
+
module AttestationStatement
|
8
|
+
class FidoU2f < Base
|
9
|
+
VALID_ATTESTATION_CERTIFICATE_COUNT = 1
|
10
|
+
|
11
|
+
def valid?(authenticator_data, client_data_hash)
|
12
|
+
valid_format? &&
|
13
|
+
valid_certificate_public_key? &&
|
14
|
+
valid_signature?(authenticator_data, client_data_hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def signature
|
20
|
+
statement["sig"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid_format?
|
24
|
+
!!(raw_attestation_certificates && signature) &&
|
25
|
+
raw_attestation_certificates.length == VALID_ATTESTATION_CERTIFICATE_COUNT
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid_certificate_public_key?
|
29
|
+
certificate_public_key.is_a?(OpenSSL::PKey::EC) && certificate_public_key.check_key
|
30
|
+
end
|
31
|
+
|
32
|
+
def certificate_public_key
|
33
|
+
attestation_certificate.public_key
|
34
|
+
end
|
35
|
+
|
36
|
+
def attestation_certificate
|
37
|
+
@attestation_certificate ||= OpenSSL::X509::Certificate.new(raw_attestation_certificates[0])
|
38
|
+
end
|
39
|
+
|
40
|
+
def raw_attestation_certificates
|
41
|
+
statement["x5c"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid_signature?(authenticator_data, client_data_hash)
|
45
|
+
certificate_public_key.verify(
|
46
|
+
"SHA256",
|
47
|
+
signature,
|
48
|
+
verification_data(authenticator_data, client_data_hash)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def verification_data(authenticator_data, client_data_hash)
|
53
|
+
"\x00" +
|
54
|
+
authenticator_data.rp_id_hash +
|
55
|
+
client_data_hash +
|
56
|
+
authenticator_data.credential_id +
|
57
|
+
authenticator_data.credential_public_key
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cbor"
|
4
|
+
require "uri"
|
5
|
+
require "openssl"
|
6
|
+
|
7
|
+
require "webauthn/authenticator_data"
|
8
|
+
require "webauthn/attestation_statement"
|
9
|
+
require "webauthn/client_data"
|
10
|
+
|
11
|
+
module WebAuthn
|
12
|
+
class AuthenticatorAttestationResponse
|
13
|
+
def initialize(attestation_object:, client_data_json:)
|
14
|
+
@attestation_object = attestation_object
|
15
|
+
@client_data_json = client_data_json
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?(original_challenge, original_origin)
|
19
|
+
valid_type? &&
|
20
|
+
valid_challenge?(original_challenge) &&
|
21
|
+
valid_origin?(original_origin) &&
|
22
|
+
valid_rp_id?(original_origin) &&
|
23
|
+
authenticator_data.valid? &&
|
24
|
+
user_present? &&
|
25
|
+
attestation_statement.valid?(authenticator_data, client_data.hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
def credential_id
|
29
|
+
authenticator_data.credential_id
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :attestation_object, :client_data_json
|
35
|
+
|
36
|
+
def valid_type?
|
37
|
+
client_data.type == CREATE_TYPE
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_challenge?(original_challenge)
|
41
|
+
Base64.urlsafe_decode64(client_data.challenge) == Base64.urlsafe_decode64(original_challenge)
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid_origin?(original_origin)
|
45
|
+
client_data.origin == original_origin
|
46
|
+
end
|
47
|
+
|
48
|
+
def attestation_statement
|
49
|
+
@attestation_statement ||=
|
50
|
+
WebAuthn::AttestationStatement.from(attestation["fmt"], attestation["attStmt"])
|
51
|
+
end
|
52
|
+
|
53
|
+
def valid_rp_id?(original_origin)
|
54
|
+
domain = URI.parse(original_origin).host
|
55
|
+
|
56
|
+
OpenSSL::Digest::SHA256.digest(domain) == authenticator_data.rp_id_hash
|
57
|
+
end
|
58
|
+
|
59
|
+
def user_present?
|
60
|
+
authenticator_data.user_present?
|
61
|
+
end
|
62
|
+
|
63
|
+
def client_data
|
64
|
+
@client_data ||= WebAuthn::ClientData.new(client_data_json)
|
65
|
+
end
|
66
|
+
|
67
|
+
def authenticator_data
|
68
|
+
@authenticator_data ||= WebAuthn::AuthenticatorData.new(attestation["authData"])
|
69
|
+
end
|
70
|
+
|
71
|
+
def attestation_format
|
72
|
+
attestation["fmt"]
|
73
|
+
end
|
74
|
+
|
75
|
+
def attestation
|
76
|
+
@attestation ||= CBOR.decode(Base64.urlsafe_decode64(attestation_object))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webauthn/authenticator_data/attested_credential_data"
|
4
|
+
|
5
|
+
module WebAuthn
|
6
|
+
class AuthenticatorData
|
7
|
+
RP_ID_HASH_POSITION = 0
|
8
|
+
|
9
|
+
RP_ID_HASH_LENGTH = 32
|
10
|
+
FLAGS_LENGTH = 1
|
11
|
+
SIGN_COUNT_LENGTH = 4
|
12
|
+
|
13
|
+
USER_PRESENT_FLAG_POSITION = 0
|
14
|
+
ATTESTED_CREDENTIAL_DATA_INCLUDED_FLAG_POSITION = 6
|
15
|
+
|
16
|
+
def initialize(data)
|
17
|
+
@data = data
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid?
|
21
|
+
if attested_credential_data_included?
|
22
|
+
data.length > base_length && attested_credential_data.valid?
|
23
|
+
else
|
24
|
+
data.length == base_length
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def user_present?
|
29
|
+
flags[USER_PRESENT_FLAG_POSITION] == "1"
|
30
|
+
end
|
31
|
+
|
32
|
+
def rp_id_hash
|
33
|
+
@rp_id_hash ||=
|
34
|
+
if valid?
|
35
|
+
data_at(RP_ID_HASH_POSITION, RP_ID_HASH_LENGTH)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def credential_id
|
40
|
+
@credential_id ||= attested_credential_data.id
|
41
|
+
end
|
42
|
+
|
43
|
+
def credential_public_key
|
44
|
+
@credential_public_key ||= attested_credential_data.public_key
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :data
|
50
|
+
|
51
|
+
def attested_credential_data
|
52
|
+
@attested_credential_data ||=
|
53
|
+
AttestedCredentialData.new(data_at(attested_credential_data_position))
|
54
|
+
end
|
55
|
+
|
56
|
+
def attested_credential_data_position
|
57
|
+
base_length
|
58
|
+
end
|
59
|
+
|
60
|
+
def base_length
|
61
|
+
RP_ID_HASH_LENGTH + FLAGS_LENGTH + SIGN_COUNT_LENGTH
|
62
|
+
end
|
63
|
+
|
64
|
+
def flags
|
65
|
+
@flags ||= data_at(flags_position, FLAGS_LENGTH).unpack("b*").first
|
66
|
+
end
|
67
|
+
|
68
|
+
def flags_position
|
69
|
+
RP_ID_HASH_LENGTH
|
70
|
+
end
|
71
|
+
|
72
|
+
def attested_credential_data_included?
|
73
|
+
flags[ATTESTED_CREDENTIAL_DATA_INCLUDED_FLAG_POSITION] == "1"
|
74
|
+
end
|
75
|
+
|
76
|
+
def data_at(position, length = nil)
|
77
|
+
length ||= data.size - position
|
78
|
+
|
79
|
+
data[position..(position + length - 1)]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webauthn/authenticator_data/attested_credential_data/public_key_u2f"
|
4
|
+
|
5
|
+
module WebAuthn
|
6
|
+
class AuthenticatorData
|
7
|
+
class AttestedCredentialData
|
8
|
+
AAGUID_LENGTH = 16
|
9
|
+
ID_LENGTH_LENGTH = 2
|
10
|
+
|
11
|
+
UINT16_BIG_ENDIAN_FORMAT = "n*"
|
12
|
+
|
13
|
+
def initialize(data)
|
14
|
+
@data = data
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid?
|
18
|
+
data.length >= AAGUID_LENGTH + ID_LENGTH_LENGTH && public_key.valid?
|
19
|
+
end
|
20
|
+
|
21
|
+
def id
|
22
|
+
if valid?
|
23
|
+
data_at(id_position, id_length)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def public_key
|
28
|
+
@public_key ||= PublicKeyU2f.new(data_at(public_key_position, public_key_length))
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :data
|
34
|
+
|
35
|
+
def id_position
|
36
|
+
id_length_position + ID_LENGTH_LENGTH
|
37
|
+
end
|
38
|
+
|
39
|
+
def id_length
|
40
|
+
@id_length ||=
|
41
|
+
data_at(id_length_position, ID_LENGTH_LENGTH)
|
42
|
+
.unpack(UINT16_BIG_ENDIAN_FORMAT)
|
43
|
+
.first
|
44
|
+
end
|
45
|
+
|
46
|
+
def id_length_position
|
47
|
+
AAGUID_LENGTH
|
48
|
+
end
|
49
|
+
|
50
|
+
def public_key_position
|
51
|
+
id_position + id_length
|
52
|
+
end
|
53
|
+
|
54
|
+
def public_key_length
|
55
|
+
data.size - (AAGUID_LENGTH + ID_LENGTH_LENGTH + id_length)
|
56
|
+
end
|
57
|
+
|
58
|
+
def data_at(position, length = nil)
|
59
|
+
length ||= data.size - position
|
60
|
+
|
61
|
+
data[position..(position + length - 1)]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WebAuthn
|
4
|
+
class AuthenticatorData
|
5
|
+
class AttestedCredentialData
|
6
|
+
class PublicKeyU2f
|
7
|
+
COORDINATE_LENGTH = 32
|
8
|
+
X_COORDINATE_KEY = -2
|
9
|
+
Y_COORDINATE_KEY = -3
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
@data = data
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?
|
16
|
+
data.size >= COORDINATE_LENGTH * 2 &&
|
17
|
+
x_coordinate.length == COORDINATE_LENGTH &&
|
18
|
+
y_coordinate.length == COORDINATE_LENGTH
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_str
|
22
|
+
"\x04" + x_coordinate + y_coordinate
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :data
|
28
|
+
|
29
|
+
def x_coordinate
|
30
|
+
decoded_data[X_COORDINATE_KEY]
|
31
|
+
end
|
32
|
+
|
33
|
+
def y_coordinate
|
34
|
+
decoded_data[Y_COORDINATE_KEY]
|
35
|
+
end
|
36
|
+
|
37
|
+
def decoded_data
|
38
|
+
@decoded_data ||= CBOR.decode(data)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
|
5
|
+
module WebAuthn
|
6
|
+
class ClientData
|
7
|
+
def initialize(client_data_json)
|
8
|
+
@client_data_json = client_data_json
|
9
|
+
end
|
10
|
+
|
11
|
+
def type
|
12
|
+
data["type"]
|
13
|
+
end
|
14
|
+
|
15
|
+
def challenge
|
16
|
+
data["challenge"]
|
17
|
+
end
|
18
|
+
|
19
|
+
def origin
|
20
|
+
data["origin"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def hash
|
24
|
+
OpenSSL::Digest::SHA256.digest(decoded_client_data_json)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :client_data_json
|
30
|
+
|
31
|
+
def decoded_client_data_json
|
32
|
+
@decoded_client_data_json ||= Base64.urlsafe_decode64(client_data_json)
|
33
|
+
end
|
34
|
+
|
35
|
+
def data
|
36
|
+
@data ||=
|
37
|
+
begin
|
38
|
+
if client_data_json
|
39
|
+
JSON.parse(decoded_client_data_json)
|
40
|
+
else
|
41
|
+
raise "Missing client_data_json"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/webauthn/version.rb
CHANGED
data/webauthn.gemspec
CHANGED
@@ -1,25 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
lib = File.expand_path("../lib", __FILE__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
5
|
require "webauthn/version"
|
4
6
|
|
5
7
|
Gem::Specification.new do |spec|
|
6
8
|
spec.name = "webauthn"
|
7
|
-
spec.version =
|
8
|
-
spec.authors = ["Gonzalo Rodriguez"]
|
9
|
-
spec.email = ["
|
9
|
+
spec.version = WebAuthn::VERSION
|
10
|
+
spec.authors = ["Gonzalo Rodriguez", "Braulio Martinez"]
|
11
|
+
spec.email = ["gonzalo@cedarcode.com", "braulio@cedarcode.com"]
|
10
12
|
|
11
13
|
spec.summary = %q{Web Authentication API Relying Party in ruby}
|
12
|
-
spec.homepage = "https://github.com/cedarcode/webauthn"
|
14
|
+
spec.homepage = "https://github.com/cedarcode/webauthn-ruby"
|
13
15
|
spec.license = "MIT"
|
14
16
|
|
15
|
-
spec.
|
17
|
+
spec.metadata = {
|
18
|
+
"bug_tracker_uri" => "https://github.com/cedarcode/webauthn-ruby/issues",
|
19
|
+
"changelog_uri" => "https://github.com/cedarcode/webauthn-ruby/blob/master/CHANGELOG.md",
|
20
|
+
"source_code_uri" => "https://github.com/cedarcode/webauthn-ruby"
|
21
|
+
}
|
22
|
+
|
23
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
24
|
f.match(%r{^(test|spec|features)/})
|
17
25
|
end
|
18
26
|
spec.bindir = "exe"
|
19
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
28
|
spec.require_paths = ["lib"]
|
21
29
|
|
30
|
+
spec.add_dependency "cbor", "~> 0.5.9.2"
|
31
|
+
|
22
32
|
spec.add_development_dependency "bundler", "~> 1.16"
|
33
|
+
spec.add_development_dependency "byebug", "~> 10.0"
|
23
34
|
spec.add_development_dependency "rake", "~> 10.0"
|
24
35
|
spec.add_development_dependency "rspec", "~> 3.0"
|
36
|
+
spec.add_development_dependency "rubocop", "0.56.0"
|
25
37
|
end
|
metadata
CHANGED
@@ -1,15 +1,30 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webauthn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gonzalo Rodriguez
|
8
|
+
- Braulio Martinez
|
8
9
|
autorequire:
|
9
10
|
bindir: exe
|
10
11
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
12
|
+
date: 2018-05-25 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: cbor
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.5.9.2
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.5.9.2
|
13
28
|
- !ruby/object:Gem::Dependency
|
14
29
|
name: bundler
|
15
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -24,6 +39,20 @@ dependencies:
|
|
24
39
|
- - "~>"
|
25
40
|
- !ruby/object:Gem::Version
|
26
41
|
version: '1.16'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: byebug
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '10.0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '10.0'
|
27
56
|
- !ruby/object:Gem::Dependency
|
28
57
|
name: rake
|
29
58
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,16 +81,33 @@ dependencies:
|
|
52
81
|
- - "~>"
|
53
82
|
- !ruby/object:Gem::Version
|
54
83
|
version: '3.0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: rubocop
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - '='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 0.56.0
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - '='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: 0.56.0
|
55
98
|
description:
|
56
99
|
email:
|
57
|
-
-
|
100
|
+
- gonzalo@cedarcode.com
|
101
|
+
- braulio@cedarcode.com
|
58
102
|
executables: []
|
59
103
|
extensions: []
|
60
104
|
extra_rdoc_files: []
|
61
105
|
files:
|
62
106
|
- ".gitignore"
|
63
107
|
- ".rspec"
|
108
|
+
- ".rubocop.yml"
|
64
109
|
- ".travis.yml"
|
110
|
+
- CHANGELOG.md
|
65
111
|
- Gemfile
|
66
112
|
- LICENSE.txt
|
67
113
|
- README.md
|
@@ -69,12 +115,24 @@ files:
|
|
69
115
|
- bin/console
|
70
116
|
- bin/setup
|
71
117
|
- lib/webauthn.rb
|
118
|
+
- lib/webauthn/attestation_statement.rb
|
119
|
+
- lib/webauthn/attestation_statement/base.rb
|
120
|
+
- lib/webauthn/attestation_statement/fido_u2f.rb
|
121
|
+
- lib/webauthn/attestation_statement/none.rb
|
122
|
+
- lib/webauthn/authenticator_attestation_response.rb
|
123
|
+
- lib/webauthn/authenticator_data.rb
|
124
|
+
- lib/webauthn/authenticator_data/attested_credential_data.rb
|
125
|
+
- lib/webauthn/authenticator_data/attested_credential_data/public_key_u2f.rb
|
126
|
+
- lib/webauthn/client_data.rb
|
72
127
|
- lib/webauthn/version.rb
|
73
128
|
- webauthn.gemspec
|
74
|
-
homepage: https://github.com/cedarcode/webauthn
|
129
|
+
homepage: https://github.com/cedarcode/webauthn-ruby
|
75
130
|
licenses:
|
76
131
|
- MIT
|
77
|
-
metadata:
|
132
|
+
metadata:
|
133
|
+
bug_tracker_uri: https://github.com/cedarcode/webauthn-ruby/issues
|
134
|
+
changelog_uri: https://github.com/cedarcode/webauthn-ruby/blob/master/CHANGELOG.md
|
135
|
+
source_code_uri: https://github.com/cedarcode/webauthn-ruby
|
78
136
|
post_install_message:
|
79
137
|
rdoc_options: []
|
80
138
|
require_paths:
|