web_authn 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 +4 -4
- data/VERSION +1 -1
- data/lib/web_authn/attestation_object.rb +33 -0
- data/lib/web_authn/attested_credential_data.rb +49 -0
- data/lib/web_authn/authenticator_data/flags.rb +30 -0
- data/lib/web_authn/authenticator_data.rb +44 -1
- data/lib/web_authn/client_data_json.rb +19 -0
- data/lib/web_authn/context/authentication.rb +54 -0
- data/lib/web_authn/context/registration.rb +23 -0
- data/lib/web_authn/context.rb +43 -0
- data/lib/web_authn.rb +18 -1
- data/samples/authentication_response.rb +46 -0
- data/samples/concept.rb +34 -0
- data/samples/registration_response.rb +37 -0
- data/web_authn.gemspec +4 -2
- metadata +44 -12
- data/bin/console +0 -14
- data/bin/setup +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd0d84bc14a9f6773426c3c509b379268f26b36ee5555fd8c3603a45cc677221
|
|
4
|
+
data.tar.gz: 124bfe151246e6380a59984f95784f6f39671038ddf98aed99dd8d4b8b59233f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 811e1addfca6f94a181bb59dc02fd1bde4ac6c5692e5a1ed5903eb8b7063db5409061ab2880e3425be6f940aac2394d90d1223dea6d4c0a1262560388ba2fa38
|
|
7
|
+
data.tar.gz: faf55e02e28045d0cd1a767170bf20f606a9329bd6c351f8d81ea974e6c78d0e08d4d02bd2e714431cddc3ff3fd0651106325487e8c257da3fecc2072ebcf356
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.1
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module WebAuthn
|
|
2
|
+
class AttestationObject
|
|
3
|
+
attr_accessor :format, :attestation_statement, :authenticator_data
|
|
4
|
+
alias_method :fmt, :format
|
|
5
|
+
alias_method :att_stmt, :attestation_statement
|
|
6
|
+
alias_method :auth_data, :authenticator_data
|
|
7
|
+
|
|
8
|
+
%i(credential_id rp_id_hash flags public_key sign_count).each do |method|
|
|
9
|
+
delegate method, to: :authenticator_data
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(attrs)
|
|
13
|
+
self.format = attrs[:fmt]
|
|
14
|
+
self.attestation_statement = case format
|
|
15
|
+
when 'none'
|
|
16
|
+
nil
|
|
17
|
+
when 'packed', 'tpm', 'android-key', 'android-safetynet', 'fido-u2f'
|
|
18
|
+
raise "Unsupported Attestation Format: #{attestation_object[:fmt]}"
|
|
19
|
+
else
|
|
20
|
+
raise 'Unknown Attestation Format'
|
|
21
|
+
end
|
|
22
|
+
self.authenticator_data = AuthenticatorData.decode attrs[:authData]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
def decode(encoded_attestation_object)
|
|
27
|
+
new CBOR.decode(
|
|
28
|
+
Base64.urlsafe_decode64 encoded_attestation_object
|
|
29
|
+
).with_indifferent_access
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module WebAuthn
|
|
2
|
+
class AttestedCredentialData
|
|
3
|
+
attr_accessor :aaguid, :credential_id, :public_key
|
|
4
|
+
|
|
5
|
+
def initialize(aaguid:, credential_id:, public_key:)
|
|
6
|
+
self.aaguid = aaguid
|
|
7
|
+
self.credential_id = credential_id
|
|
8
|
+
self.public_key = public_key
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def decode(attested_credential_data)
|
|
13
|
+
length = (
|
|
14
|
+
((attested_credential_data.getbyte(16) << 8) & 0xFF) +
|
|
15
|
+
(attested_credential_data.getbyte(17) & 0xFF)
|
|
16
|
+
)
|
|
17
|
+
aaguid,
|
|
18
|
+
credential_id,
|
|
19
|
+
_encoded_cose_key_ = [
|
|
20
|
+
attested_credential_data.byteslice(0...16),
|
|
21
|
+
attested_credential_data.byteslice(18...(18 + length)),
|
|
22
|
+
attested_credential_data.byteslice((18 + length)..-1),
|
|
23
|
+
]
|
|
24
|
+
cose_key = COSE::Key::EC2.from_cbor(_encoded_cose_key_)
|
|
25
|
+
crv = case cose_key.curve
|
|
26
|
+
when 1
|
|
27
|
+
:'P-256'
|
|
28
|
+
when 2
|
|
29
|
+
:'P-384'
|
|
30
|
+
when 3
|
|
31
|
+
:'P-521'
|
|
32
|
+
else
|
|
33
|
+
raise 'Non-supported EC curve'
|
|
34
|
+
end
|
|
35
|
+
jwk = JSON::JWK.new(
|
|
36
|
+
kty: :EC,
|
|
37
|
+
crv: crv,
|
|
38
|
+
x: Base64.urlsafe_encode64(cose_key.x_coordinate, padding: false),
|
|
39
|
+
y: Base64.urlsafe_encode64(cose_key.y_coordinate, padding: false),
|
|
40
|
+
)
|
|
41
|
+
new(
|
|
42
|
+
aaguid: Base64.urlsafe_encode64(aaguid, padding: false),
|
|
43
|
+
credential_id: Base64.urlsafe_encode64(credential_id, padding: false),
|
|
44
|
+
public_key: jwk.to_key
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module WebAuthn
|
|
2
|
+
class AuthenticatorData
|
|
3
|
+
class Flags
|
|
4
|
+
_flags_ = [:up, :uv, :at, :ex]
|
|
5
|
+
attr_accessor *_flags_
|
|
6
|
+
_flags_.each do |flag|
|
|
7
|
+
alias_method :"#{flag}?", flag
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(up:, uv:, at:, ex:)
|
|
11
|
+
self.up = up
|
|
12
|
+
self.uv = uv
|
|
13
|
+
self.at = at
|
|
14
|
+
self.ex = ex
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def decode(input)
|
|
19
|
+
bit_array = input.getbyte(0)
|
|
20
|
+
new(
|
|
21
|
+
up: bit_array[0] == 1,
|
|
22
|
+
uv: bit_array[2] == 1,
|
|
23
|
+
at: bit_array[6] == 1,
|
|
24
|
+
ex: bit_array[7] == 1
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -1,4 +1,47 @@
|
|
|
1
1
|
module WebAuthn
|
|
2
|
-
class
|
|
2
|
+
class AuthenticatorData
|
|
3
|
+
attr_accessor :rp_id_hash, :flags, :sign_count, :attested_credential_data
|
|
4
|
+
|
|
5
|
+
%i(credential_id public_key).each do |method|
|
|
6
|
+
delegate method, to: :attested_credential_data, allow_nil: true
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize(rp_id_hash:, flags:, sign_count:, attested_credential_data: nil)
|
|
10
|
+
self.rp_id_hash = rp_id_hash
|
|
11
|
+
self.flags = flags
|
|
12
|
+
self.sign_count = sign_count
|
|
13
|
+
self.attested_credential_data = attested_credential_data
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def decode(auth_data)
|
|
18
|
+
rp_id_hash,
|
|
19
|
+
_flags_,
|
|
20
|
+
sign_count = [
|
|
21
|
+
auth_data.byteslice(0...32),
|
|
22
|
+
auth_data.byteslice(32),
|
|
23
|
+
auth_data.byteslice(33...37)
|
|
24
|
+
]
|
|
25
|
+
flags = Flags.decode(_flags_)
|
|
26
|
+
attested_credential_data = if flags.at?
|
|
27
|
+
if flags.ex?
|
|
28
|
+
raise 'Extension Data Not Supported Yet'
|
|
29
|
+
else
|
|
30
|
+
AttestedCredentialData.decode auth_data.byteslice(37..-1)
|
|
31
|
+
end
|
|
32
|
+
else
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
new(
|
|
37
|
+
rp_id_hash: Base64.urlsafe_encode64(rp_id_hash, padding: false),
|
|
38
|
+
flags: flags,
|
|
39
|
+
sign_count: sign_count.unpack('N1').first,
|
|
40
|
+
attested_credential_data: attested_credential_data
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
3
44
|
end
|
|
4
45
|
end
|
|
46
|
+
|
|
47
|
+
require 'web_authn/authenticator_data/flags'
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
module WebAuthn
|
|
2
2
|
class ClientDataJSON
|
|
3
|
+
attr_accessor :type, :origin, :challenge, :raw
|
|
4
|
+
|
|
5
|
+
def initialize(attrs = {})
|
|
6
|
+
self.type = attrs[:type]
|
|
7
|
+
self.origin = attrs[:origin]
|
|
8
|
+
self.challenge = attrs[:challenge]
|
|
9
|
+
self.raw = attrs[:raw]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def decode(encoded_client_data_json)
|
|
14
|
+
raw_client_data_json = Base64.urlsafe_decode64 encoded_client_data_json
|
|
15
|
+
new JSON.parse(
|
|
16
|
+
raw_client_data_json
|
|
17
|
+
).merge(
|
|
18
|
+
raw: raw_client_data_json
|
|
19
|
+
).with_indifferent_access
|
|
20
|
+
end
|
|
21
|
+
end
|
|
3
22
|
end
|
|
4
23
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module WebAuthn
|
|
2
|
+
class Context
|
|
3
|
+
class Authentication < Context
|
|
4
|
+
attr_accessor :authenticator_data
|
|
5
|
+
|
|
6
|
+
# TODO: will need more methods, or let developers access deep methods by themselves.
|
|
7
|
+
%i(rp_id_hash flags sign_count).each do |method|
|
|
8
|
+
delegate method, to: :authenticator_data
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def authentication?
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def verify!(encoded_authenticator_data, public_key:, sign_count:, signature:)
|
|
16
|
+
raw_authenticator_data = Base64.urlsafe_decode64 encoded_authenticator_data
|
|
17
|
+
self.authenticator_data = AuthenticatorData.decode(
|
|
18
|
+
raw_authenticator_data
|
|
19
|
+
)
|
|
20
|
+
verify_sign_count!(sign_count, authenticator_data.sign_count)
|
|
21
|
+
verify_signature!(raw_authenticator_data, client_data_json.raw, public_key, signature)
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def verify_sign_count!(before, current)
|
|
28
|
+
if before == 0 && current == 0
|
|
29
|
+
self # NOTE: no counter supported on the authenticator
|
|
30
|
+
elsif before < current
|
|
31
|
+
self
|
|
32
|
+
else
|
|
33
|
+
raise 'Invalid Sign Count'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def verify_signature!(raw_authenticator_data, raw_client_data_json, public_key, signature)
|
|
38
|
+
signature_base_string = [
|
|
39
|
+
raw_authenticator_data,
|
|
40
|
+
OpenSSL::Digest::SHA256.digest(raw_client_data_json)
|
|
41
|
+
].join
|
|
42
|
+
result = public_key.dsa_verify_asn1(
|
|
43
|
+
OpenSSL::Digest::SHA256.digest(signature_base_string),
|
|
44
|
+
Base64.urlsafe_decode64(signature)
|
|
45
|
+
)
|
|
46
|
+
if result
|
|
47
|
+
self
|
|
48
|
+
else
|
|
49
|
+
raise 'Invalid Signature'
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module WebAuthn
|
|
2
|
+
class Context
|
|
3
|
+
class Registration < Context
|
|
4
|
+
attr_accessor :attestation_object
|
|
5
|
+
|
|
6
|
+
# TODO: will need more methods, or let developers access deep methods by themselves.
|
|
7
|
+
%i(credential_id rp_id_hash flags public_key sign_count).each do |method|
|
|
8
|
+
delegate method, to: :attestation_object
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def registration?
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def verify!(encoded_attestation_object)
|
|
16
|
+
self.attestation_object = AttestationObject.decode(
|
|
17
|
+
encoded_attestation_object
|
|
18
|
+
)
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module WebAuthn
|
|
2
|
+
class Context
|
|
3
|
+
attr_accessor :client_data_json
|
|
4
|
+
|
|
5
|
+
def initialize(client_data_json)
|
|
6
|
+
self.client_data_json = client_data_json
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def verify_session!(origin:, challenge:)
|
|
10
|
+
raise 'Invalid Client Data JSON Origin' unless client_data_json.origin == origin
|
|
11
|
+
raise 'Invalid Client Data JSON Session' unless client_data_json.challenge == challenge
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def registration?
|
|
16
|
+
false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def authentication?
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
def for(encoded_client_data_json, origin:, challenge:)
|
|
25
|
+
client_data_json = ClientDataJSON.decode encoded_client_data_json
|
|
26
|
+
|
|
27
|
+
context = case client_data_json.type
|
|
28
|
+
when 'webauthn.create'
|
|
29
|
+
Registration.new(client_data_json)
|
|
30
|
+
when 'webauthn.get'
|
|
31
|
+
Authentication.new(client_data_json)
|
|
32
|
+
else
|
|
33
|
+
raise 'Unknown Client Data JSON Type'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context.verify_session!(origin: origin, challenge: challenge)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
require 'web_authn/context/authentication'
|
|
43
|
+
require 'web_authn/context/registration'
|
data/lib/web_authn.rb
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
|
+
require 'active_support'
|
|
2
|
+
require 'active_support/core_ext'
|
|
3
|
+
require 'cbor'
|
|
4
|
+
require 'cose'
|
|
5
|
+
require 'cose/key/ec2'
|
|
6
|
+
require 'json/jwt'
|
|
7
|
+
|
|
1
8
|
module WebAuthn
|
|
2
|
-
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def context_for(encoded_client_data_json, origin:, challenge:)
|
|
12
|
+
Context.for(
|
|
13
|
+
encoded_client_data_json,
|
|
14
|
+
origin: origin,
|
|
15
|
+
challenge: challenge
|
|
16
|
+
)
|
|
17
|
+
end
|
|
3
18
|
end
|
|
4
19
|
|
|
5
20
|
require 'web_authn/attestation_object'
|
|
21
|
+
require 'web_authn/attested_credential_data'
|
|
6
22
|
require 'web_authn/authenticator_data'
|
|
7
23
|
require 'web_authn/client_data_json'
|
|
24
|
+
require 'web_authn/context'
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'web_authn'
|
|
2
|
+
|
|
3
|
+
authenticator_data = 'MsuA3KzDw1JGLLAfO_4wLebzcS8w_SDs0Zw7pbhYlJUBAAAAOw'
|
|
4
|
+
|
|
5
|
+
signature = 'MEUCIQDXp8Wqzz3ZYV7avKvH3R3XQhW7xPYb5Cq2nx3gpflDGwIgPN0tSy2mmgpI06IIKmjrIUxCvL4Rfc53mFXfVd_yL58'
|
|
6
|
+
sign_count = 0
|
|
7
|
+
|
|
8
|
+
client_data_json = 'eyJjaGFsbGVuZ2UiOiJjbUZ1Wkc5dExYTjBjbWx1WnkxblpXNWxjbUYwWldRdFlua3RjbkF0YzJWeWRtVnkiLCJvcmlnaW4iOiJodHRwczovL3dlYi1hdXRobi5zZWxmLWlzc3VlZC5hcHAiLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0'
|
|
9
|
+
|
|
10
|
+
origin = 'https://web-authn.self-issued.app'
|
|
11
|
+
challenge = 'cmFuZG9tLXN0cmluZy1nZW5lcmF0ZWQtYnktcnAtc2VydmVy'
|
|
12
|
+
|
|
13
|
+
public_key = OpenSSL::PKey::EC.new <<-PEM
|
|
14
|
+
-----BEGIN PUBLIC KEY-----
|
|
15
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMpNU/8TjYoyN8FlhZ+YsOMAvyfQ4
|
|
16
|
+
i6/JN0/DPXuZMoxLvdb1vjh7vPUt2Osw3Bq+0NZsx3U/8kmpFuwsZhTi9A==
|
|
17
|
+
-----END PUBLIC KEY-----
|
|
18
|
+
PEM
|
|
19
|
+
|
|
20
|
+
context = WebAuthn.context_for(
|
|
21
|
+
client_data_json,
|
|
22
|
+
origin: origin,
|
|
23
|
+
challenge: challenge,
|
|
24
|
+
)
|
|
25
|
+
raise unless context.authentication?
|
|
26
|
+
|
|
27
|
+
context.verify!(
|
|
28
|
+
authenticator_data,
|
|
29
|
+
public_key: public_key,
|
|
30
|
+
sign_count: sign_count,
|
|
31
|
+
signature: signature
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
puts <<-OUT
|
|
35
|
+
# RP ID Hash
|
|
36
|
+
#{context.rp_id_hash}
|
|
37
|
+
|
|
38
|
+
# Flags
|
|
39
|
+
up: #{context.flags.up}
|
|
40
|
+
uv: #{context.flags.uv}
|
|
41
|
+
at: #{context.flags.at}
|
|
42
|
+
ex: #{context.flags.ex}
|
|
43
|
+
|
|
44
|
+
# Sign Count
|
|
45
|
+
#{context.sign_count}
|
|
46
|
+
OUT
|
data/samples/concept.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Common
|
|
2
|
+
context = WebAuthn.context_for(
|
|
3
|
+
client_data_json,
|
|
4
|
+
origin: request.base_url,
|
|
5
|
+
challenge: session[:challenge],
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
# Registration
|
|
9
|
+
raise unless context.registration?
|
|
10
|
+
|
|
11
|
+
context.verify!(params[:attestation_object])
|
|
12
|
+
current_account.fido_authenticators.create(
|
|
13
|
+
credential_id: context.credential_id,
|
|
14
|
+
public_key: context.public_key.to_pem,
|
|
15
|
+
sign_count: context.sign_count
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Authentication
|
|
19
|
+
raise unless context.authentication?
|
|
20
|
+
|
|
21
|
+
fido_authentiator = FIDO::Authenticatior.find_by(credential_id: params[:credential_id])
|
|
22
|
+
raise unless fido_authentiator.present?
|
|
23
|
+
|
|
24
|
+
context.verify!(
|
|
25
|
+
authenticator_data,
|
|
26
|
+
public_key: fido_authentiator.public_key,
|
|
27
|
+
sign_count: fido_authentiator.sign_count,
|
|
28
|
+
signature: params[:signature]
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
fido_authentiator.update!(
|
|
32
|
+
sign_count: context.sign_count
|
|
33
|
+
)
|
|
34
|
+
authenticate authenticator.user
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'web_authn'
|
|
2
|
+
|
|
3
|
+
attestation_object = 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEMsuA3KzDw1JGLLAfO_4wLebzcS8w_SDs0Zw7pbhYlJVBAAAAMAAAAAAAAAAAAAAAAAAAAAAAQM1zXqvmYeVH9o2q1YcBZDSlkhvVs_2RjnKESVUktkQwQnYcU8jdo-duNLKrIOZNg0g4RCm0UMDZxtdXhR2bCu2lAQIDJiABIVggDMGhDLXoZit2uSMLyL-_emlFrGzlH7b2KpKpgYNzPRYiWCAl795OxcS2QimEnC9Jl_pNG3Gy_9O6m3_GbZdGsk90aw'
|
|
4
|
+
|
|
5
|
+
client_data_json = 'eyJjaGFsbGVuZ2UiOiJjbUZ1Wkc5dExYTjBjbWx1WnkxblpXNWxjbUYwWldRdFlua3RjbkF0YzJWeWRtVnkiLCJuZXdfa2V5c19tYXlfYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViLWF1dGhuLnNlbGYtaXNzdWVkLmFwcCIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ'
|
|
6
|
+
|
|
7
|
+
origin = 'https://web-authn.self-issued.app'
|
|
8
|
+
challenge = 'cmFuZG9tLXN0cmluZy1nZW5lcmF0ZWQtYnktcnAtc2VydmVy'
|
|
9
|
+
|
|
10
|
+
context = WebAuthn.context_for(
|
|
11
|
+
client_data_json,
|
|
12
|
+
origin: origin,
|
|
13
|
+
challenge: challenge
|
|
14
|
+
)
|
|
15
|
+
raise unless context.registration?
|
|
16
|
+
|
|
17
|
+
context.verify! attestation_object
|
|
18
|
+
|
|
19
|
+
puts <<-OUT
|
|
20
|
+
# RP ID Hash
|
|
21
|
+
#{context.rp_id_hash}
|
|
22
|
+
|
|
23
|
+
# Flags
|
|
24
|
+
up: #{context.flags.up}
|
|
25
|
+
uv: #{context.flags.uv}
|
|
26
|
+
at: #{context.flags.at}
|
|
27
|
+
ex: #{context.flags.ex}
|
|
28
|
+
|
|
29
|
+
# Credential ID
|
|
30
|
+
#{context.credential_id}
|
|
31
|
+
|
|
32
|
+
# Public Key
|
|
33
|
+
#{context.public_key.to_pem}
|
|
34
|
+
|
|
35
|
+
# Sign Count
|
|
36
|
+
#{context.sign_count}
|
|
37
|
+
OUT
|
data/web_authn.gemspec
CHANGED
|
@@ -12,8 +12,10 @@ Gem::Specification.new do |gem|
|
|
|
12
12
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
13
13
|
gem.require_paths = ['lib']
|
|
14
14
|
gem.required_ruby_version = '>= 2.3'
|
|
15
|
-
gem.add_runtime_dependency '
|
|
16
|
-
gem.add_runtime_dependency 'cbor'
|
|
15
|
+
gem.add_runtime_dependency 'activesupport'
|
|
16
|
+
gem.add_runtime_dependency 'cbor'
|
|
17
|
+
gem.add_runtime_dependency 'cose'
|
|
18
|
+
gem.add_runtime_dependency 'json-jwt'
|
|
17
19
|
gem.add_development_dependency 'rake', '~> 10.0'
|
|
18
20
|
gem.add_development_dependency 'simplecov'
|
|
19
21
|
gem.add_development_dependency 'rspec'
|
metadata
CHANGED
|
@@ -1,43 +1,71 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: web_authn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- nov matake
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2018-
|
|
11
|
+
date: 2018-09-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: activesupport
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
19
|
+
version: '0'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
26
|
+
version: '0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: cbor
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 0
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: cose
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: json-jwt
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
34
62
|
type: :runtime
|
|
35
63
|
prerelease: false
|
|
36
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
65
|
requirements:
|
|
38
66
|
- - ">="
|
|
39
67
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 0
|
|
68
|
+
version: '0'
|
|
41
69
|
- !ruby/object:Gem::Dependency
|
|
42
70
|
name: rake
|
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -97,9 +125,7 @@ dependencies:
|
|
|
97
125
|
description: W3C WebAuthn (a.k.a. FIDO2) RP library in Ruby
|
|
98
126
|
email:
|
|
99
127
|
- nov@matake.jp
|
|
100
|
-
executables:
|
|
101
|
-
- console
|
|
102
|
-
- setup
|
|
128
|
+
executables: []
|
|
103
129
|
extensions: []
|
|
104
130
|
extra_rdoc_files: []
|
|
105
131
|
files:
|
|
@@ -111,12 +137,18 @@ files:
|
|
|
111
137
|
- README.md
|
|
112
138
|
- Rakefile
|
|
113
139
|
- VERSION
|
|
114
|
-
- bin/console
|
|
115
|
-
- bin/setup
|
|
116
140
|
- lib/web_authn.rb
|
|
117
141
|
- lib/web_authn/attestation_object.rb
|
|
142
|
+
- lib/web_authn/attested_credential_data.rb
|
|
118
143
|
- lib/web_authn/authenticator_data.rb
|
|
144
|
+
- lib/web_authn/authenticator_data/flags.rb
|
|
119
145
|
- lib/web_authn/client_data_json.rb
|
|
146
|
+
- lib/web_authn/context.rb
|
|
147
|
+
- lib/web_authn/context/authentication.rb
|
|
148
|
+
- lib/web_authn/context/registration.rb
|
|
149
|
+
- samples/authentication_response.rb
|
|
150
|
+
- samples/concept.rb
|
|
151
|
+
- samples/registration_response.rb
|
|
120
152
|
- spec/spec_helper.rb
|
|
121
153
|
- spec/web_authn_spec.rb
|
|
122
154
|
- web_authn.gemspec
|
data/bin/console
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "web_authn"
|
|
5
|
-
|
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
-
|
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
-
# require "pry"
|
|
11
|
-
# Pry.start
|
|
12
|
-
|
|
13
|
-
require "irb"
|
|
14
|
-
IRB.start(__FILE__)
|