webauthn 0.0.0 → 0.1.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 +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
|
+
[](https://rubygems.org/gems/webauthn)
|
6
|
+
[](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:
|