class Clarion::Authenticator

Attributes

authn[R]
counter[R]
legacy_app_id[R]
rp_id[R]
store[R]

Public Class Methods

new(authn, counter, store, rp_id: nil, legacy_app_id: nil) click to toggle source
# File lib/clarion/authenticator.rb, line 12
def initialize(authn, counter, store, rp_id: nil, legacy_app_id: nil)
  @authn = authn
  @counter = counter
  @store = store
  @rp_id = rp_id
  @legacy_app_id = legacy_app_id
end

Public Instance Methods

challenge() click to toggle source
# File lib/clarion/authenticator.rb, line 22
def challenge
  @challenge ||= SecureRandom.random_bytes(32)
end
credential_request_options() click to toggle source
# File lib/clarion/authenticator.rb, line 32
def credential_request_options
  {
    publicKey: {
      timeout: 60000,
      # Convert to ArrayBuffer in sign.js
      challenge: challenge.each_byte.map(&:ord),
      allowCredentials: authn.keys.map { |_| {type: 'public-key', id: Base64.urlsafe_decode64(_.handle).each_byte.map(&:ord)} },
      extensions: webauthn_request_extensions,
    }
  }
end
verify!(challenge: self.challenge(), origin:, extension_results: {}, credential_id:, authenticator_data:, client_data_json:, signature:) click to toggle source
# File lib/clarion/authenticator.rb, line 44
def verify!(challenge: self.challenge(), origin:, extension_results: {}, credential_id:, authenticator_data:, client_data_json:, signature:)
  assertion = WebAuthn::AuthenticatorAssertionResponse.new(
    credential_id: credential_id,
    authenticator_data: authenticator_data,
    client_data_json: client_data_json,
    signature: signature,
  )

  key = authn.verify_by_handle(credential_id)
  unless key
    raise Authenticator::InvalidKey
  end

  rp_id = extension_results&.fetch('appid', extension_results&.fetch(:appid, false)) ? legacy_app_id : self.rp_id()
  allowed_credentials = authn.keys.map { |_|  {id: _.handle, public_key: _.public_key_bytes} }
  unless assertion.valid?(challenge, origin, rp_id: rp_id, allowed_credentials: allowed_credentials)
    raise Authenticator::InvalidAssertion, "invalid assertion"
  end

  sign_count = assertion.authenticator_data.sign_count
  last_sign_count = counter ? counter.get(key) : 0

  if sign_count <= last_sign_count
    raise Authenticator::InvalidAssertion, "sign_count is decreased"
  end

  key.counter = sign_count

  counter.store(key) if counter
  store.store_authn(authn)
  true
end
webauthn_request_extensions() click to toggle source
# File lib/clarion/authenticator.rb, line 26
def webauthn_request_extensions
  {}.tap do |e|
    e[:appid] = legacy_app_id if legacy_app_id
  end
end