Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for wasm32-pyodide #190

Merged
merged 30 commits into from
Feb 22, 2025
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1c95d7e
Added support for wasm32-pyodide
alarmfox Dec 27, 2024
4099d53
Removed match: not supported before python 3.10
alarmfox Dec 27, 2024
8f15021
Added test with good case
alarmfox Dec 27, 2024
2a721f8
Changed default parallelism=1 when platform.machine() = wasm32
alarmfox Dec 27, 2024
e1694cc
More robust platform checking
alarmfox Dec 28, 2024
fd48f0a
Testing under different platforms
alarmfox Dec 28, 2024
d3b6ecc
Added error message to UnsupportedParamsError
alarmfox Dec 30, 2024
25ca6f7
Added centralized functions for parameters validations and platform-c…
alarmfox Feb 12, 2025
23bb82b
Pre-commit
alarmfox Feb 12, 2025
a621190
Added test for PasswordHasher.from_parameters()
alarmfox Feb 12, 2025
5650323
Pre commit
alarmfox Feb 12, 2025
fb75e7d
Fix pre-commit
alarmfox Feb 12, 2025
fb5d68f
Fix test: made PasswordHasher.from_parameters easier to test
alarmfox Feb 12, 2025
d164813
Update src/argon2/exceptions.py
alarmfox Feb 12, 2025
79940f2
Merge branch 'hynek:main' into main
alarmfox Feb 12, 2025
b708393
Update tests/test_password_hasher.py
hynek Feb 22, 2025
da5c562
Update src/argon2/_password_hasher.py
hynek Feb 22, 2025
9f94cca
Update .python-version-default
hynek Feb 22, 2025
3697d8b
Update tests/test_password_hasher.py
hynek Feb 22, 2025
1e612bc
Update tests/test_password_hasher.py
hynek Feb 22, 2025
d89221f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 22, 2025
e85cc2d
Update src/argon2/profiles.py
hynek Feb 22, 2025
8cdc6d9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 22, 2025
622df98
Update src/argon2/_utils.py
hynek Feb 22, 2025
cfd873d
Update src/argon2/exceptions.py
hynek Feb 22, 2025
1959092
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 22, 2025
8361abc
Update tests/test_password_hasher.py
hynek Feb 22, 2025
c341964
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 22, 2025
86e2b27
Update tests/test_password_hasher.py
hynek Feb 22, 2025
bf70b4b
Update tests/test_password_hasher.py
hynek Feb 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions src/argon2/_password_hasher.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@

from typing import ClassVar, Literal

from ._utils import Parameters, _check_types, extract_parameters
from ._utils import (
Parameters,
_check_types,
extract_parameters,
validate_params_for_platform,
)
from .exceptions import InvalidHashError
from .low_level import Type, hash_secret, verify_secret
from .profiles import RFC_9106_LOW_MEMORY
from .profiles import get_default_params


DEFAULT_RANDOM_SALT_LENGTH = RFC_9106_LOW_MEMORY.salt_len
DEFAULT_HASH_LENGTH = RFC_9106_LOW_MEMORY.hash_len
DEFAULT_TIME_COST = RFC_9106_LOW_MEMORY.time_cost
DEFAULT_MEMORY_COST = RFC_9106_LOW_MEMORY.memory_cost
DEFAULT_PARALLELISM = RFC_9106_LOW_MEMORY.parallelism
default_params = get_default_params()

DEFAULT_RANDOM_SALT_LENGTH = default_params.salt_len
DEFAULT_HASH_LENGTH = default_params.hash_len
DEFAULT_TIME_COST = default_params.time_cost
DEFAULT_MEMORY_COST = default_params.memory_cost
DEFAULT_PARALLELISM = default_params.parallelism


