webauthn 2.1.0 → 2.4.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +113 -13
  3. data/.travis.yml +21 -18
  4. data/Appraisals +4 -0
  5. data/CHANGELOG.md +41 -0
  6. data/CONTRIBUTING.md +0 -5
  7. data/README.md +70 -8
  8. data/SECURITY.md +6 -4
  9. data/gemfiles/openssl_2_2.gemfile +7 -0
  10. data/lib/cose/rsapkcs1_algorithm.rb +50 -0
  11. data/lib/webauthn/attestation_object.rb +43 -0
  12. data/lib/webauthn/attestation_statement.rb +20 -20
  13. data/lib/webauthn/attestation_statement/android_key.rb +28 -30
  14. data/lib/webauthn/attestation_statement/android_safetynet.rb +27 -7
  15. data/lib/webauthn/attestation_statement/base.rb +108 -10
  16. data/lib/webauthn/attestation_statement/fido_u2f.rb +8 -6
  17. data/lib/webauthn/attestation_statement/none.rb +7 -1
  18. data/lib/webauthn/attestation_statement/packed.rb +13 -41
  19. data/lib/webauthn/attestation_statement/tpm.rb +38 -75
  20. data/lib/webauthn/authenticator_assertion_response.rb +3 -7
  21. data/lib/webauthn/authenticator_attestation_response.rb +19 -84
  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 +3 -0
  25. data/lib/webauthn/credential_creation_options.rb +2 -0
  26. data/lib/webauthn/credential_request_options.rb +2 -0
  27. data/lib/webauthn/fake_authenticator.rb +7 -3
  28. data/lib/webauthn/fake_authenticator/attestation_object.rb +7 -3
  29. data/lib/webauthn/fake_authenticator/authenticator_data.rb +2 -4
  30. data/lib/webauthn/fake_client.rb +19 -5
  31. data/lib/webauthn/public_key.rb +21 -2
  32. data/lib/webauthn/public_key_credential.rb +13 -3
  33. data/lib/webauthn/u2f_migrator.rb +5 -4
  34. data/lib/webauthn/version.rb +1 -1
  35. data/script/ci/install-openssl +7 -0
  36. data/script/ci/install-ruby +13 -0
  37. data/webauthn.gemspec +13 -9
  38. metadata +54 -41
  39. data/lib/android_safetynet/attestation_response.rb +0 -116
  40. data/lib/cose/rsassa_algorithm.rb +0 -10
  41. data/lib/tpm/constants.rb +0 -44
  42. data/lib/tpm/s_attest.rb +0 -26
  43. data/lib/tpm/s_attest/s_certify_info.rb +0 -14
  44. data/lib/tpm/sized_buffer.rb +0 -13
  45. data/lib/tpm/t_public.rb +0 -32
  46. data/lib/tpm/t_public/s_ecc_parms.rb +0 -17
  47. data/lib/tpm/t_public/s_rsa_parms.rb +0 -17
  48. data/lib/webauthn/attestation_statement/android_key/authorization_list.rb +0 -39
  49. data/lib/webauthn/attestation_statement/android_key/key_description.rb +0 -37
  50. data/lib/webauthn/attestation_statement/tpm/cert_info.rb +0 -44
  51. data/lib/webauthn/attestation_statement/tpm/pub_area.rb +0 -85
  52. data/lib/webauthn/signature_verifier.rb +0 -77
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "cose"
4
-
5
- RSASSAAlgorithm = Struct.new(:id, :name, :hash_function, :kty)
6
-
7
- COSE::Algorithm.register(RSASSAAlgorithm.new(-257, "RS256", "SHA256", COSE::Key::RSA::KTY_RSA))
8
- COSE::Algorithm.register(RSASSAAlgorithm.new(-258, "RS384", "SHA384", COSE::Key::RSA::KTY_RSA))
9
- COSE::Algorithm.register(RSASSAAlgorithm.new(-259, "RS512", "SHA512", COSE::Key::RSA::KTY_RSA))
10
- COSE::Algorithm.register(RSASSAAlgorithm.new(-65535, "RS1", "SHA1", COSE::Key::RSA::KTY_RSA))
data/lib/tpm/constants.rb DELETED
@@ -1,44 +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
-
23
- # https://trustedcomputinggroup.org/resource/vendor-id-registry/ section 2 "TPM Capabilities Vendor ID (CAP_VID)"
24
- VENDOR_IDS = {
25
- "id:414D4400" => "AMD",
26
- "id:41544D4C" => "Atmel",
27
- "id:4252434D" => "Broadcom",
28
- "id:49424D00" => "IBM",
29
- "id:49465800" => "Infineon",
30
- "id:494E5443" => "Intel",
31
- "id:4C454E00" => "Lenovo",
32
- "id:4E534D20" => "National Semiconductor",
33
- "id:4E545A00" => "Nationz",
34
- "id:4E544300" => "Nuvoton Technology",
35
- "id:51434F4D" => "Qualcomm",
36
- "id:534D5343" => "SMSC",
37
- "id:53544D20" => "ST Microelectronics",
38
- "id:534D534E" => "Samsung",
39
- "id:534E5300" => "Sinosun",
40
- "id:54584E00" => "Texas Instruments",
41
- "id:57454300" => "Winbond",
42
- "id:524F4343" => "Fuzhou Rockchip",
43
- }.freeze
44
- end
data/lib/tpm/s_attest.rb DELETED
@@ -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
data/lib/tpm/t_public.rb DELETED
@@ -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"
4
- require "cose/rsassa_algorithm"
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
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "cose"
4
- require "cose/rsassa_algorithm"
5
- require "openssl"
6
- require "webauthn/error"
7
-
8
- module WebAuthn
9
- class SignatureVerifier
10
- class UnsupportedAlgorithm < Error; end
11
-
12
- # This logic contained in this map constant is a candidate to be moved to cose gem domain
13
- KTY_MAP = {
14
- COSE::Key::EC2::KTY_EC2 => [OpenSSL::PKey::EC, OpenSSL::PKey::EC::Point],
15
- COSE::Key::RSA::KTY_RSA => [OpenSSL::PKey::RSA]
16
- }.freeze
17
-
18
- def initialize(algorithm, public_key)
19
- @algorithm = algorithm
20
- @public_key = public_key
21
-
22
- validate
23
- end
24
-
25
- def verify(signature, verification_data, rsa_pss_salt_length: :digest)
26
- if rsa_pss?
27
- public_key.verify_pss(cose_algorithm.hash_function, signature, verification_data,
28
- salt_length: rsa_pss_salt_length, mgf1_hash: cose_algorithm.hash_function)
29
- else
30
- public_key.verify(cose_algorithm.hash_function, signature, verification_data)
31
- end
32
- end
33
-
34
- private
35
-
36
- attr_reader :algorithm, :public_key
37
-
38
- def cose_algorithm
39
- case algorithm
40
- when COSE::Algorithm::Base
41
- algorithm
42
- else
43
- COSE::Algorithm.find(algorithm)
44
- end
45
- end
46
-
47
- # This logic is a candidate to be moved to cose gem domain
48
- def cose_key_type
49
- case cose_algorithm
50
- when COSE::Algorithm::ECDSA
51
- COSE::Key::EC2::KTY_EC2
52
- when COSE::Algorithm::RSAPSS, RSASSAAlgorithm
53
- COSE::Key::RSA::KTY_RSA
54
- else
55
- raise UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}"
56
- end
57
- end
58
-
59
- def rsa_pss?
60
- cose_algorithm.name.start_with?("PS")
61
- end
62
-
63
- def validate
64
- if !cose_algorithm
65
- raise UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}"
66
- elsif !supported_algorithms.include?(cose_algorithm.name)
67
- raise UnsupportedAlgorithm, "Unsupported algorithm #{algorithm}"
68
- elsif !KTY_MAP[cose_key_type].include?(public_key.class)
69
- raise("Incompatible algorithm and key")
70
- end
71
- end
72
-
73
- def supported_algorithms
74
- WebAuthn.configuration.algorithms
75
- end
76
- end
77
- end