Skip to content
Merged
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ kerf create database --cpu-count=8 --memory=16GB
kerf load --kernel=/boot/vmlinuz --initrd=/boot/initrd.img \
--cmdline="root=/dev/sda1 ro" --id=1

# Sign a spawn kernel where the signing key is kept, not on the host;
# the host kernel must trust the certificate, for example as a MOK
kerf sign /boot/vmlinuz -o vmlinux.signed --key signing_key.pem --cert signing_cert.pem
kerf load --kernel=vmlinux.signed --id=1

# Boot a kernel instance
kerf start web-server

Expand Down Expand Up @@ -310,6 +315,7 @@ The kernel exposes a filesystem interface (mounted at `/sys/fs/multikernel/`) th
[tool.poetry.dependencies]
python = "^3.8"
pylibfdt = "^1.7.0" # Device tree parsing (from dtc project)
cryptography = ">=3.1" # Signing spawn kernels (kerf sign)
```

### Installation
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pylibfdt = "^1.7.0"
click = "^8.0.0"
pyyaml = "^6.0"
pyudev = "^0.24.0"
cryptography = ">=3.1"

[tool.poetry.group.dev.dependencies]
pytest = "^7.0.0"
Expand Down
2 changes: 2 additions & 0 deletions src/kerf/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from .show.main import show
from .dump.main import dump
from .console.main import console
from .sign.main import sign


@click.group()
Expand All @@ -54,6 +55,7 @@ def main(ctx, debug):
main.add_command(show)
main.add_command(dump)
main.add_command(console)
main.add_command(sign)


if __name__ == "__main__":
Expand Down
26 changes: 25 additions & 1 deletion src/kerf/load/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"""

import ctypes
import errno
import os
import platform
import sys
Expand All @@ -32,6 +33,7 @@
save_instance_metadata,
)
from ..utils import get_instance_id_from_name, get_instance_name_from_id
from ..signature import kexec_signatures_enforced
from ..vmlinuz import BZIMAGE_HEADER_SIZE, VmlinuzError, is_bzimage, open_kernel_fd


Expand Down Expand Up @@ -521,6 +523,12 @@ def load( # pylint: disable=too-many-arguments,too-many-positional-arguments,to
kernel_is_bzimage = is_bzimage(f.read(BZIMAGE_HEADER_SIZE))
if kernel_is_bzimage and verbose:
click.echo("bzImage detected, extracting embedded vmlinux")
if kernel_is_bzimage and kexec_signatures_enforced():
click.echo(
"Warning: this host enforces kernel signatures, and a bzImage is "
"loaded as its extracted, unsigned vmlinux. Sign it with 'kerf sign'.",
err=True,
)
kernel_fd = open_kernel_fd(kernel_path)
except VmlinuzError as e:
click.echo(f"Error: {e}", err=True)
Expand Down Expand Up @@ -583,8 +591,24 @@ def load( # pylint: disable=too-many-arguments,too-many-positional-arguments,to

except OSError as e:
click.echo(f"Error: kexec_file_load failed: {e}", err=True)
if e.errno == 1: # EPERM
if e.errno == errno.EPERM and os.geteuid() == 0:
click.echo(
"Note: Refused by kernel lockdown; the kernel image must be signed "
"(see 'kerf sign').", err=True
)
elif e.errno == 1: # EPERM
click.echo("Note: This operation requires root privileges", err=True)
elif e.errno == errno.ENODATA:
click.echo(
"Note: The kernel image is not signed and this host requires "
"signatures. Sign it with 'kerf sign'.", err=True
)
elif e.errno in (errno.ENOKEY, errno.EKEYREJECTED, errno.EKEYEXPIRED,
errno.EKEYREVOKED, errno.EBADMSG):
click.echo(
"Note: The kernel signature was rejected. The signing certificate "
"must be trusted by this host (for example enrolled as a MOK).", err=True
)
elif e.errno == 16: # EBUSY
click.echo(
f"Note: Instance '{instance_name}' already has a kernel loaded. "
Expand Down
21 changes: 21 additions & 0 deletions src/kerf/sign/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2026 Multikernel Technologies, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Kernel signing subcommand implementation.
"""

from .main import sign

__all__ = ["sign"]
77 changes: 77 additions & 0 deletions src/kerf/sign/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright 2026 Multikernel Technologies, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Sign a spawn kernel so kexec_file_load() can verify it.

Run this where the signing key is kept, such as a build machine, not on
the hosts that load kernels.
"""

import sys
from pathlib import Path

import click

from ..signature import HASH_ALGORITHMS, SignatureError, is_signed, load_signing_key, sign_kernel
from ..vmlinuz import VmlinuzError, extract_vmlinux, is_bzimage


@click.command()
@click.argument("kernel", type=click.Path(exists=True, dir_okay=False, path_type=Path))
@click.option(
"-o", "--output", required=True, type=click.Path(dir_okay=False, path_type=Path),
help="Signed ELF vmlinux to write",
)
@click.option(
"--key", "key_path", required=True, type=click.Path(exists=True, dir_okay=False),
help="Private key, PEM or DER",
)
@click.option(
"--cert", "cert_path", type=click.Path(exists=True, dir_okay=False),
help="X.509 certificate, PEM or DER (default: read from --key)",
)
@click.option(
"--hash", "hash_name", type=click.Choice(HASH_ALGORITHMS), default="sha256",
show_default=True, help="Digest algorithm",
)
@click.option(
"--password", envvar="KERF_SIGN_PASSWORD",
help="Private key password (or $KERF_SIGN_PASSWORD)",
)
def sign(kernel, output, key_path, cert_path, hash_name, password):
"""Sign KERNEL for kexec_file_load() signature verification.

KERNEL may be a bzImage, from which the embedded ELF vmlinux is
extracted, or an ELF vmlinux. An existing signature is replaced. The
signing certificate must be trusted by the host kernel, for example
enrolled as a MOK.
"""
try:
key, cert = load_signing_key(key_path, cert_path, password)
data = kernel.read_bytes()
if is_bzimage(data):
data = extract_vmlinux(data)
elif is_signed(data):
click.echo(f"Replacing the existing signature on {kernel}")
signed = sign_kernel(data, key, cert, hash_name)
output.write_bytes(signed)
except (SignatureError, VmlinuzError) as e:
click.echo(f"Error: {e}", err=True)
sys.exit(1)
except OSError as e:
click.echo(f"Error: {e}", err=True)
sys.exit(1)

click.echo(f"Signed {output} with {cert.subject.rfc4514_string()} ({hash_name})")
Loading
Loading