-
Notifications
You must be signed in to change notification settings - Fork 61
/
Copy pathfake_client.rb
167 lines (142 loc) · 4.51 KB
/
fake_client.rb
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# frozen_string_literal: true
require "openssl"
require "securerandom"
require "webauthn/authenticator_data"
require "webauthn/encoder"
require "webauthn/fake_authenticator"
module WebAuthn
class FakeClient
TYPES = { create: "webauthn.create", get: "webauthn.get" }.freeze
attr_reader :origin, :token_binding, :encoding
def initialize(
origin = fake_origin,
token_binding: nil,
authenticator: WebAuthn::FakeAuthenticator.new,
encoding: WebAuthn.configuration.encoding
)
@origin = origin
@token_binding = token_binding
@authenticator = authenticator
@encoding = encoding
end
def create(
challenge: fake_challenge,
rp_id: nil,
user_present: true,
user_verified: false,
backup_eligibility: false,
backup_state: false,
attested_credential_data: true,
credential_algorithm: nil,
extensions: nil
)
rp_id ||= URI.parse(origin).host
client_data_json = data_json_for(:create, encoder.decode(challenge))
client_data_hash = hashed(client_data_json)
attestation_object = authenticator.make_credential(
rp_id: rp_id,
client_data_hash: client_data_hash,
user_present: user_present,
user_verified: user_verified,
backup_eligibility: backup_eligibility,
backup_state: backup_state,
attested_credential_data: attested_credential_data,
algorithm: credential_algorithm,
extensions: extensions
)
id =
if attested_credential_data
WebAuthn::AuthenticatorData
.deserialize(CBOR.decode(attestation_object)["authData"])
.attested_credential_data
.id
else
"id-for-pk-without-attested-credential-data"
end
{
"type" => "public-key",
"id" => internal_encoder.encode(id),
"rawId" => encoder.encode(id),
"authenticatorAttachment" => 'platform',
"clientExtensionResults" => extensions,
"response" => {
"attestationObject" => encoder.encode(attestation_object),
"clientDataJSON" => encoder.encode(client_data_json),
"transports" => ["internal"],
}
}
end
def get(challenge: fake_challenge,
rp_id: nil,
user_present: true,
user_verified: false,
backup_eligibility: false,
backup_state: true,
sign_count: nil,
extensions: nil,
user_handle: nil,
allow_credentials: nil)
rp_id ||= URI.parse(origin).host
client_data_json = data_json_for(:get, encoder.decode(challenge))
client_data_hash = hashed(client_data_json)
if allow_credentials
allow_credentials = allow_credentials.map { |credential| encoder.decode(credential) }
end
assertion = authenticator.get_assertion(
rp_id: rp_id,
client_data_hash: client_data_hash,
user_present: user_present,
user_verified: user_verified,
backup_eligibility: backup_eligibility,
backup_state: backup_state,
sign_count: sign_count,
extensions: extensions,
allow_credentials: allow_credentials
)
{
"type" => "public-key",
"id" => internal_encoder.encode(assertion[:credential_id]),
"rawId" => encoder.encode(assertion[:credential_id]),
"clientExtensionResults" => extensions,
"authenticatorAttachment" => 'platform',
"response" => {
"clientDataJSON" => encoder.encode(client_data_json),
"authenticatorData" => encoder.encode(assertion[:authenticator_data]),
"signature" => encoder.encode(assertion[:signature]),
"userHandle" => user_handle ? encoder.encode(user_handle) : nil
}
}
end
private
attr_reader :authenticator
def data_json_for(method, challenge)
data = {
type: type_for(method),
challenge: internal_encoder.encode(challenge),
origin: origin
}
if token_binding
data[:tokenBinding] = token_binding
end
data.to_json
end
def encoder
@encoder ||= WebAuthn::Encoder.new(encoding)
end
def internal_encoder
WebAuthn.standard_encoder
end
def hashed(data)
OpenSSL::Digest::SHA256.digest(data)
end
def fake_challenge
encoder.encode(SecureRandom.random_bytes(32))
end
def fake_origin
"http://localhost#{rand(1000)}.test"
end
def type_for(method)
TYPES[method]
end
end
end