android_key_attestation 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 80a822b17938c94650c9a22a6aa11f984774ceb095fa2f21907dffe635471f9d
4
+ data.tar.gz: 00b086c968d927e5e1c71639922255bd3e664120e8246a2be7998a28b72b17ed
5
+ SHA512:
6
+ metadata.gz: fb59d0fcba2c3c65b73a9e3500e812e7229acb9dc56a75bc981258b70c62c215215f21a6382ac31452bf5c8fdf0cabdf9aa17204ba0015280466638245d40423
7
+ data.tar.gz: 7c588c47e006d3270de6c5ebca545b9bb8468ed4974d88d4c45cc5ddd756752655c6017118ef9feebf76358fff0733ce784b431629f861466a3df6471c57907b
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,197 @@
1
+ inherit_mode:
2
+ merge:
3
+ - AllowedNames
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 2.3
7
+ DisabledByDefault: true
8
+ Exclude:
9
+ - "gemfiles/**/*"
10
+ - "vendor/**/*"
11
+
12
+ Bundler:
13
+ Enabled: true
14
+
15
+ Gemspec:
16
+ Enabled: true
17
+
18
+ Layout:
19
+ Enabled: true
20
+
21
+ Lint:
22
+ Enabled: true
23
+
24
+ Metrics/LineLength:
25
+ Max: 120
26
+
27
+ Naming:
28
+ Enabled: true
29
+
30
+ Security:
31
+ Enabled: true
32
+
33
+ Style/BlockComments:
34
+ Enabled: true
35
+
36
+ Style/BracesAroundHashParameters:
37
+ Enabled: true
38
+
39
+ Style/CaseEquality:
40
+ Enabled: true
41
+
42
+ Style/ClassAndModuleChildren:
43
+ Enabled: true
44
+
45
+ Style/ClassMethods:
46
+ Enabled: true
47
+
48
+ Style/ClassVars:
49
+ Enabled: true
50
+
51
+ Style/CommentAnnotation:
52
+ Enabled: true
53
+
54
+ Style/ConditionalAssignment:
55
+ Enabled: true
56
+
57
+ Style/DefWithParentheses:
58
+ Enabled: true
59
+
60
+ Style/Dir:
61
+ Enabled: true
62
+
63
+ Style/EachForSimpleLoop:
64
+ Enabled: true
65
+
66
+ Style/EachWithObject:
67
+ Enabled: true
68
+
69
+ Style/EmptyBlockParameter:
70
+ Enabled: true
71
+
72
+ Style/EmptyCaseCondition:
73
+ Enabled: true
74
+
75
+ Style/EmptyElse:
76
+ Enabled: true
77
+
78
+ Style/EmptyLambdaParameter:
79
+ Enabled: true
80
+
81
+ Style/EmptyLiteral:
82
+ Enabled: true
83
+
84
+ Style/EvenOdd:
85
+ Enabled: true
86
+
87
+ Style/ExpandPathArguments:
88
+ Enabled: true
89
+
90
+ Style/For:
91
+ Enabled: true
92
+
93
+ Style/FrozenStringLiteralComment:
94
+ Enabled: true
95
+
96
+ Style/GlobalVars:
97
+ Enabled: true
98
+
99
+ Style/HashSyntax:
100
+ Enabled: true
101
+
102
+ Style/IdenticalConditionalBranches:
103
+ Enabled: true
104
+
105
+ Style/IfInsideElse:
106
+ Enabled: true
107
+
108
+ Style/InverseMethods:
109
+ Enabled: true
110
+
111
+ Style/MethodCallWithoutArgsParentheses:
112
+ Enabled: true
113
+
114
+ Style/MethodDefParentheses:
115
+ Enabled: true
116
+
117
+ Style/MultilineMemoization:
118
+ Enabled: true
119
+
120
+ Style/MutableConstant:
121
+ Enabled: true
122
+
123
+ Style/NestedParenthesizedCalls:
124
+ Enabled: true
125
+
126
+ Style/OptionalArguments:
127
+ Enabled: true
128
+
129
+ Style/ParenthesesAroundCondition:
130
+ Enabled: true
131
+
132
+ Style/RedundantBegin:
133
+ Enabled: true
134
+
135
+ Style/RedundantConditional:
136
+ Enabled: true
137
+
138
+ Style/RedundantException:
139
+ Enabled: true
140
+
141
+ Style/RedundantFreeze:
142
+ Enabled: true
143
+
144
+ Style/RedundantParentheses:
145
+ Enabled: true
146
+
147
+ Style/RedundantReturn:
148
+ Enabled: true
149
+
150
+ Style/RedundantSelf:
151
+ Enabled: true
152
+
153
+ Style/Semicolon:
154
+ Enabled: true
155
+
156
+ Style/SingleLineMethods:
157
+ Enabled: true
158
+
159
+ Style/SpecialGlobalVars:
160
+ Enabled: true
161
+
162
+ Style/SymbolLiteral:
163
+ Enabled: true
164
+
165
+ Style/StringLiterals:
166
+ Enabled: true
167
+ EnforcedStyle: double_quotes
168
+
169
+ Style/TrailingBodyOnClass:
170
+ Enabled: true
171
+
172
+ Style/TrailingBodyOnMethodDefinition:
173
+ Enabled: true
174
+
175
+ Style/TrailingBodyOnModule:
176
+ Enabled: true
177
+
178
+ Style/TrailingMethodEndStatement:
179
+ Enabled: true
180
+
181
+ Style/TrivialAccessors:
182
+ Enabled: true
183
+
184
+ Style/UnneededInterpolation:
185
+ Enabled: true
186
+
187
+ Style/UnneededPercentQ:
188
+ Enabled: true
189
+
190
+ Style/UnpackFirst:
191
+ Enabled: true
192
+
193
+ Style/YodaCondition:
194
+ Enabled: true
195
+
196
+ Style/ZeroLengthPredicate:
197
+ Enabled: true
@@ -0,0 +1,20 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.6.5
6
+ - 2.5.7
7
+ - 2.4.9
8
+ - 2.3.8
9
+ before_install:
10
+ - gem install bundler
11
+ script:
12
+ - bin/rspec
13
+ jobs:
14
+ fast_finish: true
15
+ include:
16
+ - rvm: 2.6.5
17
+ name: Rubocop
18
+ script:
19
+ - bundle info rubocop
20
+ - bin/rubocop
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.3.0] - 2020-02-16
10
+ ### Added
11
+ - `Statement#verify_certificate_chain` to verify if the attestation certificate is trustworthy
12
+
13
+ ## [0.2.0] - 2019-12-31
14
+ ### Changed
15
+ - Raise `ChallengeMismatchError` if the challenge lengths are different, not `ArgumentError`
16
+
17
+ ## [0.1.0] - 2019-12-29
18
+ ### Added
19
+ - Extracted from [webauthn-ruby](https://github.com/cedarcode/webauthn-ruby) after discussion with the maintainers. Thanks for the feedback @grzuy and @brauliomartinezlm!
20
+
21
+ [Unreleased]: https://github.com/bdewater/android_key_attestation/compare/v0.3.0...HEAD
22
+ [0.3.0]: https://github.com/bdewater/android_key_attestation/compare/v0.2.0...v0.3.0
23
+ [0.2.0]: https://github.com/bdewater/android_key_attestation/compare/v0.1.0...v0.2.0
24
+ [0.1.0]: https://github.com/bdewater/android_key_attestation/releases/tag/v0.1.0
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at bartdewater@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in android_key_attestation.gemspec
6
+ gemspec
@@ -0,0 +1,59 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ android_key_attestation (0.3.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.0)
10
+ byebug (11.0.1)
11
+ coderay (1.1.2)
12
+ diff-lcs (1.3)
13
+ jaro_winkler (1.5.4)
14
+ method_source (0.9.2)
15
+ parallel (1.19.1)
16
+ parser (2.6.5.0)
17
+ ast (~> 2.4.0)
18
+ pry (0.12.2)
19
+ coderay (~> 1.1.0)
20
+ method_source (~> 0.9.0)
21
+ pry-byebug (3.7.0)
22
+ byebug (~> 11.0)
23
+ pry (~> 0.10)
24
+ rainbow (3.0.0)
25
+ rspec (3.9.0)
26
+ rspec-core (~> 3.9.0)
27
+ rspec-expectations (~> 3.9.0)
28
+ rspec-mocks (~> 3.9.0)
29
+ rspec-core (3.9.0)
30
+ rspec-support (~> 3.9.0)
31
+ rspec-expectations (3.9.0)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.9.0)
34
+ rspec-mocks (3.9.0)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.9.0)
37
+ rspec-support (3.9.0)
38
+ rubocop (0.75.0)
39
+ jaro_winkler (~> 1.5.1)
40
+ parallel (~> 1.10)
41
+ parser (>= 2.6)
42
+ rainbow (>= 2.2.2, < 4.0)
43
+ ruby-progressbar (~> 1.7)
44
+ unicode-display_width (>= 1.4.0, < 1.7)
45
+ ruby-progressbar (1.10.1)
46
+ unicode-display_width (1.6.0)
47
+
48
+ PLATFORMS
49
+ ruby
50
+
51
+ DEPENDENCIES
52
+ android_key_attestation!
53
+ bundler
54
+ pry-byebug
55
+ rspec (~> 3.8)
56
+ rubocop (= 0.75.0)
57
+
58
+ BUNDLED WITH
59
+ 1.17.3
@@ -0,0 +1,69 @@
1
+ # AndroidKeyAttestation
2
+
3
+ A Ruby gem to verify Android Key attestation statements on your server. Key attestation allows you to verify that the
4
+ cryptographic keys you use in apps are stored the a hardware keystore of an Android device.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'android_key_attestation'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle install
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install android_key_attestation
21
+
22
+ ## Usage
23
+
24
+ Request an attestation statement as described in the [Android developer documentation](https://developer.android.com/training/articles/security-key-attestation#verifying) and send the certificate chain to your server application.
25
+
26
+ In your server application code, do the following:
27
+
28
+ ```ruby
29
+ require "android_key_attestation"
30
+
31
+ statement = AndroidKeyAttestation::Statement.new(certificates)
32
+
33
+ # Verify the attestation certificate was issued for the challenge you generated
34
+ begin
35
+ statement.verify(challenge)
36
+ rescue AndroidKeyAttestation::ChallengeMismatchError => e
37
+ # abort
38
+ end
39
+
40
+ # Inspect properties of the attestation certificate belonging to the generated key pair, see
41
+ # https://developer.android.com/training/articles/security-key-attestation#certificate_schema_keydescription
42
+ # for more details. The gem uses snake_case versions of the lowerCamelCase names in the above link.
43
+ statement.attestation_version
44
+ # => 3
45
+ statement.attestation_security_level
46
+ # => :trusted_environment
47
+ statement.tee_enforced.purpose
48
+ # => [:sign, :verify]
49
+ statement.tee_enforced.origin
50
+ # => :generated
51
+ statement.software_enforced.creation_date
52
+ # => 2018-07-29 08:31:54 -0400
53
+ ```
54
+
55
+ ## Development
56
+
57
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
58
+
59
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
60
+
61
+ ## Contributing
62
+
63
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bdewater/android_key_attestation. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
64
+
65
+ ## License
66
+
67
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
68
+
69
+ The gem and its authors are unaffiliated with Google.
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/android_key_attestation/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "android_key_attestation"
7
+ spec.version = AndroidKeyAttestation::VERSION
8
+ spec.authors = ["Bart de Water"]
9
+
10
+ spec.summary = "Android key attestation verification"
11
+ spec.homepage = "https://github.com/bdewater/android_key_attestation"
12
+ spec.license = "MIT"
13
+
14
+ if spec.respond_to?(:metadata)
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = spec.homepage
17
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
18
+ end
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.required_ruby_version = ">= 2.3"
30
+
31
+ spec.add_development_dependency "bundler"
32
+ spec.add_development_dependency "pry-byebug"
33
+ spec.add_development_dependency "rspec", "~> 3.8"
34
+ spec.add_development_dependency "rubocop", "0.75.0"
35
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "android_key_attestation"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("bundle", __dir__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rspec-core", "rspec")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("bundle", __dir__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rubocop", "rubocop")
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AndroidKeyAttestation
4
+ class Error < StandardError; end
5
+ class ExtensionMissingError < Error; end
6
+ class ChallengeMismatchError < Error; end
7
+ class CertificateVerificationError < Error; end
8
+
9
+ GEM_ROOT = File.expand_path(__dir__)
10
+ end
11
+
12
+ require_relative "android_key_attestation/statement"
13
+ require_relative "android_key_attestation/version"
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AndroidKeyAttestation
4
+ class AuthorizationList
5
+ # https://source.android.com/security/keystore/attestation#attestation-extension
6
+ PURPOSE_TAG = 1
7
+ ALGORITHM_TAG = 2
8
+ KEY_SIZE_TAG = 3
9
+ DIGEST_TAG = 5
10
+ PADDING_TAG = 6
11
+ EC_CURVE_TAG = 10
12
+ RSA_PUBLIC_EXPONENT_TAG = 200
13
+ ROLLBACK_RESISTANCE_TAG = 303
14
+ ACTIVE_DATE_TIME_TAG = 400
15
+ ORIGINATION_EXPIRE_DATE_TIME_TAG = 401
16
+ USAGE_EXPIRE_DATE_TIME_TAG = 402
17
+ NO_AUTH_REQUIRED_TAG = 503
18
+ USER_AUTH_TYPE_TAG = 504
19
+ AUTH_TIMEOUT_TAG = 505
20
+ ALLOW_WHILE_ON_BODY_TAG = 506
21
+ TRUSTED_USER_PRESENCE_REQUIRED_TAG = 507
22
+ TRUSTED_CONFIRMATION_REQUIRED_TAG = 508
23
+ UNLOCK_DEVICE_REQUIRED_TAG = 509
24
+ ALL_APPLICATIONS_TAG = 600
25
+ APPLICATION_ID_TAG = 601
26
+ CREATION_DATE_TIME_TAG = 701
27
+ ORIGIN_TAG = 702
28
+ ROOT_OF_TRUST_TAG = 704
29
+ OS_VERSION_TAG = 705
30
+ OS_PATCH_LEVEL_TAG = 706
31
+ ATTESTATION_APPLICATION_ID_TAG = 709
32
+ ATTESTATION_ID_BRAND_TAG = 710
33
+ ATTESTATION_ID_DEVICE_TAG = 711
34
+ ATTESTATION_ID_PRODUCT_TAG = 712
35
+ ATTESTATION_ID_SERIAL_TAG = 713
36
+ ATTESTATION_ID_IMEI_TAG = 714
37
+ ATTESTATION_ID_MEID_TAG = 715
38
+ ATTESTATION_ID_MANUFACTURER_TAG = 716
39
+ ATTESTATION_ID_MODEL_TAG = 717
40
+ VENDOR_PATCH_LEVEL_TAG = 718
41
+ BOOT_PATCH_LEVEL_TAG = 719
42
+
43
+ # https://source.android.com/security/keystore/tags
44
+ PURPOSE_ENUM = {
45
+ 0 => :encrypt,
46
+ 1 => :decrypt,
47
+ 2 => :sign,
48
+ 3 => :verify,
49
+ 4 => :derive_key,
50
+ 5 => :wrap_key,
51
+ }.freeze
52
+ ORIGIN_ENUM = {
53
+ 0 => :generated,
54
+ 1 => :derived,
55
+ 2 => :imported,
56
+ 3 => :unknown,
57
+ }.freeze
58
+
59
+ def initialize(sequence)
60
+ @sequence = sequence
61
+ end
62
+
63
+ def purpose
64
+ integers = find_optional_integer_set(PURPOSE_TAG)
65
+ integers&.map { |i| PURPOSE_ENUM.fetch(i) }
66
+ end
67
+
68
+ def all_applications
69
+ find_boolean(ALL_APPLICATIONS_TAG)
70
+ end
71
+
72
+ def creation_date
73
+ find_time_milliseconds(CREATION_DATE_TIME_TAG)
74
+ end
75
+
76
+ def origin
77
+ integer = find_optional_integer(ORIGIN_TAG)
78
+ ORIGIN_ENUM.fetch(integer) if integer
79
+ end
80
+
81
+ def find_by_tag(tag)
82
+ sequence.detect { |data| data.tag == tag }
83
+ end
84
+
85
+ private
86
+
87
+ attr_reader :sequence
88
+
89
+ def find_optional_integer_set(tag)
90
+ value = find_by_tag(tag)&.value&.at(0)&.value
91
+ value&.map { |asn1_int| asn1_int.value.to_i }
92
+ end
93
+
94
+ def find_optional_integer(tag)
95
+ find_by_tag(tag)&.value&.at(0)&.value&.to_i
96
+ end
97
+
98
+ def find_boolean(tag)
99
+ find_by_tag(tag)&.value || false
100
+ end
101
+
102
+ def find_time_milliseconds(tag)
103
+ value = find_by_tag(tag)&.value&.at(0)&.value
104
+ Time.at(value.to_i / 1000) if value
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module AndroidKeyAttestation
6
+ module FixedLengthSecureCompare
7
+ unless OpenSSL.singleton_class.method_defined?(:fixed_length_secure_compare)
8
+ refine OpenSSL.singleton_class do
9
+ def fixed_length_secure_compare(a, b) # rubocop:disable Naming/UncommunicativeMethodParamName
10
+ raise ArgumentError, "inputs must be of equal length" unless a.bytesize == b.bytesize
11
+
12
+ # borrowed from Rack::Utils
13
+ l = a.unpack("C*")
14
+ r, i = 0, -1
15
+ b.each_byte { |v| r |= v ^ l[i += 1] }
16
+ r == 0
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
3
+ BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYy
4
+ ODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B
5
+ AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS
6
+ Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7
7
+ tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj
8
+ nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq
9
+ C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ
10
+ oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O
11
+ JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg
12
+ sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi
13
+ igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M
14
+ RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E
15
+ aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um
16
+ AGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYD
17
+ VR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAO
18
+ BgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lk
19
+ Lmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQAD
20
+ ggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB
21
+ Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00m
22
+ qC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rY
23
+ DBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPm
24
+ QUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4u
25
+ JU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyD
26
+ CdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79Iy
27
+ ZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxD
28
+ qwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23Uaic
29
+ MDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1
30
+ wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk
31
+ -----END CERTIFICATE-----
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "authorization_list"
4
+
5
+ module AndroidKeyAttestation
6
+ class KeyDescription
7
+ # https://developer.android.com/training/articles/security-key-attestation#certificate_schema
8
+ ATTESTATION_VERSION_INDEX = 0
9
+ ATTESTATION_SECURITY_LEVEL_INDEX = 1
10
+ KEYMASTER_VERSION_INDEX = 2
11
+ KEYMASTER_SECURITY_LEVEL_INDEX = 3
12
+ ATTESTATION_CHALLENGE_INDEX = 4
13
+ UNIQUE_ID_INDEX = 5
14
+ SOFTWARE_ENFORCED_INDEX = 6
15
+ TEE_ENFORCED_INDEX = 7
16
+
17
+ SECURITY_LEVEL_ENUM = {
18
+ 0 => :software,
19
+ 1 => :trusted_environment,
20
+ 2 => :strong_box
21
+ }.freeze
22
+
23
+ def initialize(sequence)
24
+ @sequence = sequence
25
+ end
26
+
27
+ def attestation_version
28
+ Integer(sequence[ATTESTATION_VERSION_INDEX].value)
29
+ end
30
+
31
+ def attestation_security_level
32
+ SECURITY_LEVEL_ENUM.fetch(Integer(sequence[ATTESTATION_SECURITY_LEVEL_INDEX].value))
33
+ end
34
+
35
+ def keymaster_version
36
+ Integer(sequence[KEYMASTER_VERSION_INDEX].value)
37
+ end
38
+
39
+ def keymaster_security_level
40
+ SECURITY_LEVEL_ENUM.fetch(Integer(sequence[KEYMASTER_SECURITY_LEVEL_INDEX].value))
41
+ end
42
+
43
+ def attestation_challenge
44
+ sequence[ATTESTATION_CHALLENGE_INDEX].value
45
+ end
46
+
47
+ def unique_id
48
+ sequence[UNIQUE_ID_INDEX].value
49
+ end
50
+
51
+ def tee_enforced
52
+ @tee_enforced ||= AuthorizationList.new(sequence[TEE_ENFORCED_INDEX].value)
53
+ end
54
+
55
+ def software_enforced
56
+ @software_enforced ||= AuthorizationList.new(sequence[SOFTWARE_ENFORCED_INDEX].value)
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :sequence
62
+ end
63
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "openssl"
5
+ require_relative "key_description"
6
+ require_relative "fixed_length_secure_compare"
7
+
8
+ module AndroidKeyAttestation
9
+ class Statement
10
+ EXTENSION_DATA_OID = "1.3.6.1.4.1.11129.2.1.17"
11
+ GOOGLE_ROOT_CERTIFICATES = begin
12
+ file = File.read(File.join(GEM_ROOT, "android_key_attestation", "google_hardware_attestation_root.pem"))
13
+ [OpenSSL::X509::Certificate.new(file)]
14
+ end.freeze
15
+
16
+ extend Forwardable
17
+ def_delegators :key_description, :attestation_version, :attestation_security_level, :keymaster_version,
18
+ :keymaster_security_level, :unique_id, :tee_enforced, :software_enforced
19
+
20
+ using FixedLengthSecureCompare
21
+
22
+ def initialize(*certificates)
23
+ @certificates = certificates
24
+ end
25
+
26
+ def attestation_certificate
27
+ @certificates.first
28
+ end
29
+
30
+ def verify_challenge(challenge)
31
+ attestation_challenge = key_description.attestation_challenge
32
+ attestation_challenge.bytesize == challenge.bytesize &&
33
+ OpenSSL.fixed_length_secure_compare(attestation_challenge, challenge) ||
34
+ raise(ChallengeMismatchError)
35
+ end
36
+
37
+ def verify_certificate_chain(root_certificates: GOOGLE_ROOT_CERTIFICATES, time: Time.now)
38
+ store = OpenSSL::X509::Store.new
39
+ root_certificates.each { |cert| store.add_cert(cert) }
40
+ store.time = time
41
+
42
+ store.verify(attestation_certificate, @certificates[1..-1]) ||
43
+ raise(CertificateVerificationError, store.error_string)
44
+ end
45
+
46
+ def key_description
47
+ @key_description ||= begin
48
+ extension_data = attestation_certificate.extensions.detect { |ext| ext.oid == EXTENSION_DATA_OID }
49
+ raise AndroidKeyAttestation::ExtensionMissingError unless extension_data
50
+
51
+ raw_key_description = OpenSSL::ASN1.decode(extension_data).value.last
52
+ KeyDescription.new(OpenSSL::ASN1.decode(raw_key_description.value).value)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AndroidKeyAttestation
4
+ VERSION = "0.3.0"
5
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: android_key_attestation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Bart de Water
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-02-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry-byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
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: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.75.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.75.0
69
+ description:
70
+ email:
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - ".gitignore"
76
+ - ".rspec"
77
+ - ".rubocop.yml"
78
+ - ".travis.yml"
79
+ - CHANGELOG.md
80
+ - CODE_OF_CONDUCT.md
81
+ - Gemfile
82
+ - Gemfile.lock
83
+ - README.md
84
+ - Rakefile
85
+ - android_key_attestation.gemspec
86
+ - bin/console
87
+ - bin/rspec
88
+ - bin/rubocop
89
+ - bin/setup
90
+ - lib/android_key_attestation.rb
91
+ - lib/android_key_attestation/authorization_list.rb
92
+ - lib/android_key_attestation/fixed_length_secure_compare.rb
93
+ - lib/android_key_attestation/google_hardware_attestation_root.pem
94
+ - lib/android_key_attestation/key_description.rb
95
+ - lib/android_key_attestation/statement.rb
96
+ - lib/android_key_attestation/version.rb
97
+ homepage: https://github.com/bdewater/android_key_attestation
98
+ licenses:
99
+ - MIT
100
+ metadata:
101
+ homepage_uri: https://github.com/bdewater/android_key_attestation
102
+ source_code_uri: https://github.com/bdewater/android_key_attestation
103
+ changelog_uri: https://github.com/bdewater/android_key_attestation/blob/master/CHANGELOG.md
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '2.3'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubygems_version: 3.0.3
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Android key attestation verification
123
+ test_files: []