lib/webauthn/attestation_statement/android_safetynet.rb
# frozen_string_literal: true require "safety_net_attestation" require "openssl" require "webauthn/attestation_statement/base" module WebAuthn module AttestationStatement # Implements https://www.w3.org/TR/webauthn-1/#sctn-android-safetynet-attestation class AndroidSafetynet < Base def valid?(authenticator_data, client_data_hash) valid_response?(authenticator_data, client_data_hash) && valid_version? && cts_profile_match? && trustworthy?(aaguid: authenticator_data.aaguid) && [attestation_type, attestation_trust_path] end private def valid_response?(authenticator_data, client_data_hash) nonce = Digest::SHA256.base64digest(authenticator_data.data + client_data_hash) begin attestation_response .verify(nonce, trusted_certificates: root_certificates(aaguid: authenticator_data.aaguid), time: time) rescue SafetyNetAttestation::Error false end end # TODO: improve once the spec has clarifications https://github.com/w3c/webauthn/issues/968 def valid_version? !statement["ver"].empty? end def cts_profile_match? attestation_response.cts_profile_match? end def valid_certificate_chain?(**_) # Already performed as part of #valid_response? true end def attestation_type WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC end # SafetyNetAttestation returns full chain including root, WebAuthn expects only the x5c certificates def certificates attestation_response.certificate_chain[0..-2] end def attestation_response @attestation_response ||= SafetyNetAttestation::Statement.new(statement["response"]) end def default_root_certificates SafetyNetAttestation::Statement::GOOGLE_ROOT_CERTIFICATES end def time Time.now end end end end