Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/py/selenium/webdriver/common/virtual_authenticator.py
1864 views
1
# Licensed to the Software Freedom Conservancy (SFC) under one
2
# or more contributor license agreements. See the NOTICE file
3
# distributed with this work for additional information
4
# regarding copyright ownership. The SFC licenses this file
5
# to you under the Apache License, Version 2.0 (the
6
# "License"); you may not use this file except in compliance
7
# with the License. You may obtain a copy of the License at
8
#
9
# http://www.apache.org/licenses/LICENSE-2.0
10
#
11
# Unless required by applicable law or agreed to in writing,
12
# software distributed under the License is distributed on an
13
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
# KIND, either express or implied. See the License for the
15
# specific language governing permissions and limitations
16
# under the License.
17
18
import functools
19
from base64 import urlsafe_b64decode, urlsafe_b64encode
20
from enum import Enum
21
from typing import Any, Optional, Union
22
23
24
class Protocol(str, Enum):
25
"""Protocol to communicate with the authenticator."""
26
27
CTAP2 = "ctap2"
28
U2F = "ctap1/u2f"
29
30
31
class Transport(str, Enum):
32
"""Transport method to communicate with the authenticator."""
33
34
BLE = "ble"
35
USB = "usb"
36
NFC = "nfc"
37
INTERNAL = "internal"
38
39
40
class VirtualAuthenticatorOptions:
41
# These are so unnecessary but are now public API so we can't remove them without deprecating first.
42
# These should not be class level state in here.
43
Protocol = Protocol
44
Transport = Transport
45
46
def __init__(
47
self,
48
protocol: str = Protocol.CTAP2,
49
transport: str = Transport.USB,
50
has_resident_key: bool = False,
51
has_user_verification: bool = False,
52
is_user_consenting: bool = True,
53
is_user_verified: bool = False,
54
) -> None:
55
"""Constructor.
56
57
Initialize VirtualAuthenticatorOptions object.
58
"""
59
60
self.protocol: str = protocol
61
self.transport: str = transport
62
self.has_resident_key: bool = has_resident_key
63
self.has_user_verification: bool = has_user_verification
64
self.is_user_consenting: bool = is_user_consenting
65
self.is_user_verified: bool = is_user_verified
66
67
def to_dict(self) -> dict[str, Union[str, bool]]:
68
return {
69
"protocol": self.protocol,
70
"transport": self.transport,
71
"hasResidentKey": self.has_resident_key,
72
"hasUserVerification": self.has_user_verification,
73
"isUserConsenting": self.is_user_consenting,
74
"isUserVerified": self.is_user_verified,
75
}
76
77
78
class Credential:
79
def __init__(
80
self,
81
credential_id: bytes,
82
is_resident_credential: bool,
83
rp_id: str,
84
user_handle: Optional[bytes],
85
private_key: bytes,
86
sign_count: int,
87
):
88
"""Constructor. A credential stored in a virtual authenticator.
89
https://w3c.github.io/webauthn/#credential-parameters.
90
91
:Args:
92
- credential_id (bytes): Unique base64 encoded string.
93
- is_resident_credential (bool): Whether the credential is client-side discoverable.
94
- rp_id (str): Relying party identifier.
95
- user_handle (bytes): userHandle associated to the credential. Must be Base64 encoded string. Can be None.
96
- private_key (bytes): Base64 encoded PKCS#8 private key.
97
- sign_count (int): initial value for a signature counter.
98
"""
99
self._id = credential_id
100
self._is_resident_credential = is_resident_credential
101
self._rp_id = rp_id
102
self._user_handle = user_handle
103
self._private_key = private_key
104
self._sign_count = sign_count
105
106
@property
107
def id(self) -> str:
108
return urlsafe_b64encode(self._id).decode()
109
110
@property
111
def is_resident_credential(self) -> bool:
112
return self._is_resident_credential
113
114
@property
115
def rp_id(self) -> str:
116
return self._rp_id
117
118
@property
119
def user_handle(self) -> Optional[str]:
120
if self._user_handle:
121
return urlsafe_b64encode(self._user_handle).decode()
122
return None
123
124
@property
125
def private_key(self) -> str:
126
return urlsafe_b64encode(self._private_key).decode()
127
128
@property
129
def sign_count(self) -> int:
130
return self._sign_count
131
132
@classmethod
133
def create_non_resident_credential(cls, id: bytes, rp_id: str, private_key: bytes, sign_count: int) -> "Credential":
134
"""Creates a non-resident (i.e. stateless) credential.
135
136
:Args:
137
- id (bytes): Unique base64 encoded string.
138
- rp_id (str): Relying party identifier.
139
- private_key (bytes): Base64 encoded PKCS
140
- sign_count (int): initial value for a signature counter.
141
142
:Returns:
143
- Credential: A non-resident credential.
144
"""
145
return cls(id, False, rp_id, None, private_key, sign_count)
146
147
@classmethod
148
def create_resident_credential(
149
cls, id: bytes, rp_id: str, user_handle: Optional[bytes], private_key: bytes, sign_count: int
150
) -> "Credential":
151
"""Creates a resident (i.e. stateful) credential.
152
153
:Args:
154
- id (bytes): Unique base64 encoded string.
155
- rp_id (str): Relying party identifier.
156
- user_handle (bytes): userHandle associated to the credential. Must be Base64 encoded string.
157
- private_key (bytes): Base64 encoded PKCS
158
- sign_count (int): initial value for a signature counter.
159
160
:returns:
161
- Credential: A resident credential.
162
"""
163
return cls(id, True, rp_id, user_handle, private_key, sign_count)
164
165
def to_dict(self) -> dict[str, Any]:
166
credential_data = {
167
"credentialId": self.id,
168
"isResidentCredential": self._is_resident_credential,
169
"rpId": self.rp_id,
170
"privateKey": self.private_key,
171
"signCount": self.sign_count,
172
}
173
174
if self.user_handle:
175
credential_data["userHandle"] = self.user_handle
176
177
return credential_data
178
179
@classmethod
180
def from_dict(cls, data: dict[str, Any]) -> "Credential":
181
_id = urlsafe_b64decode(f"{data['credentialId']}==")
182
is_resident_credential = bool(data["isResidentCredential"])
183
rp_id = data["rpId"]
184
private_key = urlsafe_b64decode(f"{data['privateKey']}==")
185
sign_count = int(data["signCount"])
186
user_handle = urlsafe_b64decode(f"{data['userHandle']}==") if data.get("userHandle", None) else None
187
188
return cls(_id, is_resident_credential, rp_id, user_handle, private_key, sign_count)
189
190
def __str__(self) -> str:
191
return f"Credential(id={self.id}, is_resident_credential={self.is_resident_credential}, rp_id={self.rp_id},\
192
user_handle={self.user_handle}, private_key={self.private_key}, sign_count={self.sign_count})"
193
194
195
def required_chromium_based_browser(func):
196
"""A decorator to ensure that the client used is a chromium based
197
browser."""
198
199
@functools.wraps(func)
200
def wrapper(self, *args, **kwargs):
201
assert self.caps["browserName"].lower() not in [
202
"firefox",
203
"safari",
204
], "This only currently works in Chromium based browsers"
205
return func(self, *args, **kwargs)
206
207
return wrapper
208
209
210
def required_virtual_authenticator(func):
211
"""A decorator to ensure that the function is called with a virtual
212
authenticator."""
213
214
@functools.wraps(func)
215
@required_chromium_based_browser
216
def wrapper(self, *args, **kwargs):
217
if not self.virtual_authenticator_id:
218
raise ValueError("This function requires a virtual authenticator to be set.")
219
return func(self, *args, **kwargs)
220
221
return wrapper
222
223