web_authn 0.2.2 → 0.3.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/README.md +37 -0
- data/VERSION +1 -1
- data/lib/web_authn.rb +3 -0
- data/lib/web_authn/attestation_object.rb +16 -5
- data/lib/web_authn/attestation_statement.rb +6 -0
- data/lib/web_authn/attestation_statement/android_safetynet.rb +81 -0
- data/lib/web_authn/client_data_json.rb +13 -13
- data/lib/web_authn/context/registration.rb +7 -1
- data/spec/context/registration_spec.rb +38 -0
- data/web_authn.gemspec +1 -0
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba07fe60ce73457f0f61ddc7c53dbf2b07886d93
|
4
|
+
data.tar.gz: a9d76ab96805dd91c619551c09e0701a2a7b0795
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4550a5b91a35c2c5e302b58072b2a063b203370a8128d03cc851d7866e8f6bee92eab6d86dda0a41dcc366b47f9c86bfc9a0eef1e6f4a7df24bba18bbf61e32f
|
7
|
+
data.tar.gz: 5aa59a73d18cfd24b6bd86431676c4c570ee6e5ff0e7af5c83fc70767a134ec87a577d1a7ebe0aab187dd92d9e467987b0e0adb085fe244aa57be6d4ac6f9849
|
data/README.md
CHANGED
@@ -26,6 +26,43 @@ $ gem install web_authn
|
|
26
26
|
|
27
27
|
## Usage
|
28
28
|
|
29
|
+
```ruby
|
30
|
+
context = WebAuthn.context_for(
|
31
|
+
client_data_json, # NOTE: URL-safe Base64 encoded
|
32
|
+
origin: request.base_url,
|
33
|
+
challenge: session[:challenge],
|
34
|
+
)
|
35
|
+
|
36
|
+
if context.registration?
|
37
|
+
context.verify!(
|
38
|
+
attestation_object # URL-safe Base64 encoded
|
39
|
+
)
|
40
|
+
context.credential_id
|
41
|
+
context.public_key # => `OpenSSL::PKey::RSA` or `OpenSSL::PKey::EC`
|
42
|
+
context.public_cose_key # => `COSE::Key::RSA` or `COSE::Key::EC2` ref.) https://github.com/nov/cose-key
|
43
|
+
context.sign_count # => `Integer`
|
44
|
+
elsif context.authentication?
|
45
|
+
context.verify!(
|
46
|
+
authenticator_data, # URL-safe Base64 encoded
|
47
|
+
|
48
|
+
# NOTE:
|
49
|
+
# either 'public_key' or 'public_cose_key' is required.
|
50
|
+
# if `public_key` is given, you can also specify `digest` (default: `OpenSSL::Digest::SHA256.new`).
|
51
|
+
# if `public_cose_key` is given, it includes digest size information, so no `digest` is required.
|
52
|
+
|
53
|
+
# public_key: public_key, # `OpenSSL::PKey::RSA` or `OpenSSL::PKey::EC`
|
54
|
+
# digest: OpenSSL::Digest::SHA256.new, # `OpenSSL::Digest::SHA(1|256|384|512)`` (default: `OpenSSL::Digest::SHA256`)
|
55
|
+
public_cose_key: public_cose_key, # `COSE::Key::RSA` or `COSE::Key::EC` ref.) https://github.com/nov/cose-key
|
56
|
+
|
57
|
+
sign_count: previously_stored_sign_count,
|
58
|
+
signature: signature # URL-safe Base64 encoded
|
59
|
+
)
|
60
|
+
context.sign_count # => Integer
|
61
|
+
else
|
62
|
+
# should never happen.
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
29
66
|
See sample code in this repository, or [working sample site](https://web-authn.herokuapp.com/).
|
30
67
|
|
31
68
|
Currently, there are several restrictions.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/web_authn.rb
CHANGED
@@ -3,10 +3,12 @@ require 'active_support'
|
|
3
3
|
require 'active_support/core_ext'
|
4
4
|
require 'cbor'
|
5
5
|
require 'cose/key'
|
6
|
+
require 'json/jwt'
|
6
7
|
|
7
8
|
module WebAuthn
|
8
9
|
class Exception < StandardError; end
|
9
10
|
class InvalidContext < Exception; end
|
11
|
+
class InvalidAttestation < Exception; end
|
10
12
|
class InvalidAssertion < Exception; end
|
11
13
|
class NotImplementedError < NotImplementedError; end
|
12
14
|
|
@@ -22,6 +24,7 @@ module WebAuthn
|
|
22
24
|
end
|
23
25
|
|
24
26
|
require 'web_authn/attestation_object'
|
27
|
+
require 'web_authn/attestation_statement'
|
25
28
|
require 'web_authn/attested_credential_data'
|
26
29
|
require 'web_authn/authenticator_data'
|
27
30
|
require 'web_authn/client_data_json'
|
@@ -9,24 +9,35 @@ module WebAuthn
|
|
9
9
|
delegate method, to: :authenticator_data
|
10
10
|
end
|
11
11
|
|
12
|
-
def initialize(
|
13
|
-
self.format =
|
12
|
+
def initialize(fmt:, att_stmt:, auth_data:)
|
13
|
+
self.format = fmt
|
14
14
|
self.attestation_statement = case format
|
15
15
|
when 'none'
|
16
16
|
nil
|
17
|
-
when '
|
17
|
+
when 'android-safetynet'
|
18
|
+
AttestationStatement::AndroidSafetynet.decode att_stmt
|
19
|
+
when 'packed', 'tpm', 'android-key', 'fido-u2f'
|
18
20
|
raise NotImplementedError, "Unsupported Attestation Format: #{format}"
|
19
21
|
else
|
20
22
|
raise InvalidContext, 'Unknown Attestation Format'
|
21
23
|
end
|
22
|
-
self.authenticator_data = AuthenticatorData.decode
|
24
|
+
self.authenticator_data = AuthenticatorData.decode auth_data
|
25
|
+
end
|
26
|
+
|
27
|
+
def verify_signature!(client_data_json)
|
28
|
+
attestation_statement.try(:verify!, authenticator_data, client_data_json)
|
23
29
|
end
|
24
30
|
|
25
31
|
class << self
|
26
32
|
def decode(encoded_attestation_object)
|
27
|
-
|
33
|
+
cbor = CBOR.decode(
|
28
34
|
Base64.urlsafe_decode64 encoded_attestation_object
|
29
35
|
).with_indifferent_access
|
36
|
+
new(
|
37
|
+
fmt: cbor[:fmt],
|
38
|
+
att_stmt: cbor[:attStmt],
|
39
|
+
auth_data: cbor[:authData]
|
40
|
+
)
|
30
41
|
end
|
31
42
|
end
|
32
43
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module WebAuthn
|
2
|
+
class AttestationStatement
|
3
|
+
class AndroidSafetynet < AttestationStatement
|
4
|
+
attr_accessor :ver, :response, :certs
|
5
|
+
|
6
|
+
def initialize(ver:, response:)
|
7
|
+
self.ver = ver
|
8
|
+
self.response = response
|
9
|
+
self.certs = response.x5c.collect do |x5c|
|
10
|
+
OpenSSL::X509::Certificate.new(
|
11
|
+
Base64.decode64 x5c
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def verify!(authenticator_data, client_data_json)
|
17
|
+
verify_nonce! authenticator_data, client_data_json
|
18
|
+
verify_signature!
|
19
|
+
verify_certificate!
|
20
|
+
|
21
|
+
# TODO: put more ref.) https://www.w3.org/TR/webauthn/#android-safetynet-attestation
|
22
|
+
unless response[:ctsProfileMatch]
|
23
|
+
raise InvalidAttestation, 'Invalid Android Safetynet Response: ctsProfileMatch'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def verify_nonce!(authenticator_data, client_data_json)
|
30
|
+
nonce = Base64.encode64(
|
31
|
+
OpenSSL::Digest::SHA256.digest [
|
32
|
+
authenticator_data.raw,
|
33
|
+
OpenSSL::Digest::SHA256.digest(client_data_json.raw)
|
34
|
+
].join
|
35
|
+
).strip
|
36
|
+
unless response[:nonce] == nonce
|
37
|
+
raise InvalidAttestation, 'Invalid Android Safetynet Response: nonce'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def verify_signature!
|
42
|
+
response.verify! certs.first.public_key
|
43
|
+
rescue JSON::JWS::VerificationFailed => e
|
44
|
+
raise InvalidAttestation, 'Invalid Android Safetynet Response: signature'
|
45
|
+
end
|
46
|
+
|
47
|
+
def verify_certificate!
|
48
|
+
signing_cert = certs.first
|
49
|
+
remaining_chain = certs[1..-1]
|
50
|
+
|
51
|
+
store = OpenSSL::X509::Store.new
|
52
|
+
store.set_default_paths
|
53
|
+
valid_chain = store.verify(signing_cert, remaining_chain)
|
54
|
+
|
55
|
+
valid_subject = signing_cert.subject.to_a.detect do |key, value, type|
|
56
|
+
key == 'CN'
|
57
|
+
end.second == 'attest.android.com'
|
58
|
+
|
59
|
+
valid_timestamp = (
|
60
|
+
signing_cert.not_after > Time.now &&
|
61
|
+
signing_cert.not_before < Time.now
|
62
|
+
)
|
63
|
+
|
64
|
+
# TODO: do we need CRL check?
|
65
|
+
|
66
|
+
unless valid_chain && valid_subject && valid_timestamp
|
67
|
+
raise InvalidAttestation, 'Invalid Android Safetynet Response: certificate'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class << self
|
72
|
+
def decode(att_stmt)
|
73
|
+
new(
|
74
|
+
ver: att_stmt[:ver],
|
75
|
+
response: JSON::JWT.decode(att_stmt[:response], :skip_verification)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -2,23 +2,23 @@ module WebAuthn
|
|
2
2
|
class ClientDataJSON
|
3
3
|
attr_accessor :type, :origin, :challenge, :raw
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
self.type =
|
7
|
-
self.origin =
|
8
|
-
self.challenge =
|
9
|
-
self.raw =
|
5
|
+
def initialize(type:, origin:, challenge:, raw: nil)
|
6
|
+
self.type = type
|
7
|
+
self.origin = origin
|
8
|
+
self.challenge = challenge
|
9
|
+
self.raw = raw
|
10
10
|
end
|
11
11
|
|
12
12
|
class << self
|
13
13
|
def decode(encoded_client_data_json)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
raw = Base64.urlsafe_decode64 encoded_client_data_json
|
15
|
+
json = JSON.parse(raw).with_indifferent_access
|
16
|
+
new(
|
17
|
+
type: json[:type],
|
18
|
+
origin: json[:origin],
|
19
|
+
challenge: Base64.urlsafe_decode64(json[:challenge]),
|
20
|
+
raw: raw
|
21
|
+
)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -4,7 +4,8 @@ module WebAuthn
|
|
4
4
|
attr_accessor :attestation_object
|
5
5
|
|
6
6
|
# TODO: will need more methods, or let developers access deep methods by themselves.
|
7
|
-
%i(credential_id rp_id_hash flags public_key public_cose_key sign_count
|
7
|
+
%i(credential_id rp_id_hash flags public_key public_cose_key sign_count
|
8
|
+
attestation_statement).each do |method|
|
8
9
|
delegate method, to: :attestation_object
|
9
10
|
end
|
10
11
|
|
@@ -17,6 +18,7 @@ module WebAuthn
|
|
17
18
|
encoded_attestation_object
|
18
19
|
)
|
19
20
|
verify_flags!
|
21
|
+
verify_signature!
|
20
22
|
self
|
21
23
|
end
|
22
24
|
|
@@ -26,6 +28,10 @@ module WebAuthn
|
|
26
28
|
super
|
27
29
|
raise InvalidAssertion, 'Missing Flag: "at"' unless flags.at?
|
28
30
|
end
|
31
|
+
|
32
|
+
def verify_signature!
|
33
|
+
attestation_object.verify_signature! client_data_json
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -43,5 +43,43 @@ RSpec.describe WebAuthn::Context::Registration do
|
|
43
43
|
subject.public_key.to_pem.should == public_key_pem
|
44
44
|
end
|
45
45
|
its(:sign_count) { should == sign_count }
|
46
|
+
|
47
|
+
context 'when android-safetynet attestation given' do
|
48
|
+
let(:context) do
|
49
|
+
{
|
50
|
+
origin: 'https://web-authn.self-issued.app',
|
51
|
+
challenge: 'random-string-generated-by-rp-server'
|
52
|
+
}
|
53
|
+
end
|
54
|
+
let(:client_data_json) do
|
55
|
+
'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiY21GdVpHOXRMWE4wY21sdVp5MW5aVzVsY21GMFpXUXRZbmt0Y25BdGMyVnlkbVZ5Iiwib3JpZ2luIjoiaHR0cHM6XC9cL3dlYi1hdXRobi5zZWxmLWlzc3VlZC5hcHAiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uY2hyb21lLmNhbmFyeSJ9'
|
56
|
+
end
|
57
|
+
let(:attestation_object) do
|
58
|
+
'o2NmbXRxYW5kcm9pZC1zYWZldHluZXRnYXR0U3RtdKJjdmVyaDEyODc0MDQxaHJlc3BvbnNlWRMHZXlKaGJHY2lPaUpTVXpJMU5pSXNJbmcxWXlJNld5Sk5TVWxGYVdwRFEwRXpTMmRCZDBsQ1FXZEpTVmxyV1c4MVJqQm5PRFpyZDBSUldVcExiMXBKYUhaalRrRlJSVXhDVVVGM1ZrUkZURTFCYTBkQk1WVkZRbWhOUTFaV1RYaElha0ZqUW1kT1ZrSkJiMVJHVldSMllqSmtjMXBUUWxWamJsWjZaRU5DVkZwWVNqSmhWMDVzWTNwRmJFMURUVWRCTVZWRlFYaE5ZMUl5T1haYU1uaHNTVVZzZFdSSFZubGliVll3U1VWR01XUkhhSFpqYld3d1pWTkNTRTE2UVdWR2R6QjRUbnBGZVUxRVVYaE5la1UwVGtST1lVWjNNSGhQUkVWNVRVUk5kMDFFUVhkTlJFSmhUVWQzZUVONlFVcENaMDVXUWtGWlZFRnNWbFJOVWsxM1JWRlpSRlpSVVVsRVFYQkVXVmQ0Y0ZwdE9YbGliV3hvVFZKWmQwWkJXVVJXVVZGSVJFRXhUbUl6Vm5Wa1IwWndZbWxDVjJGWFZqTk5VazEzUlZGWlJGWlJVVXRFUVhCSVlqSTVibUpIVldkVFZ6VnFUVkp6ZDBkUldVUldVVkZFUkVKS2FHUklVbXhqTTFGMVdWYzFhMk50T1hCYVF6VnFZakl3ZDJkblJXbE5RVEJIUTFOeFIxTkpZak5FVVVWQ1FWRlZRVUUwU1VKRWQwRjNaMmRGUzBGdlNVSkJVVU5WYWpoM1dXOVFhWGhMWW1KV09ITm5XV2QyVFZSbVdDdGtTWE5HVkU5clowdFBiR2hVTUdrd1ltTkVSbHBMTW5KUGVFcGFNblZUVEZOV2FGbDJhWEJhVGtVelNFcFJXWFYxV1hkR2FtbDVLM2xyWm1GMFFVZFRhbEo2UmpGaU16RjFORE12TjI5SE5XcE5hRE5UTXpkaGJIZHFWV0k0UTFkcFZIaHZhWEJXVDFsM1MwdDZkVlY1YTNGRlEzUnFiR2hLTkVGclYyRkVVeXRhZUV0RmNVOWhaVGwwYmtOblpVaHNiRnBGTDA5U1oyVk5ZWGd5V0U1RGIwZzJjM0pVUlZKamEzTnFlbHBhY2tGWGVFdHpaR1oyVm5KWVRucERVamxFZUZaQlUzVkpOa3g2ZDJnNFJGTnNNa1ZQYjJ0aWMyRnVXaXNyTDBweFRXVkJRa1ptVUhkcWVYZHlZakJ3Y2tWVmVUQndZV1ZXYzNWa0t6QndaV1Y0U3k4MUswVTJhM0JaUjBzMFdrc3libXR2Vmt4MVowVTFkR0ZJY2tGcU9ETlJLMUJQWW1KMlQzcFhZMFpyY0c1V1MzbHFielpMVVVGdFdEWlhTa0ZuVFVKQlFVZHFaMmRHUjAxSlNVSlJha0ZVUW1kT1ZraFRWVVZFUkVGTFFtZG5ja0puUlVaQ1VXTkVRVlJCWkVKblRsWklVa1ZGUm1wQlZXZG9TbWhrU0ZKc1l6TlJkVmxYTld0amJUbHdXa00xYW1JeU1IZGhRVmxKUzNkWlFrSlJWVWhCVVVWRldFUkNZVTFETUVkRFEzTkhRVkZWUmtKNlFVTm9hVVp2WkVoU2QwOXBPSFpqUjNSd1RHMWtkbUl5WTNaYU0wNTVUV2s1U0ZaR1RraFRWVVpJVFhrMWFtTnVVWGRMVVZsSlMzZFpRa0pSVlVoTlFVZEhTRmRvTUdSSVFUWk1lVGwyV1ROT2QweHVRbkpoVXpWdVlqSTVia3d3WkZWVk1HUktVVlZqZWsxQ01FZEJNVlZrUkdkUlYwSkNVVWM0U1hKUmRFWlNOa05WVTJ0cGEySXpZV2x0YzIweU5tTkNWRUZOUW1kT1ZraFNUVUpCWmpoRlFXcEJRVTFDT0VkQk1WVmtTWGRSV1UxQ1lVRkdTR1pEZFVaRFlWb3pXakp6VXpORGFIUkRSRzlJTm0xbWNuQk1UVU5GUjBFeFZXUkpRVkZoVFVKbmQwUkJXVXRMZDFsQ1FrRklWMlZSU1VaQmVrRkpRbWRhYm1kUmQwSkJaMGwzVFZGWlJGWlNNR1pDUTI5M1MwUkJiVzlEVTJkSmIxbG5ZVWhTTUdORWIzWk1NazU1WWtNMWQyRXlhM1ZhTWpsMlduazVTRlpHVGtoVFZVWklUWGsxYW1OdGQzZEVVVmxLUzI5YVNXaDJZMDVCVVVWTVFsRkJSR2RuUlVKQlJpOVNlazV1UXpWRWVrSlZRblJ1YURKdWRFcE1WMFZSYURsNlJXVkdXbVpRVERsUmIydHliRUZ2V0dkcVYyZE9PSEJUVWxVeGJGWkhTWEIwZWsxNFIyaDVNeTlQVWxKYVZHRTJSREpFZVRob2RrTkVja1pKTXl0c1Exa3dNVTFNTlZFMldFNUZOVkp6TW1ReFVtbGFjRTF6ZWtRMFMxRmFUa2N6YUZvd1FrWk9VUzlqYW5KRGJVeENUMGRMYTBWVk1XUnRRVmh6UmtwWVNtbFBjakpEVGxSQ1QxUjFPVVZpVEZkb1VXWmtRMFl4WW5kNmVYVXJWelppVVZOMk9GRkVialZQWkUxVEwxQnhSVEZrUldkbGRDODJSVWxTUWpjMk1VdG1XbEVyTDBSRk5reHdNMVJ5V2xSd1QwWkVSR2RZYUN0TVowZFBjM2RvUld4cU9XTXpkbHBJUjBwdWFHcHdkRGh5YTJKcGNpOHlkVXhIWm5oc1ZsbzBTekY0TlVSU1RqQlFWVXhrT1hsUVUyMXFaeXRoYWpFcmRFaDNTVEZ0VVcxYVZsazNjWFpQTlVSbmFFOTRhRXBOUjJ4Nk5teE1hVnB0ZW05blBTSXNJazFKU1VWWVJFTkRRVEJUWjBGM1NVSkJaMGxPUVdWUGNFMUNlamhqWjFrMFVEVndWRWhVUVU1Q1oydHhhR3RwUnpsM01FSkJVWE5HUVVSQ1RVMVRRWGRJWjFsRVZsRlJURVY0WkVoaVJ6bHBXVmQ0VkdGWFpIVkpSa3AyWWpOUloxRXdSV2RNVTBKVFRXcEZWRTFDUlVkQk1WVkZRMmhOUzFJeWVIWlpiVVp6VlRKc2JtSnFSVlJOUWtWSFFURlZSVUY0VFV0U01uaDJXVzFHYzFVeWJHNWlha0ZsUm5jd2VFNTZRVEpOVkZWM1RVUkJkMDVFU21GR2R6QjVUVlJGZVUxVVZYZE5SRUYzVGtSS1lVMUdVWGhEZWtGS1FtZE9Wa0pCV1ZSQmJGWlVUVkkwZDBoQldVUldVVkZMUlhoV1NHSXlPVzVpUjFWblZraEtNV016VVdkVk1sWjVaRzFzYWxwWVRYaEtWRUZxUW1kT1ZrSkJUVlJJUldSMllqSmtjMXBUUWtwaWJsSnNZMjAxYkdSRFFrSmtXRkp2WWpOS2NHUklhMmRTZWsxM1oyZEZhVTFCTUVkRFUzRkhVMGxpTTBSUlJVSkJVVlZCUVRSSlFrUjNRWGRuWjBWTFFXOUpRa0ZSUkV0VmEzWnhTSFl2VDBwSGRXOHlia2xaWVU1V1YxaFJOVWxYYVRBeFExaGFZWG8yVkVsSVRFZHdMMnhQU2lzMk1EQXZOR2hpYmpkMmJqWkJRVUl6UkZaNlpGRlBkSE0zUnpWd1NEQnlTbTV1VDBaVlFVczNNVWMwYm5wTFRXWklRMGRWYTNOWEwyMXZibUVyV1RKbGJVcFJNazRyWVdsamQwcExaWFJRUzFKVFNXZEJkVkJQUWpaQllXaG9PRWhpTWxoUE0yZzVVbFZyTWxRd1NFNXZkVUl5Vm5wNGIwMVliR3Q1VnpkWVZWSTFiWGMyU210TVNHNUJOVEpZUkZadlVsUlhhMDUwZVRWdlEwbE9USFpIYlc1U2Mwb3hlbTkxUVhGWlIxWlJUV012TjNONUt5OUZXV2hCVEhKV1NrVkJPRXRpZEhsWUszSTRjMjUzVlRWRE1XaFZjbmRoVnpaTlYwOUJVbUU0Y1VKd1RsRmpWMVJyWVVsbGIxbDJlUzl6UjBsS1JXMXFVakIyUmtWM1NHUndNV05UWVZkSmNqWXZOR2MzTW00M1QzRllkMlpwYm5VM1dsbFhPVGRGWm05UFUxRktaVUY2UVdkTlFrRkJSMnBuWjBWNlRVbEpRa3g2UVU5Q1owNVdTRkU0UWtGbU9FVkNRVTFEUVZsWmQwaFJXVVJXVWpCc1FrSlpkMFpCV1VsTGQxbENRbEZWU0VGM1JVZERRM05IUVZGVlJrSjNUVU5OUWtsSFFURlZaRVYzUlVJdmQxRkpUVUZaUWtGbU9FTkJVVUYzU0ZGWlJGWlNNRTlDUWxsRlJraG1RM1ZHUTJGYU0xb3ljMU16UTJoMFEwUnZTRFp0Wm5Kd1RFMUNPRWRCTVZWa1NYZFJXVTFDWVVGR1NuWnBRakZrYmtoQ04wRmhaMkpsVjJKVFlVeGtMMk5IV1ZsMVRVUlZSME5EYzBkQlVWVkdRbmRGUWtKRGEzZEtla0ZzUW1kbmNrSm5SVVpDVVdOM1FWbFpXbUZJVWpCalJHOTJUREk1YW1NelFYVmpSM1J3VEcxa2RtSXlZM1phTTA1NVRXcEJlVUpuVGxaSVVqaEZTM3BCY0UxRFpXZEtZVUZxYUdsR2IyUklVbmRQYVRoMldUTktjMHh1UW5KaFV6VnVZakk1Ymt3eVpIcGpha2wyV2pOT2VVMXBOV3BqYlhkM1VIZFpSRlpTTUdkQ1JHZDNUbXBCTUVKbldtNW5VWGRDUVdkSmQwdHFRVzlDWjJkeVFtZEZSa0pSWTBOQlVsbGpZVWhTTUdOSVRUWk1lVGwzWVRKcmRWb3lPWFphZVRsNVdsaENkbU15YkRCaU0wbzFUSHBCVGtKbmEzRm9hMmxIT1hjd1FrRlJjMFpCUVU5RFFWRkZRVWhNWlVwc2RWSlVOMkoyY3pJMlozbEJXamh6YnpneGRISlZTVk5rTjA4ME5YTnJSRlZ0UVdkbE1XTnVlR2hITVZBeVkwNXRVM2hpVjNOdmFVTjBNbVYxZURsTVUwUXJVRUZxTWt4SldWSkdTRmN6TVM4MmVHOXBZekZyTkhSaVYxaHJSRU5xYVhJek4zaFVWRTV4VWtGTlVGVjVSbEpYVTJSMmRDdHViRkJ4ZDI1aU9FOWhNa2t2YldGVFNuVnJZM2hFYWs1VFpuQkVhQzlDWkRGc1drNW5aR1F2T0dOTVpITkZNeXQzZVhCMVprbzVkVmhQTVdsUmNHNW9PWHBpZFVaSmQzTkpUMDVIYkRGd00wRTRRMmQ0YTNGSkwxVkJhV2d6U21GSFQzRmpjR05rWVVOSmVtdENZVkk1ZFZsUk1WZzBhekpXWnpWQlVGSk1iM1Y2Vm5rM1lUaEpWbXMyZDNWNU5uQnRLMVEzU0ZRMFRGazRhV0pUTlVaRldteG1RVVpNVTFjNFRuZHpWbm81VTBKTE1sWnhiakZPTUZCSlRXNDFlRUUyVGxwV1l6ZHZPRE0xUkV4QlJuTm9SVmRtUXpkVVNXVXpaejA5SWwxOS5leUp1YjI1alpTSTZJaTlYVG1SVFZXOHpiRUl4ZWt0Mk5UVlpNV1EyY2psR1pXdFJkbE5rZERjNWNHaDRjMmhuTjNsMVIxazlJaXdpZEdsdFpYTjBZVzF3VFhNaU9qRTFNelUyT1Rjd056TTFOVEFzSW1Gd2ExQmhZMnRoWjJWT1lXMWxJam9pWTI5dExtZHZiMmRzWlM1aGJtUnliMmxrTG1kdGN5SXNJbUZ3YTBScFoyVnpkRk5vWVRJMU5pSTZJako1TXpBNFJ6Y3hMM2RaUmtZMk9XRnVSVzlKT1VGYWNrTmFPREZFV0dSMk1YaEhNMjg0UVZkUlNITTlJaXdpWTNSelVISnZabWxzWlUxaGRHTm9JanAwY25WbExDSmhjR3REWlhKMGFXWnBZMkYwWlVScFoyVnpkRk5vWVRJMU5pSTZXeUk0VURGelZ6QkZVRXBqYzJ4M04xVjZVbk5wV0V3Mk5IY3JUelV3UldRclVrSkpRM1JoZVRGbk1qUk5QU0pkTENKaVlYTnBZMGx1ZEdWbmNtbDBlU0k2ZEhKMVpYMC5QVW9fT0h0dW96SWUxZGZFNlctSUVlYkZtN0R0Qll6M2NmZ0UzRlB5X3dVQlFCMjUwUE1WWjNVbk1NVjYwb0Q2U3d0ajZtQ0lQYnNYZnBULXFuY184eHlTUURndXZQUmp1b191b1JtUWF4aV9FSEZVaTZrZFV0akhaNkY1bWpYcVF4LWRiaFlONU00dG01WlM2bUxaMkdlbGlVcE1UVG9nU2FQUVZiVnl1Y3g0aGpfT1VDLWVPM1lCRmRldmw0d0pmSy15QVJxaGtmOHN6NEgwa1E5TU9IT2U0N0x2a0h3RnI2MElQSjNaQ3QwSklKYnJWVDZnMHJJal9iSlo3aXFrTDFOWUhrbURvNUhnSkgyTXA3QnYtZlJfMHcydzJyWkIzZk5zVGhxRm9UbUI4RU5XUmJjQ3lWQXBncVdIbFU5bUh5SlJGWVVsbnVDcGRGSEwwUjFaX1FoYXV0aERhdGFYxTLLgNysw8NSRiywHzv-MC3m83EvMP0g7NGcO6W4WJSVRAAAAAAAAAAAAAAAAAAAAAAAAAAAAEEBKg96NvDCk5gmyyLqM0zXE0ZZnSkTarHzUfYU2PHWwdQhjjWLXayf0-jYLazWjpSr-N8DM1Zhls4jfmQCqa50X6UBAgMmIAEhWCAGD72C5VXE3mwMjzc_X0_7wUgIOA6kt2KoDIn1-1PHdSJYIAoZmBXyEJkjvlu251BDb8VoOtIketAD1VvYc3WUJJrZ'
|
59
|
+
end
|
60
|
+
|
61
|
+
it do
|
62
|
+
expect do
|
63
|
+
subject
|
64
|
+
end.not_to raise_error
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'when client_data_json is invalid' do
|
68
|
+
let(:client_data_json) do
|
69
|
+
Base64.urlsafe_encode64({
|
70
|
+
type: "webauthn.create",
|
71
|
+
challenge: "cmFuZG9tLXN0cmluZy1nZW5lcmF0ZWQtYnktcnAtc2VydmVy",
|
72
|
+
origin: "https://web-authn.self-issued.app",
|
73
|
+
androidPackageName: "com.chrome.canary.malformed"
|
74
|
+
}.to_json, padding: false)
|
75
|
+
end
|
76
|
+
|
77
|
+
it do
|
78
|
+
expect do
|
79
|
+
subject
|
80
|
+
end.to raise_error WebAuthn::InvalidAttestation, 'Invalid Android Safetynet Response: nonce'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
46
84
|
end
|
47
85
|
end
|
data/web_authn.gemspec
CHANGED
@@ -15,6 +15,7 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.add_runtime_dependency 'activesupport'
|
16
16
|
gem.add_runtime_dependency 'cbor'
|
17
17
|
gem.add_runtime_dependency 'cose-key'
|
18
|
+
gem.add_runtime_dependency 'json-jwt'
|
18
19
|
gem.add_development_dependency 'rake', '~> 10.0'
|
19
20
|
gem.add_development_dependency 'simplecov'
|
20
21
|
gem.add_development_dependency 'rspec'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: web_authn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
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-09-
|
11
|
+
date: 2018-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
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'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rake
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -128,6 +142,8 @@ files:
|
|
128
142
|
- bin/setup
|
129
143
|
- lib/web_authn.rb
|
130
144
|
- lib/web_authn/attestation_object.rb
|
145
|
+
- lib/web_authn/attestation_statement.rb
|
146
|
+
- lib/web_authn/attestation_statement/android_safetynet.rb
|
131
147
|
- lib/web_authn/attested_credential_data.rb
|
132
148
|
- lib/web_authn/authenticator_data.rb
|
133
149
|
- lib/web_authn/authenticator_data/flags.rb
|
@@ -165,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
181
|
version: '0'
|
166
182
|
requirements: []
|
167
183
|
rubyforge_project:
|
168
|
-
rubygems_version: 2.
|
184
|
+
rubygems_version: 2.5.2
|
169
185
|
signing_key:
|
170
186
|
specification_version: 4
|
171
187
|
summary: WebAuthn RP library
|