Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gh-91258: Linux Kernel CryptoAPI bindings #32173

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
159 changes: 158 additions & 1 deletion Lib/hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ def __get_builtin_constructor(name):
elif name in {'SHA256', 'sha256', 'SHA224', 'sha224'}:
import _sha256
cache['SHA224'] = cache['sha224'] = _sha256.sha224
cache['SHA256'] = cache['sha256'] = _sha256.sha256
# cache['SHA256'] = cache['sha256'] = _sha256.sha256
cache['SHA256'] = cache['sha256'] = linux_sha256
elif name in {'SHA512', 'sha512', 'SHA384', 'sha384'}:
import _sha512
cache['SHA384'] = cache['sha384'] = _sha512.sha384
Expand Down Expand Up @@ -192,6 +193,162 @@ def __hash_new(name, data=b'', **kwargs):
pass


import socket as _socket
import binascii as _binascii
import warnings as _warnings

class _LinuxKCAPI:
"""Linux Kernel Crypto API (AF_ALG socket)
"""

# TODO: retrieve info from AF_NETLINK, NETLINK_CRYPTO
_digests = {
'md5': (16, 64),
'sha1': (20, 64),
'sha224': (28, 64),
'sha256': (32, 64),
'sha384': (48, 128),
'sha512': (64, 128),
}
__slots__ = ("_digest", "_kcapi_sock")

def __init__(self, digest):
digest = digest.lower() # SHA256 -> sha256
if digest not in self._digests:
raise ValueError(f"Kernel Crypto API does not support {digest}.")
self._digest = digest

def __getstate__(self):
raise TypeError(f"cannot pickle {self.__class__.__name__!r} object")

def __del__(self):
if getattr(self, "_kcapi_sock", None):
self._kcapi_sock.close()
self._kcapi_sock = None

def _get_kcapi_sock(self, digest, mac_key=None):
"""Create KCAPI client socket

Algorithm and MAC key are configured on the server socket. accept()
creates a new client socket that consumes data. accept() on a client
socket creates an independent copy.

https://www.kernel.org/doc/html/v5.17/crypto/userspace-if.html
"""
with _socket.socket(_socket.AF_ALG, _socket.SOCK_SEQPACKET, 0) as cfg:
algo = digest if mac_key is None else f"hmac({digest})"
binding = ("hash", algo)
try:
cfg.bind(binding)
except FileNotFoundError:
raise ValueError(
f"Kernel Crypto API does not support {algo}."
)
if mac_key is not None:
# Linux Kernel 5.17 docs are incorrect. AF_ALG setsockopt()
# requires a non-connected 'server' socket.
# if (sock->state == SS_CONNECTED) return ENOPROTOOPT;
cfg.setsockopt(_socket.SOL_ALG, _socket.ALG_SET_KEY, mac_key)
return cfg.accept()[0]

def _digest_by_digestmod(self, digestmod):
"""Get digest object and name
"""
if isinstance(digestmod, str):
return None, digestmod
elif callable(digestmod):
if _hashlib is not None:
# _hashopenssl.c constructor?
try:
return None, _hashlib._constructors[digestmod]
except KeyError:
pass
# callable
digestobj = digestmod()
return digestobj, digestobj.name
else:
# digest module with new() function
digestobj = digestmod.new()
return digestobj, digestobj.name

def __repr__(self):
return f"<Linux KCAPI {self.name} at 0x{id(self):x}>"

@property
def digest_size(self):
return self._digests[self._digest][0]

@property
def block_size(self):
return self._digests[self._digest][1]

def update(self, data):
self._kcapi_sock.sendall(data, _socket.MSG_MORE)

def copy(self):
new = self.__new__(type(self))
new._digest = self._digest
new._kcapi_sock = self._kcapi_sock.accept()[0]
return new

def digest(self):
copysock = self._kcapi_sock.accept()[0]
with copysock:
copysock.send(b'')
return copysock.recv(64)

def hexdigest(self):
return _binascii.hexlify(self.digest()).decode("ascii")


class _LinuxKCAPIHash(_LinuxKCAPI):
def __init__(self, name, data=None, usedforsecurity=True):
super().__init__(name)
# ignores usedforsecurity
self._kcapi_sock = self._get_kcapi_sock(name)
if data is not None:
self.update(data)

@property
def name(self):
return self._digest


class _LinuxKCAPIHMAC(_LinuxKCAPI):
def __init__(self, key, msg=None, digestmod=''):
if not isinstance(key, (bytes, bytearray)):
raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)

if not digestmod:
raise TypeError("Missing required parameter 'digestmod'.")
digestobj, name = self._digest_by_digestmod(digestmod)
super().__init__(name)
if digestobj:
if not hasattr(digestobj, "block_size"):
_warnings.warn(
"No block_size attribute on given digest object.",
RuntimeWarning, 2
)
elif digestobj.block_size != self.block_size:
_warnings.warn(
f"digest object block_size {digestobj.block_size} does "
f"not match KCAPI block_size {self.block_size}.",
RuntimeWarning, 2
)

self._kcapi_sock = self._get_kcapi_sock(name, key)
if msg is not None:
self.update(msg)

@property
def name(self):
return f"hmac-{self._digest}"


def linux_sha256(data=None, usedforsecurity=True):
return _LinuxKCAPIHash("sha256", data, usedforsecurity=usedforsecurity)


def file_digest(fileobj, digest, /, *, _bufsize=2**18):
"""Hash the contents of a file-like object. Returns a digest object.

Expand Down
8 changes: 7 additions & 1 deletion Lib/hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import hashlib as _hashlib

import sys as _sys

trans_5C = bytes((x ^ 0x5C) for x in range(256))
trans_36 = bytes((x ^ 0x36) for x in range(256))

Expand Down Expand Up @@ -55,7 +57,11 @@ def __init__(self, key, msg=None, digestmod=''):
if not digestmod:
raise TypeError("Missing required parameter 'digestmod'.")

if _hashopenssl and isinstance(digestmod, (str, _functype)):
if _sys.platform == "linux":
self._hmac = _hashlib._LinuxKCAPIHMAC(key, msg, digestmod)
self.digest_size = self._hmac.digest_size
self.block_size = self._hmac.block_size
elif _hashopenssl and isinstance(digestmod, (str, _functype)):
try:
self._init_hmac(key, msg, digestmod)
except _hashopenssl.UnsupportedDigestmodError:
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,9 @@ def test_disallow_instantiation(self):
h = constructor()
except ValueError:
continue
if isinstance(h, hashlib._LinuxKCAPI):
# Linux KCAPI classes are pure Python
continue
with self.subTest(constructor=constructor):
support.check_disallow_instantiation(self, type(h))

Expand All @@ -985,6 +988,9 @@ def test_readonly_types(self):
hash_type = type(constructor())
except ValueError:
continue
if issubclass(hash_type, hashlib._LinuxKCAPI):
# Linux KCAPI classes are pure Python
continue
with self.subTest(hash_type=hash_type):
with self.assertRaisesRegex(TypeError, "immutable type"):
hash_type.value = False
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ def test_sha512_rfc4231(self):
def test_legacy_block_size_warnings(self):
class MockCrazyHash(object):
"""Ain't no block_size attribute here."""
name = "sha256"
def __init__(self, *args):
self._x = hashlib.sha256(*args)
self.digest_size = self._x.digest_size
Expand Down