def _ensure_bytes(s: bytes | str, encoding: str) -> bytes:
Expand Down Expand Up @@ -106,8 +113,7 @@ def __init__(
if e:
raise TypeError(e)

# Cache a Parameters object for check_needs_rehash.
self._parameters = Parameters(
params = Parameters(
type=type,
version=19,
salt_len=salt_len,
Expand All @@ -116,6 +122,11 @@ def __init__(
memory_cost=memory_cost,
parallelism=parallelism,
)

validate_params_for_platform(params)

# Cache a Parameters object for check_needs_rehash.
self._parameters = params
self.encoding = encoding

@classmethod
Expand All @@ -128,10 +139,15 @@ def from_parameters(cls, params: Parameters) -> PasswordHasher:

.. versionadded:: 21.2.0
"""
ph = cls()
ph._parameters = params

return ph
return cls(
time_cost=params.time_cost,
memory_cost=params.memory_cost,
parallelism=params.parallelism,
hash_len=params.hash_len,
salt_len=params.salt_len,
type=params.type,
)

@property
def time_cost(self) -> int:
Expand Down
27 changes: 26 additions & 1 deletion src/argon2/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

from __future__ import annotations

import platform
import sys

from dataclasses import dataclass
from typing import Any

from .exceptions import InvalidHashError
from .exceptions import InvalidHashError, UnsupportedParamsError
from .low_level import Type


Expand Down Expand Up @@ -35,6 +38,13 @@ def _check_types(**kw: Any) -> str | None:
return None


def _is_wasm() -> bool:
return sys.platform == "emscripten" or platform.machine() in [
"wasm32",
"wasm64",
]


def _decoded_str_len(length: int) -> int:
"""
Compute how long an encoded string of length *l* becomes.
Expand Down Expand Up @@ -147,3 +157,18 @@ def extract_parameters(hash: str) -> Parameters:
memory_cost=kvs["m"],
parallelism=kvs["p"],
)


def validate_params_for_platform(params: Parameters) -> None:
"""
Validate *params* against current platform.

Args:
params: Parameters to be validated

Returns:
None
"""
if _is_wasm() and params.parallelism != 1:
msg = "In WebAssembly environments `parallelism` must be 1."
raise UnsupportedParamsError(msg)
16 changes: 16 additions & 0 deletions src/argon2/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ class InvalidHashError(ValueError):
"""


class UnsupportedParamsError(ValueError):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class needs to be added to the API docs.

"""
Raised if the current platform does not support the parameters.

For example, in WebAssembly parallelism must be set to 1.

.. versionadded:: 25.1.0
"""

def __init__(
self,
message: str = "Params are not compatible with the current platform",
) -> None:
super().__init__(message)


InvalidHash = InvalidHashError
"""
Deprecated alias for :class:`InvalidHashError`.
Expand Down
19 changes: 18 additions & 1 deletion src/argon2/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,27 @@

from __future__ import annotations

from ._utils import Parameters
from ._utils import Parameters, _is_wasm
from .low_level import Type


def get_default_params() -> Parameters:
"""
Create default params for current platform.

Returns:
Default parameters for current platform.

.. versionadded:: 25.1.0
"""
params = RFC_9106_LOW_MEMORY

if _is_wasm():
params.parallelism = 1

return params


# FIRST RECOMMENDED option per RFC 9106.
RFC_9106_HIGH_MEMORY = Parameters(
type=Type.ID,
Expand Down
47 changes: 46 additions & 1 deletion tests/test_password_hasher.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
# SPDX-License-Identifier: MIT

from unittest import mock

import pytest

from argon2 import PasswordHasher, Type, extract_parameters, profiles
from argon2._password_hasher import _ensure_bytes
from argon2.exceptions import InvalidHash, InvalidHashError
from argon2._utils import Parameters
from argon2.exceptions import (
InvalidHash,
InvalidHashError,
UnsupportedParamsError,
)


class TestEnsureBytes:
Expand Down Expand Up @@ -151,3 +158,41 @@ def test_type_is_configurable(self):
assert Type.I is ph.type is ph._parameters.type
assert Type.I is extract_parameters(ph.hash("foo")).type
assert ph.check_needs_rehash(default_hash)

@mock.patch("sys.platform", "emscripten")
def test_params_on_wasm(self):
"""
Should fail if on wasm and parallelism > 1
"""
for machine in ["wasm32", "wasm64"]:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's make this a parametrization

with mock.patch("platform.machine", return_value=machine):
with pytest.raises(UnsupportedParamsError) as exinfo:
PasswordHasher(parallelism=2)

assert (
str(exinfo.value)
== "within wasm/wasi environments `parallelism` must be set to 1"
)

# last param is parallelism so it should fail
params = Parameters(Type.I, 2, 8, 8, 3, 256, 8)
with pytest.raises(
UnsupportedParamsError,
match="In WebAssembly environments `parallelism` must be 1.",
) as exinfo:
ph = PasswordHasher.from_parameters(params)

# explicitly correct parameters
ph = PasswordHasher(parallelism=1)

hash = ph.hash("hello")

assert ph.verify(hash, "hello") is True

# explicit, but still default parameters
default_params = profiles.get_default_params()
ph = PasswordHasher.from_parameters(default_params)

hash = ph.hash("hello")

assert ph.verify(hash, "hello") is True
Loading