File: fake_authenticator.rb

package info (click to toggle)
ruby-webauthn 2.5.2-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 384 kB
  • sloc: ruby: 2,138; sh: 4; makefile: 4
file content (105 lines) | stat: -rw-r--r-- 2,927 bytes parent folder | download
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
# frozen_string_literal: true

require "cbor"
require "openssl"
require "securerandom"
require "webauthn/fake_authenticator/attestation_object"
require "webauthn/fake_authenticator/authenticator_data"

module WebAuthn
  class FakeAuthenticator
    def initialize
      @credentials = {}
    end

    def make_credential(
      rp_id:,
      client_data_hash:,
      user_present: true,
      user_verified: false,
      attested_credential_data: true,
      sign_count: nil,
      extensions: nil
    )
      credential_id, credential_key, credential_sign_count = new_credential
      sign_count ||= credential_sign_count

      credentials[rp_id] ||= {}
      credentials[rp_id][credential_id] = {
        credential_key: credential_key,
        sign_count: sign_count + 1
      }

      AttestationObject.new(
        client_data_hash: client_data_hash,
        rp_id_hash: hashed(rp_id),
        credential_id: credential_id,
        credential_key: credential_key,
        user_present: user_present,
        user_verified: user_verified,
        attested_credential_data: attested_credential_data,
        sign_count: sign_count,
        extensions: extensions
      ).serialize
    end

    def get_assertion(
      rp_id:,
      client_data_hash:,
      user_present: true,
      user_verified: false,
      aaguid: AuthenticatorData::AAGUID,
      sign_count: nil,
      extensions: nil,
      allow_credentials: nil
    )
      credential_options = credentials[rp_id]

      if credential_options
        allow_credentials ||= credential_options.keys
        credential_id = (credential_options.keys & allow_credentials).first
        unless credential_id
          raise "No matching credentials (allowed=#{allow_credentials}) " \
                "found for RP #{rp_id} among credentials=#{credential_options}"
        end

        credential = credential_options[credential_id]
        credential_key = credential[:credential_key]
        credential_sign_count = credential[:sign_count]

        authenticator_data = AuthenticatorData.new(
          rp_id_hash: hashed(rp_id),
          user_present: user_present,
          user_verified: user_verified,
          aaguid: aaguid,
          credential: nil,
          sign_count: sign_count || credential_sign_count,
          extensions: extensions
        ).serialize

        signature = credential_key.sign("SHA256", authenticator_data + client_data_hash)
        credential[:sign_count] += 1

        {
          credential_id: credential_id,
          authenticator_data: authenticator_data,
          signature: signature
        }
      else
        raise "No credentials found for RP #{rp_id}"
      end
    end

    private

    attr_reader :credentials

    def new_credential
      [SecureRandom.random_bytes(16), OpenSSL::PKey::EC.generate("prime256v1"), 0]
    end

    def hashed(target)
      OpenSSL::Digest::SHA256.digest(target)
    end
  end
end