safety_net_attestation 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/CHANGELOG.md +10 -3
- data/Gemfile.lock +1 -1
- data/lib/safety_net_attestation/statement.rb +14 -6
- data/lib/safety_net_attestation/version.rb +1 -1
- data/lib/safety_net_attestation/x5c_key_finder.rb +3 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ccbadd15213737c97c7380d54fbf0b742f33aec05897ae6e7c2f51fe114f487
|
4
|
+
data.tar.gz: e0fe5abd1f16f7b084b53431108defbc1d534d8061ec7d0363674b0ad207fd08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2f0413cb1b611f2bb0319da23002edb5f695119badb401fdef6af4e225e9a5cd778af31e0cf4af4ab228fca8c322763eb806d35ef21fa29cd7080edddbc318c
|
7
|
+
data.tar.gz: 6327628183000d2110361284af325cc210f7d45da9af7da755cde2b108e9ac4858e40b6028b71f46c864be58b3bb72736dab8a5cf0bdc1f1a6235745f44b187b
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [0.3.0] - 2019-12-29
|
10
|
+
### Added
|
11
|
+
- `Statement#certificates` exposes the certificate chain used during verification
|
12
|
+
- `Statement#verify` takes an optional `time` argument, defaulting to the current time. This can be used for testing
|
13
|
+
captured statements from real devices without needing stubbing.
|
14
|
+
|
9
15
|
## [0.2.0] - 2019-12-28
|
10
16
|
### Fixed
|
11
17
|
- Fixed loading bundled root certificates
|
@@ -14,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
14
20
|
### Added
|
15
21
|
- Extracted from [webauthn-ruby](https://github.com/cedarcode/webauthn-ruby) after discussion with the maintainers. Thanks for the feedback @grzuy and @brauliomartinezlm!
|
16
22
|
|
17
|
-
[Unreleased]: https://github.com/bdewater/
|
18
|
-
[0.
|
19
|
-
[0.
|
23
|
+
[Unreleased]: https://github.com/bdewater/safety_net_attestation/compare/v0.1.0...HEAD
|
24
|
+
[0.3.0]: https://github.com/bdewater/safety_net_attestation/compare/v0.2.0...v0.3.0
|
25
|
+
[0.2.0]: https://github.com/bdewater/safety_net_attestation/compare/v0.1.0...v0.2.0
|
26
|
+
[0.1.0]: https://github.com/bdewater/safety_net_attestation/releases/tag/v0.1.0
|
data/Gemfile.lock
CHANGED
@@ -24,21 +24,23 @@ module SafetyNetAttestation
|
|
24
24
|
@jws_result = jws_result
|
25
25
|
end
|
26
26
|
|
27
|
-
def verify(nonce, timestamp_leeway: 60, trusted_certificates: GOOGLE_ROOT_CERTIFICATES)
|
27
|
+
def verify(nonce, timestamp_leeway: 60, trusted_certificates: GOOGLE_ROOT_CERTIFICATES, time: Time.now)
|
28
28
|
certificates = nil
|
29
29
|
response, _ = JWT.decode(@jws_result, nil, true, algorithms: ["ES256", "RS256"]) do |headers|
|
30
|
-
|
30
|
+
x5c_certificates = headers["x5c"].map do |encoded|
|
31
31
|
OpenSSL::X509::Certificate.new(Base64.strict_decode64(encoded))
|
32
32
|
end
|
33
33
|
|
34
|
-
X5cKeyFinder.from(
|
34
|
+
certificates = X5cKeyFinder.from(x5c_certificates, trusted_certificates, time: time)
|
35
|
+
certificates.first.public_key
|
35
36
|
end
|
36
37
|
|
37
38
|
verify_certificate_subject(certificates.first)
|
38
39
|
verify_nonce(response, nonce)
|
39
|
-
verify_timestamp(response, timestamp_leeway)
|
40
|
+
verify_timestamp(response, timestamp_leeway, time)
|
40
41
|
|
41
42
|
@json = response
|
43
|
+
@certificates = certificates
|
42
44
|
self
|
43
45
|
end
|
44
46
|
|
@@ -78,6 +80,12 @@ module SafetyNetAttestation
|
|
78
80
|
json["advice"]&.split(",")
|
79
81
|
end
|
80
82
|
|
83
|
+
def certificates
|
84
|
+
raise NotVerifiedError unless json
|
85
|
+
|
86
|
+
@certificates
|
87
|
+
end
|
88
|
+
|
81
89
|
private
|
82
90
|
|
83
91
|
def verify_certificate_subject(certificate)
|
@@ -94,8 +102,8 @@ module SafetyNetAttestation
|
|
94
102
|
end
|
95
103
|
end
|
96
104
|
|
97
|
-
def verify_timestamp(response, leeway)
|
98
|
-
now =
|
105
|
+
def verify_timestamp(response, leeway, time)
|
106
|
+
now = time.to_f
|
99
107
|
response_time = response["timestampMs"] / 1000.0
|
100
108
|
unless response_time.between?(now - leeway, now + leeway)
|
101
109
|
raise TimestampError, "not within #{leeway}s leeway"
|
@@ -7,15 +7,16 @@ module SafetyNetAttestation
|
|
7
7
|
class SignatureError < Error; end
|
8
8
|
|
9
9
|
class X5cKeyFinder
|
10
|
-
def self.from(x5c_certificates, trusted_certificates)
|
10
|
+
def self.from(x5c_certificates, trusted_certificates, time: Time.now)
|
11
11
|
store = OpenSSL::X509::Store.new
|
12
12
|
trusted_certificates.each { |certificate| store.add_cert(certificate) }
|
13
13
|
|
14
14
|
signing_certificate, *certificate_chain = x5c_certificates
|
15
15
|
store_context = OpenSSL::X509::StoreContext.new(store, signing_certificate, certificate_chain)
|
16
|
+
store_context.time = time
|
16
17
|
|
17
18
|
if store_context.verify
|
18
|
-
|
19
|
+
store_context.chain
|
19
20
|
else
|
20
21
|
error = "Certificate verification failed: #{store_context.error_string}."
|
21
22
|
error = "#{error} Certificate subject: #{store_context.current_cert.subject}." if store_context.current_cert
|