class SafetyNetAttestation::Statement

Constants

GOOGLE_ROOT_CERTIFICATES

Attributes

json[R]

Public Class Methods

new(jws_result) click to toggle source
# File lib/safety_net_attestation/statement.rb, line 23
def initialize(jws_result)
  @jws_result = jws_result
end

Public Instance Methods

advice() click to toggle source
# File lib/safety_net_attestation/statement.rb, line 77
def advice
  raise NotVerifiedError unless json

  json["advice"]&.split(",")
end
apk_certificate_digest_sha256() click to toggle source
# File lib/safety_net_attestation/statement.rb, line 65
def apk_certificate_digest_sha256
  raise NotVerifiedError unless json

  json["apkCertificateDigestSha256"]
end
apk_package_name() click to toggle source
# File lib/safety_net_attestation/statement.rb, line 59
def apk_package_name
  raise NotVerifiedError unless json

  json["apkPackageName"]
end
basic_integrity?() click to toggle source
# File lib/safety_net_attestation/statement.rb, line 53
def basic_integrity?
  raise NotVerifiedError unless json

  json["basicIntegrity"]
end
certificate_chain() click to toggle source
# File lib/safety_net_attestation/statement.rb, line 83
def certificate_chain
  raise NotVerifiedError unless json

  @certificate_chain
end
cts_profile_match?() click to toggle source
# File lib/safety_net_attestation/statement.rb, line 47
def cts_profile_match?
  raise NotVerifiedError unless json

  json["ctsProfileMatch"]
end
error() click to toggle source
# File lib/safety_net_attestation/statement.rb, line 71
def error
  raise NotVerifiedError unless json

  json["error"]
end
verify(nonce, timestamp_leeway: 60, trusted_certificates: GOOGLE_ROOT_CERTIFICATES, time: Time.now) click to toggle source
# File lib/safety_net_attestation/statement.rb, line 27
def verify(nonce, timestamp_leeway: 60, trusted_certificates: GOOGLE_ROOT_CERTIFICATES, time: Time.now)
  certificate_chain = nil
  response, _ = JWT.decode(@jws_result, nil, true, algorithms: ["ES256", "RS256"]) do |headers|
    x5c_certificates = headers["x5c"].map do |encoded|
      OpenSSL::X509::Certificate.new(Base64.strict_decode64(encoded))
    end

    certificate_chain = X5cKeyFinder.from(x5c_certificates, trusted_certificates, time: time)
    certificate_chain.first.public_key
  end

  verify_certificate_subject(certificate_chain.first)
  verify_nonce(response, nonce)
  verify_timestamp(response, timestamp_leeway, time)

  @json = response
  @certificate_chain = certificate_chain
  self
end

Private Instance Methods

verify_certificate_subject(certificate) click to toggle source
# File lib/safety_net_attestation/statement.rb, line 91
def verify_certificate_subject(certificate)
  common_name = certificate.subject.to_a.assoc("CN")

  unless common_name[1] == "attest.android.com"
    raise CertificateSubjectError
  end
end
verify_nonce(response, nonce) click to toggle source
# File lib/safety_net_attestation/statement.rb, line 99
def verify_nonce(response, nonce)
  unless OpenSSL.fixed_length_secure_compare(nonce, response["nonce"])
    raise NonceMismatchError
  end
end
verify_timestamp(response, leeway, time) click to toggle source
# File lib/safety_net_attestation/statement.rb, line 105
def verify_timestamp(response, leeway, time)
  now = time.to_f
  response_time = response["timestampMs"] / 1000.0
  unless response_time.between?(now - leeway, now + leeway)
    raise TimestampError, "not within #{leeway}s leeway"
  end
end