webauthn 2.0.0.beta1 → 2.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +65 -13
  4. data/.travis.yml +22 -18
  5. data/Appraisals +4 -0
  6. data/CHANGELOG.md +72 -25
  7. data/CONTRIBUTING.md +0 -5
  8. data/README.md +172 -15
  9. data/SECURITY.md +4 -4
  10. data/gemfiles/openssl_2_2.gemfile +7 -0
  11. data/lib/cose/rsapkcs1_algorithm.rb +43 -0
  12. data/lib/webauthn/attestation_object.rb +43 -0
  13. data/lib/webauthn/attestation_statement.rb +20 -20
  14. data/lib/webauthn/attestation_statement/android_key.rb +28 -30
  15. data/lib/webauthn/attestation_statement/android_safetynet.rb +30 -20
  16. data/lib/webauthn/attestation_statement/base.rb +124 -14
  17. data/lib/webauthn/attestation_statement/fido_u2f.rb +13 -9
  18. data/lib/webauthn/attestation_statement/packed.rb +14 -42
  19. data/lib/webauthn/attestation_statement/tpm.rb +38 -54
  20. data/lib/webauthn/authenticator_assertion_response.rb +7 -36
  21. data/lib/webauthn/authenticator_attestation_response.rb +24 -46
  22. data/lib/webauthn/authenticator_data.rb +51 -51
  23. data/lib/webauthn/authenticator_data/attested_credential_data.rb +29 -50
  24. data/lib/webauthn/authenticator_response.rb +15 -10
  25. data/lib/webauthn/configuration.rb +23 -0
  26. data/lib/webauthn/credential.rb +4 -4
  27. data/lib/webauthn/credential_creation_options.rb +1 -1
  28. data/lib/webauthn/fake_authenticator.rb +7 -3
  29. data/lib/webauthn/fake_authenticator/attestation_object.rb +7 -3
  30. data/lib/webauthn/fake_authenticator/authenticator_data.rb +2 -4
  31. data/lib/webauthn/fake_client.rb +17 -4
  32. data/lib/webauthn/public_key.rb +68 -0
  33. data/lib/webauthn/public_key_credential.rb +13 -3
  34. data/lib/webauthn/public_key_credential/creation_options.rb +2 -2
  35. data/lib/webauthn/u2f_migrator.rb +5 -4
  36. data/lib/webauthn/version.rb +1 -1
  37. data/script/ci/install-openssl +7 -0
  38. data/script/ci/install-ruby +13 -0
  39. data/webauthn.gemspec +14 -9
  40. metadata +70 -42
  41. data/lib/android_safetynet/attestation_response.rb +0 -84
  42. data/lib/cose/algorithm.rb +0 -38
  43. data/lib/tpm/constants.rb +0 -22
  44. data/lib/tpm/s_attest.rb +0 -26
  45. data/lib/tpm/s_attest/s_certify_info.rb +0 -14
  46. data/lib/tpm/sized_buffer.rb +0 -13
  47. data/lib/tpm/t_public.rb +0 -32
  48. data/lib/tpm/t_public/s_ecc_parms.rb +0 -17
  49. data/lib/tpm/t_public/s_rsa_parms.rb +0 -17
  50. data/lib/webauthn/attestation_statement/android_key/authorization_list.rb +0 -39
  51. data/lib/webauthn/attestation_statement/android_key/key_description.rb +0 -37
  52. data/lib/webauthn/attestation_statement/tpm/cert_info.rb +0 -44
  53. data/lib/webauthn/attestation_statement/tpm/pub_area.rb +0 -85
  54. data/lib/webauthn/signature_verifier.rb +0 -65
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "base64"
4
- require "jwt"
5
- require "webauthn/security_utils"
6
-
7
- module AndroidSafetynet
8
- # Decoupled from WebAuthn, candidate for extraction
9
- # Reference: https://developer.android.com/training/safetynet/attestation.html
10
- class AttestationResponse
11
- class VerificationError < StandardError; end
12
- class LeafCertificateSubjectError < VerificationError; end
13
- class NonceMismatchError < VerificationError; end
14
- class SignatureError < VerificationError; end
15
- class ResponseMissingError < VerificationError; end
16
-
17
- CERTIRICATE_CHAIN_HEADER = "x5c"
18
- VALID_SUBJECT_HOSTNAME = "attest.android.com"
19
- HEADERS_POSITION = 1
20
- PAYLOAD_POSITION = 0
21
-
22
- attr_reader :response
23
-
24
- def initialize(response)
25
- @response = response
26
- end
27
-
28
- def verify(nonce)
29
- if response
30
- valid_nonce?(nonce) || raise(NonceMismatchError)
31
- valid_attestation_domain? || raise(LeafCertificateSubjectError)
32
- valid_signature? || raise(SignatureError)
33
- else
34
- raise(ResponseMissingError)
35
- end
36
- end
37
-
38
- def cts_profile_match?
39
- payload["ctsProfileMatch"]
40
- end
41
-
42
- def certificate_chain
43
- @certificate_chain ||= headers[CERTIRICATE_CHAIN_HEADER].map do |cert|
44
- OpenSSL::X509::Certificate.new(Base64.strict_decode64(cert))
45
- end
46
- end
47
-
48
- private
49
-
50
- def valid_nonce?(nonce)
51
- WebAuthn::SecurityUtils.secure_compare(payload["nonce"], nonce)
52
- end
53
-
54
- def valid_attestation_domain?
55
- common_name = leaf_certificate&.subject&.to_a&.assoc('CN')
56
-
57
- if common_name
58
- common_name[1] == VALID_SUBJECT_HOSTNAME
59
- end
60
- end
61
-
62
- def valid_signature?
63
- JWT.decode(response, leaf_certificate.public_key, true, algorithms: ["ES256", "RS256"])
64
- rescue JWT::VerificationError
65
- false
66
- end
67
-
68
- def leaf_certificate
69
- certificate_chain[0]
70
- end
71
-
72
- def headers
73
- jws_parts[HEADERS_POSITION]
74
- end
75
-
76
- def payload
77
- jws_parts[PAYLOAD_POSITION]
78
- end
79
-
80
- def jws_parts
81
- @jws_parts ||= JWT.decode(response, nil, false)
82
- end
83
- end
84
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "cose/key"
4
-
5
- # TODO: Move this to cose gem
6
- module COSE
7
- # https://tools.ietf.org/html/rfc8152#section-8.1
8
- Algorithm = Struct.new(:id, :name, :hash, :kty, :key_curve) do
9
- @registered = {}
10
-
11
- def self.register(id, name, hash, kty, key_curve = nil)
12
- @registered[id] = COSE::Algorithm.new(id, name, hash, kty, key_curve)
13
- end
14
-
15
- def self.find(id)
16
- @registered[id]
17
- end
18
-
19
- def self.by_name(name)
20
- @registered.values.detect { |algorithm| algorithm.name == name }
21
- end
22
-
23
- def value
24
- id
25
- end
26
- end
27
- end
28
-
29
- COSE::Algorithm.register(-7, "ES256", "SHA256", COSE::Key::EC2::KTY_EC2, "prime256v1")
30
- COSE::Algorithm.register(-35, "ES384", "SHA384", COSE::Key::EC2::KTY_EC2, "prime256v1")
31
- COSE::Algorithm.register(-36, "ES512", "SHA512", COSE::Key::EC2::KTY_EC2, "prime256v1")
32
- COSE::Algorithm.register(-37, "PS256", "SHA256", COSE::Key::RSA::KTY_RSA)
33
- COSE::Algorithm.register(-38, "PS384", "SHA384", COSE::Key::RSA::KTY_RSA)
34
- COSE::Algorithm.register(-39, "PS512", "SHA512", COSE::Key::RSA::KTY_RSA)
35
- COSE::Algorithm.register(-257, "RS256", "SHA256", COSE::Key::RSA::KTY_RSA)
36
- COSE::Algorithm.register(-258, "RS384", "SHA384", COSE::Key::RSA::KTY_RSA)
37
- COSE::Algorithm.register(-259, "RS512", "SHA512", COSE::Key::RSA::KTY_RSA)
38
- COSE::Algorithm.register(-65535, "RS1", "SHA1", COSE::Key::RSA::KTY_RSA)
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TPM
4
- # Section 6 in https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
5
-
6
- GENERATED_VALUE = 0xFF544347
7
-
8
- ST_ATTEST_CERTIFY = 0x8017
9
-
10
- # Algorithms
11
- ALG_RSA = 0x0001
12
- ALG_SHA1 = 0x0004
13
- ALG_SHA256 = 0x000B
14
- ALG_NULL = 0x0010
15
- ALG_RSASSA = 0x0014
16
- ALG_RSAPSS = 0x0016
17
- ALG_ECDSA = 0x0018
18
- ALG_ECC = 0x0023
19
-
20
- # ECC curves
21
- ECC_NIST_P256 = 0x0003
22
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bindata"
4
- require "tpm/constants"
5
- require "tpm/sized_buffer"
6
- require "tpm/s_attest/s_certify_info"
7
-
8
- module TPM
9
- # Section 10.12.8 in https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
10
- class SAttest < BinData::Record
11
- endian :big
12
-
13
- uint32 :magic
14
- uint16 :attested_type
15
- sized_buffer :qualified_signer
16
- sized_buffer :extra_data
17
-
18
- # s_clock_info :clock_info
19
- # uint64 :firmware_version
20
- skip length: 25
21
-
22
- choice :attested, selection: :attested_type do
23
- s_certify_info TPM::ST_ATTEST_CERTIFY
24
- end
25
- end
26
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bindata"
4
- require "tpm/sized_buffer"
5
-
6
- module TPM
7
- class SAttest < BinData::Record
8
- # Section 10.12.3 in https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
9
- class SCertifyInfo < BinData::Record
10
- sized_buffer :name
11
- sized_buffer :qualified_name
12
- end
13
- end
14
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bindata"
4
-
5
- module TPM
6
- # Section 10.4 in https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
7
- class SizedBuffer < BinData::Record
8
- endian :big
9
-
10
- uint16 :buffer_size, value: lambda { buffer.size }
11
- string :buffer, read_length: :buffer_size
12
- end
13
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bindata"
4
- require "tpm/constants"
5
- require "tpm/sized_buffer"
6
- require "tpm/t_public/s_ecc_parms"
7
- require "tpm/t_public/s_rsa_parms"
8
-
9
- module TPM
10
- # Section 12.2.4 in https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
11
- class TPublic < BinData::Record
12
- endian :big
13
-
14
- uint16 :alg_type
15
- uint16 :name_alg
16
-
17
- # :object_attributes
18
- skip length: 4
19
-
20
- sized_buffer :auth_policy
21
-
22
- choice :parameters, selection: :alg_type do
23
- s_ecc_parms TPM::ALG_ECC
24
- s_rsa_parms TPM::ALG_RSA
25
- end
26
-
27
- choice :unique, selection: :alg_type do
28
- sized_buffer TPM::ALG_ECC
29
- sized_buffer TPM::ALG_RSA
30
- end
31
- end
32
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bindata"
4
-
5
- module TPM
6
- class TPublic < BinData::Record
7
- # Section 12.2.3.6 in https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
8
- class SEccParms < BinData::Record
9
- endian :big
10
-
11
- uint16 :symmetric
12
- uint16 :scheme
13
- uint16 :curve_id
14
- uint16 :kdf
15
- end
16
- end
17
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bindata"
4
-
5
- module TPM
6
- class TPublic < BinData::Record
7
- # Section 12.2.3.5 in https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
8
- class SRsaParms < BinData::Record
9
- endian :big
10
-
11
- uint16 :symmetric
12
- uint16 :scheme
13
- uint16 :key_bits
14
- uint32 :exponent
15
- end
16
- end
17
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "webauthn/attestation_statement/base"
4
-
5
- module WebAuthn
6
- module AttestationStatement
7
- class AndroidKey < Base
8
- class AuthorizationList
9
- PURPOSE_TAG = 1
10
- ALL_APPLICATIONS_TAG = 600
11
- ORIGIN_TAG = 702
12
-
13
- def initialize(sequence)
14
- @sequence = sequence
15
- end
16
-
17
- def purpose
18
- find_by_tag(PURPOSE_TAG)&.value&.at(0)&.value&.at(0)&.value
19
- end
20
-
21
- def all_applications
22
- find_by_tag(ALL_APPLICATIONS_TAG)&.value
23
- end
24
-
25
- def origin
26
- find_by_tag(ORIGIN_TAG)&.value&.at(0)&.value
27
- end
28
-
29
- private
30
-
31
- attr_reader :sequence
32
-
33
- def find_by_tag(tag)
34
- sequence.detect { |data| data.tag == tag }
35
- end
36
- end
37
- end
38
- end
39
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "webauthn/attestation_statement/android_key/authorization_list"
4
- require "webauthn/attestation_statement/base"
5
-
6
- module WebAuthn
7
- module AttestationStatement
8
- class AndroidKey < Base
9
- class KeyDescription
10
- # https://developer.android.com/training/articles/security-key-attestation#certificate_schema
11
- ATTESTATION_CHALLENGE_INDEX = 4
12
- SOFTWARE_ENFORCED_INDEX = 6
13
- TEE_ENFORCED_INDEX = 7
14
-
15
- def initialize(sequence)
16
- @sequence = sequence
17
- end
18
-
19
- def attestation_challenge
20
- sequence[ATTESTATION_CHALLENGE_INDEX].value
21
- end
22
-
23
- def tee_enforced
24
- @tee_enforced ||= AuthorizationList.new(sequence[TEE_ENFORCED_INDEX].value)
25
- end
26
-
27
- def software_enforced
28
- @software_enforced ||= AuthorizationList.new(sequence[SOFTWARE_ENFORCED_INDEX].value)
29
- end
30
-
31
- private
32
-
33
- attr_reader :sequence
34
- end
35
- end
36
- end
37
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "openssl"
4
- require "tpm/constants"
5
- require "tpm/s_attest"
6
- require "webauthn/attestation_statement/base"
7
-
8
- module WebAuthn
9
- module AttestationStatement
10
- class TPM < Base
11
- class CertInfo
12
- TPM_TO_OPENSSL_HASH_ALG = {
13
- ::TPM::ALG_SHA1 => "SHA1",
14
- ::TPM::ALG_SHA256 => "SHA256"
15
- }.freeze
16
-
17
- def initialize(data)
18
- @data = data
19
- end
20
-
21
- def valid?(attested_data, extra_data)
22
- s_attest.magic == ::TPM::GENERATED_VALUE &&
23
- valid_name?(attested_data) &&
24
- s_attest.extra_data.buffer == extra_data
25
- end
26
-
27
- private
28
-
29
- attr_reader :data
30
-
31
- def valid_name?(attested_data)
32
- name_hash_alg = s_attest.attested.name.buffer[0..1].unpack("n")[0]
33
- name = s_attest.attested.name.buffer[2..-1]
34
-
35
- name == OpenSSL::Digest.digest(TPM_TO_OPENSSL_HASH_ALG[name_hash_alg], attested_data)
36
- end
37
-
38
- def s_attest
39
- @s_attest ||= ::TPM::SAttest.read(data)
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "cose/algorithm"
4
- require "cose/key"
5
- require "tpm/constants"
6
- require "tpm/t_public"
7
- require "webauthn/attestation_statement/base"
8
-
9
- module WebAuthn
10
- module AttestationStatement
11
- class TPM < Base
12
- class PubArea
13
- BYTE_LENGTH = 8
14
-
15
- COSE_ECC_TO_TPM_ALG = {
16
- COSE::Algorithm.by_name("ES256").id => ::TPM::ALG_ECDSA,
17
- }.freeze
18
-
19
- COSE_RSA_TO_TPM_ALG = {
20
- COSE::Algorithm.by_name("RS256").id => ::TPM::ALG_RSASSA,
21
- COSE::Algorithm.by_name("PS256").id => ::TPM::ALG_RSAPSS,
22
- }.freeze
23
-
24
- COSE_TO_TPM_CURVE = {
25
- COSE::Key::Curve.by_name("P-256").id => ::TPM::ECC_NIST_P256
26
- }.freeze
27
-
28
- def initialize(data)
29
- @data = data
30
- end
31
-
32
- def valid?(public_key)
33
- cose_key = COSE::Key.deserialize(public_key)
34
-
35
- case cose_key
36
- when COSE::Key::EC2
37
- valid_ecc_key?(cose_key)
38
- when COSE::Key::RSA
39
- valid_rsa_key?(cose_key)
40
- else
41
- raise "Unsupported or unknown TPM key type"
42
- end
43
- end
44
-
45
- private
46
-
47
- attr_reader :data
48
-
49
- def valid_ecc_key?(cose_key)
50
- valid_symmetric? &&
51
- valid_scheme?(COSE_ECC_TO_TPM_ALG[cose_key.alg]) &&
52
- parameters.curve_id == COSE_TO_TPM_CURVE[cose_key.crv] &&
53
- unique == cose_key.x + cose_key.y
54
- end
55
-
56
- def valid_rsa_key?(cose_key)
57
- valid_symmetric? &&
58
- valid_scheme?(COSE_RSA_TO_TPM_ALG[cose_key.alg]) &&
59
- parameters.key_bits == cose_key.n.size * BYTE_LENGTH &&
60
- unique == cose_key.n
61
- end
62
-
63
- def valid_symmetric?
64
- parameters.symmetric == ::TPM::ALG_NULL
65
- end
66
-
67
- def valid_scheme?(scheme)
68
- parameters.scheme == ::TPM::ALG_NULL || parameters.scheme == scheme
69
- end
70
-
71
- def unique
72
- t_public.unique.buffer
73
- end
74
-
75
- def parameters
76
- t_public.parameters
77
- end
78
-
79
- def t_public
80
- @t_public = ::TPM::TPublic.read(data)
81
- end
82
- end
83
- end
84
- end
85
- end