From ebbedd1e890be93bfda8aabb34cb8526de1d9ed6 Mon Sep 17 00:00:00 2001 From: David Rheinsberg Date: Mon, 27 Apr 2020 14:38:22 +0200 Subject: [PATCH] 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. --- osbuild/util/linux.py | 38 +++++++++++++++++++++++++++++++++++++ test/mod/test_util_linux.py | 18 ++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/osbuild/util/linux.py b/osbuild/util/linux.py index ce106b9a..e8ee04b3 100644 --- a/osbuild/util/linux.py +++ b/osbuild/util/linux.py @@ -16,15 +16,19 @@ import array import ctypes import ctypes.util import fcntl +import hashlib +import hmac import os import platform import struct import threading +import uuid __all__ = [ "fcntl_flock", "ioctl_get_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) + + +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])) diff --git a/test/mod/test_util_linux.py b/test/mod/test_util_linux.py index 9f8a29c4..14a9e1a4 100644 --- a/test/mod/test_util_linux.py +++ b/test/mod/test_util_linux.py @@ -165,3 +165,21 @@ def test_fcntl_flock(): # Cleanup 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