Class: Clarion::Authenticator

Inherits:
Object
  • Object
show all
Defined in:
lib/clarion/authenticator.rb

Defined Under Namespace

Classes: Error, InvalidAssertion, InvalidKey

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(authn, counter, store, rp_id: nil, legacy_app_id: nil) ⇒ Authenticator

Returns a new instance of Authenticator.



12
13
14
15
16
17
18
# 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

Instance Attribute Details

#authnObject (readonly)

Returns the value of attribute authn.



20
21
22
# File 'lib/clarion/authenticator.rb', line 20

def authn
  @authn
end

#counterObject (readonly)

Returns the value of attribute counter.



20
21
22
# File 'lib/clarion/authenticator.rb', line 20

def counter
  @counter
end

#legacy_app_idObject (readonly)

Returns the value of attribute legacy_app_id.



20
21
22
# File 'lib/clarion/authenticator.rb', line 20

def legacy_app_id
  @legacy_app_id
end

#rp_idObject (readonly)

Returns the value of attribute rp_id.



20
21
22
# File 'lib/clarion/authenticator.rb', line 20

def rp_id
  @rp_id
end

#storeObject (readonly)

Returns the value of attribute store.



20
21
22
# File 'lib/clarion/authenticator.rb', line 20

def store
  @store
end

Instance Method Details

#challengeObject



22
23
24
# File 'lib/clarion/authenticator.rb', line 22

def challenge
  @challenge ||= SecureRandom.random_bytes(32)
end

#credential_request_optionsObject



32
33
34
35
36
37
38
39
40
41
42
# 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:) ⇒ Object



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
# 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_extensionsObject



26
27
28
29
30
# 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