linux: add proc_boot_id()

A new helper for the util.linux module which exposes the linux boot-id.
For security reasons, the boot-id is never exposed directly, but
instead only exposed through an application-id combined with the boot-id
via HMAC-SHA256.

Note that a raw kernel boot-id is always considered confidential, since
we never want an outside entity to deduce any information when they see
a boot-id used in protocol A and one in protocol B. It should not be
possible to tell whether both are from the same user and boot or not.
Hence, both should use their own boot-id namespace.
This commit is contained in:
David Rheinsberg 2020-04-27 14:38:22 +02:00 committed by Christian Kellner
parent aefaf21411
commit ebbedd1e89
2 changed files with 56 additions and 0 deletions

View file

@ -16,15 +16,19 @@ import array
import ctypes import ctypes
import ctypes.util import ctypes.util
import fcntl import fcntl
import hashlib
import hmac
import os import os
import platform import platform
import struct import struct
import threading import threading
import uuid
__all__ = [ __all__ = [
"fcntl_flock", "fcntl_flock",
"ioctl_get_immutable", "ioctl_get_immutable",
"ioctl_toggle_immutable", "ioctl_toggle_immutable",
"proc_boot_id",
] ]
@ -415,3 +419,37 @@ def fcntl_flock(fd: int, lock_type: int, wait: bool = False):
# #
fcntl.fcntl(fd, lock_cmd, arg_flock64) fcntl.fcntl(fd, lock_cmd, arg_flock64)
def proc_boot_id(appid: str):
"""Acquire Application-specific Boot-ID
This queries the kernel for the boot-id of the running system. It then
calculates an application-specific boot-id by combining the kernel boot-id
with the provided application-id. This uses a cryptographic HMAC.
Therefore, the kernel boot-id will not be deducable from the output. This
allows the caller to use the resulting application specific boot-id for any
purpose they wish without exposing the confidential kernel boot-id.
This always returns an object of type `uuid.UUID` from the python standard
library. Furthermore, this always produces UUIDs of version 4 variant 1.
Parameters
----------
appid
An arbitrary object (usually a string) that identifies the use-case of
the boot-id.
"""
with open("/proc/sys/kernel/random/boot_id", "r", encoding="utf8") as f:
content = f.read().strip(" \t\r\n")
# Running the boot-id through HMAC-SHA256 guarantees that the original
# boot-id will not be exposed. Thus two IDs generated with this interface
# will not allow to deduce whether they share a common boot-id.
# From the result, we throw away everything but the lower 128bits and then
# turn it into a UUID version 4 variant 1.
h = bytearray(hmac.new(content.encode(), appid.encode(), hashlib.sha256).digest()) # type: ignore
h[6] = (h[6] & 0x0f) | 0x40 # mark as version 4
h[8] = (h[6] & 0x3f) | 0x80 # mark as variant 1
return uuid.UUID(bytes=bytes(h[0:16]))

View file

@ -165,3 +165,21 @@ def test_fcntl_flock():
# Cleanup # Cleanup
os.close(fd2) os.close(fd2)
def test_proc_boot_id():
#
# Test the `proc_boot_id()` function which reads the current boot-id
# from the kernel. Make sure it is a valid UUID and also consistent on
# repeated queries.
#
bootid = linux.proc_boot_id("test")
assert len(bootid.hex) == 32
assert bootid.version == 4
bootid2 = linux.proc_boot_id("test")
assert bootid.int == bootid2.int
bootid3 = linux.proc_boot_id("foobar")
assert bootid.int != bootid3.int