Skip to content

Commit

Permalink
Disable hmac-secret by default
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Oct 15, 2024
1 parent c991e7c commit 56475d6
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 11 deletions.
23 changes: 19 additions & 4 deletions examples/hmac_secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@
Connects to the first FIDO device found which supports the HmacSecret extension,
creates a new credential for it with the extension enabled, and uses it to
derive two separate secrets.
NOTE: This extension is not enabled by default as direct access to the extension
is now allowed in a browser setting. See also prf.py for an example which uses
the PRF extension which is enabled by default.
"""
from fido2.hid import CtapHidDevice
from fido2.server import Fido2Server
from fido2.client import Fido2Client, WindowsClient, UserInteraction
from fido2.ctap2.extensions import HmacSecretExtension
from functools import partial
from getpass import getpass
import ctypes
import sys
Expand Down Expand Up @@ -70,13 +76,20 @@ def request_uv(self, permissions, rd_id):

if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin():
# Use the Windows WebAuthn API if available, and we're not running as admin
client = WindowsClient("https://example.com")
# By default only the PRF extension is allowed, we need to explicitly
# configure the client to allow hmac-secret
client = WindowsClient("https://example.com", allow_hmac_secret=True)
rk = "required" # Windows requires resident key for hmac-secret
else:
# Locate a device
for dev in enumerate_devices():
client = Fido2Client(
dev, "https://example.com", user_interaction=CliInteraction()
dev,
"https://example.com",
user_interaction=CliInteraction(),
# By default only the PRF extension is allowed, we need to explicitly
# configure the client to allow hmac-secret
extension_types=[partial(HmacSecretExtension, allow_hmac_secret=True)],
)
if "hmac-secret" in client.info.extensions:
break
Expand Down Expand Up @@ -118,7 +131,6 @@ def request_uv(self, permissions, rd_id):
print("New credential created, with the HmacSecret extension.")

# Prepare parameters for getAssertion
challenge = b"Q0hBTExFTkdF" # Use a new challenge for each call.
allow_list = [{"type": "public-key", "id": credential.credential_id}]

# Generate a salt for HmacSecret:
Expand All @@ -131,7 +143,10 @@ def request_uv(self, permissions, rd_id):

# Authenticate the credential
result = client.get_assertion(
{**request_options["publicKey"], "extensions": {"hmacGetSecret": {"salt1": salt}}}
{
**request_options["publicKey"],
"extensions": {"hmacGetSecret": {"salt1": salt}},
}
)

# Only one cred in allowCredentials, only one response.
Expand Down
5 changes: 4 additions & 1 deletion fido2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,9 +900,12 @@ def __init__(
origin: str,
verify: Callable[[str, str], bool] = verify_rp_id,
handle=None,
allow_hmac_secret=False,
):
super().__init__(origin, verify)
self.api = WinAPI(handle, return_extensions=True)
self.api = WinAPI(
handle, return_extensions=True, allow_hmac_secret=allow_hmac_secret
)
self.info = Info(
versions=["U2F_V2", "FIDO_2_0"], extensions=[], aaguid=Aaguid.NONE
)
Expand Down
7 changes: 4 additions & 3 deletions fido2/ctap2/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,14 @@ class HmacSecretExtension(Ctap2Extension):
NAME = "hmac-secret"
SALT_LEN = 32

def __init__(self, ctap, pin_protocol=None):
def __init__(self, ctap, pin_protocol=None, allow_hmac_secret=False):
super().__init__(ctap)
self.pin_protocol = pin_protocol
self._allow_hmac_secret = allow_hmac_secret

def process_create_input(self, inputs):
if self.is_supported():
if inputs.get("hmacCreateSecret") is True:
if inputs.get("hmacCreateSecret") is True and self._allow_hmac_secret:
self.prf = False
return True
elif inputs.get("prf") is not None:
Expand Down Expand Up @@ -138,7 +139,7 @@ def process_get_input(self, inputs):
self.prf = True
else:
data = inputs.get("hmacGetSecret")
if not data:
if not data or not self._allow_hmac_secret:
return
salts = data["salt1"], data.get("salt2", b"")
self.prf = False
Expand Down
7 changes: 4 additions & 3 deletions fido2/win_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,7 @@ class WinAPI:

version = WEBAUTHN_API_VERSION

def __init__(self, handle=None, return_extensions=False):
def __init__(self, handle=None, return_extensions=False, allow_hmac_secret=False):
self.handle = handle or windll.user32.GetForegroundWindow()
if not return_extensions:
warnings.warn(
Expand All @@ -949,6 +949,7 @@ def __init__(self, handle=None, return_extensions=False):
DeprecationWarning,
)
self._return_extensions = return_extensions
self._allow_hmac_secret = allow_hmac_secret

def get_error_name(self, winerror):
"""Returns an error name given an error HRESULT value.
Expand Down Expand Up @@ -1041,7 +1042,7 @@ def make_credential(
if "prf" in extensions:
enable_prf = True
win_extensions.append(WebAuthNExtension("hmac-secret", BOOL(True)))
elif "hmacCreateSecret" in extensions:
elif "hmacCreateSecret" in extensions and self._allow_hmac_secret:
win_extensions.append(WebAuthNExtension("hmac-secret", BOOL(True)))

if event:
Expand Down Expand Up @@ -1155,7 +1156,7 @@ def get_assertion(
for cred_id, salts in cred_salts.items()
],
)
elif "hmacGetSecret" in extensions:
elif "hmacGetSecret" in extensions and self._allow_hmac_secret:
flags |= 0x00100000
salts = extensions["hmacGetSecret"]
hmac_secret_salts = WebAuthNHmacSecretSaltValues(
Expand Down

0 comments on commit 56475d6

Please sign in to comment.