108 lines
3.4 KiB
Python
108 lines
3.4 KiB
Python
import typing as t
|
|
from typing import Any
|
|
import secrets
|
|
import warnings
|
|
from ..errors import SecurityWarning
|
|
from ..util import (
|
|
to_bytes,
|
|
urlsafe_b64decode,
|
|
urlsafe_b64encode,
|
|
)
|
|
from ..registry import KeyParameter
|
|
from .._rfc7517.models import SymmetricKey, NativeKeyBinding
|
|
from .._rfc7517.types import KeyParameters, DictKey, AnyKey
|
|
|
|
|
|
POSSIBLE_UNSAFE_KEYS = (
|
|
b"-----BEGIN ",
|
|
b"---- BEGIN ",
|
|
b"ssh-rsa ",
|
|
b"ssh-dss ",
|
|
b"ssh-ed25519 ",
|
|
b"ecdsa-sha2-",
|
|
)
|
|
|
|
|
|
class OctBinding(NativeKeyBinding):
|
|
@classmethod
|
|
def convert_raw_key_to_dict(cls, raw_key: bytes, private: bool) -> DictKey:
|
|
k = urlsafe_b64encode(raw_key).decode("utf-8")
|
|
return {"k": k}
|
|
|
|
@classmethod
|
|
def import_from_dict(cls, value: DictKey) -> bytes:
|
|
return urlsafe_b64decode(to_bytes(value["k"]))
|
|
|
|
@classmethod
|
|
def import_from_bytes(cls, value: bytes, password: Any | None = None) -> bytes:
|
|
# security check
|
|
if value.startswith(POSSIBLE_UNSAFE_KEYS):
|
|
warnings.warn("This key should not be used as an oct key", SecurityWarning)
|
|
return value
|
|
|
|
|
|
class OctKey(SymmetricKey):
|
|
"""OctKey is a symmetric key, defined by RFC7518 Section 6.4."""
|
|
|
|
key_type = "oct"
|
|
binding = OctBinding
|
|
|
|
#: https://www.rfc-editor.org/rfc/rfc7518#section-6.4
|
|
value_registry = {"k": KeyParameter("Key Value", "str", True, True)}
|
|
|
|
@classmethod
|
|
def import_key(
|
|
cls: Any,
|
|
value: AnyKey,
|
|
parameters: KeyParameters | None = None,
|
|
password: Any = None,
|
|
) -> "OctKey":
|
|
key: OctKey = super(OctKey, cls).import_key(value, parameters, password)
|
|
if not key.raw_value:
|
|
# An empty oct key material produces a deterministic HMAC digest
|
|
# that any party can reproduce, allowing trivial signature forgery
|
|
# in JWS HS256/HS384/HS512 verification. Reject outright rather
|
|
# than emitting a SecurityWarning that callers commonly suppress.
|
|
raise ValueError("oct key material must not be empty")
|
|
if len(key.raw_value) < 14:
|
|
# https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final
|
|
warnings.warn("Key size should be >= 112 bits", SecurityWarning)
|
|
return key
|
|
|
|
@classmethod
|
|
def generate_key(
|
|
cls,
|
|
key_size: int | None = 256,
|
|
parameters: KeyParameters | None = None,
|
|
private: bool = True,
|
|
auto_kid: bool = False,
|
|
) -> "OctKey":
|
|
"""Generate a ``OctKey`` with the given bit size (not bytes).
|
|
|
|
:param key_size: size in bit
|
|
:param parameters: extra parameter in JWK
|
|
:param private: must be True
|
|
:param auto_kid: add ``kid`` automatically
|
|
"""
|
|
if not private:
|
|
raise ValueError("oct key can not be generated as public")
|
|
|
|
if key_size is None:
|
|
key_size = 256
|
|
|
|
if key_size % 8 != 0:
|
|
raise ValueError("Invalid bit size for oct key")
|
|
|
|
if key_size < 112:
|
|
# https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final
|
|
warnings.warn("Key size should be >= 112 bits", SecurityWarning)
|
|
|
|
raw_key = secrets.token_bytes(key_size // 8)
|
|
key: OctKey = cls(raw_key, raw_key, parameters)
|
|
if auto_kid:
|
|
key.ensure_kid()
|
|
return key
|
|
|
|
def as_dict(self, private: bool = False, **params: t.Any) -> DictKey:
|
|
return super().as_dict(private=True, **params)
|