webauthn 2.1.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
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