1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
|
# frozen_string_literal: true
require "webauthn/authenticator_data"
require "webauthn/client_data"
require "webauthn/error"
module WebAuthn
TYPES = { create: "webauthn.create", get: "webauthn.get" }.freeze
class VerificationError < Error; end
class AuthenticatorDataVerificationError < VerificationError; end
class ChallengeVerificationError < VerificationError; end
class OriginVerificationError < VerificationError; end
class RpIdVerificationError < VerificationError; end
class TokenBindingVerificationError < VerificationError; end
class TypeVerificationError < VerificationError; end
class UserPresenceVerificationError < VerificationError; end
class UserVerifiedVerificationError < VerificationError; end
class AuthenticatorResponse
def initialize(client_data_json:)
@client_data_json = client_data_json
end
def verify(expected_challenge, expected_origin = nil, user_verification: nil, rp_id: nil)
expected_origin ||= WebAuthn.configuration.origin || raise("Unspecified expected origin")
rp_id ||= WebAuthn.configuration.rp_id
verify_item(:type)
verify_item(:token_binding)
verify_item(:challenge, expected_challenge)
verify_item(:origin, expected_origin)
verify_item(:authenticator_data)
verify_item(:rp_id, rp_id || rp_id_from_origin(expected_origin))
if !WebAuthn.configuration.silent_authentication
verify_item(:user_presence)
end
if user_verification
verify_item(:user_verified)
end
true
end
def valid?(*args, **keyword_arguments)
verify(*args, **keyword_arguments)
rescue WebAuthn::VerificationError
false
end
def client_data
@client_data ||= WebAuthn::ClientData.new(client_data_json)
end
private
attr_reader :client_data_json
def verify_item(item, *args)
if send("valid_#{item}?", *args)
true
else
camelized_item = item.to_s.split('_').map { |w| w.capitalize }.join
error_const_name = "WebAuthn::#{camelized_item}VerificationError"
raise Object.const_get(error_const_name)
end
end
def valid_type?
client_data.type == type
end
def valid_token_binding?
client_data.valid_token_binding_format?
end
def valid_challenge?(expected_challenge)
OpenSSL.secure_compare(client_data.challenge, expected_challenge)
end
def valid_origin?(expected_origin)
expected_origin && (client_data.origin == expected_origin)
end
def valid_rp_id?(rp_id)
OpenSSL::Digest::SHA256.digest(rp_id) == authenticator_data.rp_id_hash
end
def valid_authenticator_data?
authenticator_data.valid?
rescue WebAuthn::AuthenticatorDataFormatError
false
end
def valid_user_presence?
authenticator_data.user_flagged?
end
def valid_user_verified?
authenticator_data.user_verified?
end
def rp_id_from_origin(expected_origin)
URI.parse(expected_origin).host
end
def type
raise NotImplementedError, "Please define #type method in subclass"
end
end
end
|