Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/backport.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ permissions:
jobs:
backport:
name: Backport pull request
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 5

# Only run when pull request is merged
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/black.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ concurrency:

jobs:
lint:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
Expand Down
8 changes: 1 addition & 7 deletions .github/workflows/canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ jobs:
matrix:
include:
# A representative subset of environments
- os: ubuntu-24.04
python-version: '3.11'
toxenv: py311-llfuse
- os: ubuntu-24.04
python-version: '3.12'
toxenv: py312-pyfuse3
- os: ubuntu-24.04
- os: ubuntu-26.04
python-version: '3.14'
toxenv: py314-mfusepy
- os: macos-15
Expand Down
68 changes: 45 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ permissions:
jobs:
lint:

runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 5

steps:
Expand All @@ -40,15 +40,15 @@ jobs:

security:

runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 5

steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
python-version: '3.14'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand All @@ -59,7 +59,7 @@ jobs:

asan_ubsan:

runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 25
needs: [lint]

Expand All @@ -73,7 +73,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
python-version: '3.14'

- name: Install system packages
run: |
Expand Down Expand Up @@ -141,19 +141,18 @@ jobs:
${{ fromJSON(
github.event_name == 'pull_request' && '{
"include": [
{"os": "ubuntu-24.04", "python-version": "3.11", "toxenv": "mypy"},
{"os": "ubuntu-24.04", "python-version": "3.11", "toxenv": "docs"},
{"os": "ubuntu-24.04", "python-version": "3.11", "toxenv": "py311-llfuse"},
{"os": "ubuntu-24.04", "python-version": "3.12", "toxenv": "py312-pyfuse3"},
{"os": "ubuntu-24.04", "python-version": "3.14", "toxenv": "py314-mfusepy"}
{"os": "ubuntu-26.04", "python-version": "3.14", "toxenv": "mypy"},
{"os": "ubuntu-26.04", "python-version": "3.14", "toxenv": "docs"},
{"os": "ubuntu-26.04", "python-version": "3.14", "toxenv": "py314-llfuse"},
{"os": "ubuntu-26.04", "python-version": "3.14", "toxenv": "py314-pyfuse3"},
{"os": "ubuntu-26.04", "python-version": "3.14", "toxenv": "py314-mfusepy"}
]
}' || '{
"include": [
{"os": "ubuntu-24.04", "python-version": "3.11", "toxenv": "py311-llfuse"},
{"os": "ubuntu-24.04", "python-version": "3.12", "toxenv": "py312-pyfuse3"},
{"os": "ubuntu-24.04", "python-version": "3.13", "toxenv": "py313-mfusepy"},
{"os": "ubuntu-24.04", "python-version": "3.14", "toxenv": "py314-pyfuse3", "binary": "borg-linux-glibc239-x86_64-gh"},
{"os": "ubuntu-24.04-arm", "python-version": "3.14", "toxenv": "py314-pyfuse3", "binary": "borg-linux-glibc239-arm64-gh"},
{"os": "ubuntu-26.04", "python-version": "3.14", "toxenv": "py314-llfuse"},
{"os": "ubuntu-26.04", "python-version": "3.14", "toxenv": "py314-mfusepy"},
{"os": "ubuntu-26.04", "python-version": "3.14", "toxenv": "py314-pyfuse3", "binary": "borg-linux-glibc243-x86_64-gh"},
{"os": "ubuntu-26.04-arm", "python-version": "3.14", "toxenv": "py314-pyfuse3", "binary": "borg-linux-glibc243-arm64-gh"},
{"os": "macos-15", "python-version": "3.14", "toxenv": "py314-none", "binary": "borg-macos-15-arm64-gh"},
{"os": "macos-15-intel", "python-version": "3.14", "toxenv": "py314-none", "binary": "borg-macos-15-x86_64-gh"}
]
Expand Down Expand Up @@ -382,7 +381,7 @@ jobs:
contents: read
id-token: write
attestations: write
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 180
needs: [lint]
continue-on-error: true
Expand All @@ -392,11 +391,11 @@ jobs:
matrix:
include:
- os: freebsd
version: '14.3'
version: '15.0'
display_name: FreeBSD
# Controls binary build and provenance attestation on tags
do_binaries: true
artifact_prefix: borg-freebsd-14-x86_64-gh
artifact_prefix: borg-freebsd-15-x86_64-gh

- os: netbsd
version: '10.1'
Expand Down Expand Up @@ -520,6 +519,30 @@ jobs:
touch ${TMPDIR}/testfile
lsextattr user ${TMPDIR}/testfile && echo "[xattr] *** xattrs SUPPORTED on ${TMPDIR}! ***"

# NetBSD 10 has a too old OpenSSL, build a fresher one.
VERSION="3.5.6"
echo "--- Building OpenSSL ${VERSION} ---"
PREFIX="/usr/local"
JOBS=$(sysctl -n hw.ncpu)

pushd /tmp
ftp -o "openssl-${VERSION}.tar.gz" "https://www.openssl.org/source/openssl-${VERSION}.tar.gz"
tar xzf "openssl-${VERSION}.tar.gz"
pushd "openssl-${VERSION}"

./Configure --prefix="${PREFIX}" --openssldir="${PREFIX}/etc/ssl" --libdir=lib \
-Wl,-rpath,${PREFIX}/lib shared threads no-tests no-docs
export LD_LIBRARY_PATH="/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
make -j"${JOBS}"
sudo -E make install
popd
rm -rf "/tmp/openssl-${VERSION}" "/tmp/openssl-${VERSION}.tar.gz"
popd

"${PREFIX}/bin/openssl" version
export BORG_OPENSSL_PREFIX="${PREFIX}"
export PKG_CONFIG_PATH="/usr/local/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"

tox3 -e py311-none
;;

Expand Down Expand Up @@ -564,8 +587,7 @@ jobs:
pkgman install -y openssl3
pkgman install -y rust_bin
pkgman install -y python3.11
pkgman install -y cffi
pkgman install -y lz4_devel openssl3_devel libffi_devel
pkgman install -y lz4_devel openssl3_devel

# there is no pkgman package for tox, so we install it into a venv
python3 -m ensurepip --upgrade
Expand Down Expand Up @@ -651,7 +673,7 @@ jobs:

- name: Build python venv
run: |
# building cffi / argon2-cffi in the venv fails, so we try to use the system packages
# building native extensions in the venv fails, so we try to use the system packages
python -m venv --system-site-packages env
. env/bin/activate
# python -m pip install --upgrade pip
Expand Down Expand Up @@ -685,7 +707,7 @@ jobs:
uses: codecov/codecov-action@v7
env:
OS: ${{ runner.os }}
python: '3.11'
python: '3.14'
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: test_results
Expand All @@ -697,7 +719,7 @@ jobs:
uses: codecov/codecov-action@v7
env:
OS: ${{ runner.os }}
python: '3.11'
python: '3.14'
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: coverage
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ concurrency:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 20
permissions:
actions: read
Expand All @@ -53,7 +53,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: 3.11
python-version: 3.14
- name: Cache pip
uses: actions/cache@v5
with:
Expand Down
4 changes: 2 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,10 @@ following dependencies first. For the libraries you will also need their
development header files (sometimes in a separate `-dev` or `-devel` package).

* `Python 3`_ >= 3.11.0
* OpenSSL_ >= 1.1.1 (LibreSSL will not work)
* OpenSSL_ >= 3.2.0 (LibreSSL will not work)
* libacl_ (which depends on libattr_)
* liblz4_ >= 1.7.0 (r129)
* libffi (required for argon2-cffi-bindings)

* pkg-config (cli tool) - Borg uses this to discover header and library
locations automatically. Alternatively, you can also point to them via some
environment variables, see setup.py.
Expand Down
2 changes: 1 addition & 1 deletion docs/internals/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ on widely used libraries providing them:
primitives implemented in libcrypto.
- SHA-256, SHA-512 and BLAKE2b from Python's hashlib_ standard library module are used.
- HMAC and a constant-time comparison from Python's hmac_ standard library module are used.
- argon2 is used via argon2-cffi.
- argon2 is used from OpenSSL (>= 3.2).

.. _Horton principle: https://en.wikipedia.org/wiki/Horton_Principle
.. _length extension: https://en.wikipedia.org/wiki/Length_extension_attack
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ dependencies = [
"packaging",
"platformdirs >=3.0.0, <5.0.0; sys_platform == 'darwin'", # for macOS: breaking changes in 3.0.0.
"platformdirs >=2.6.0, <5.0.0; sys_platform != 'darwin'", # for others: 2.6+ works consistently.
"argon2-cffi",
"shtab>=1.8.0",
"backports-zstd; python_version < '3.14'", # for python < 3.14.
"jsonargparse>=4.47.0",
Expand Down
2 changes: 1 addition & 1 deletion scripts/msys2-install-deps
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,lz4,openssl,rclone,python-msgpack,python-argon2_cffi,python-platformdirs,python,cython,python-setuptools,python-wheel,python-build,python-pkgconfig,python-packaging,python-pip,python-paramiko,rust,python-maturin}
pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,lz4,openssl,rclone,python-msgpack,python-platformdirs,python,cython,python-setuptools,python-wheel,python-build,python-pkgconfig,python-packaging,python-pip,python-paramiko,rust,python-maturin}

if [ "$1" = "development" ]; then
pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-python-{pytest,pytest-benchmark,pytest-cov,pytest-xdist}
Expand Down
13 changes: 11 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

is_win32 = sys.platform.startswith("win32")
is_openbsd = sys.platform.startswith("openbsd")
is_netbsd = sys.platform.startswith("netbsd")

# Number of threads to use for cythonize, not used on Windows
cpu_threads = multiprocessing.cpu_count() if multiprocessing and multiprocessing.get_start_method() != "spawn" else None
Expand Down Expand Up @@ -142,7 +143,7 @@ def lib_ext_kwargs(pc, prefix_env_var, lib_name, lib_pkg_name, pc_version, lib_s
)

if is_win32:
crypto_ext_lib = lib_ext_kwargs(pc, "BORG_OPENSSL_PREFIX", "libcrypto", "libcrypto", ">=1.1.1", lib_subdir="")
crypto_ext_lib = lib_ext_kwargs(pc, "BORG_OPENSSL_PREFIX", "libcrypto", "libcrypto", ">=3.2.0", lib_subdir="")
elif is_openbsd:
# Use OpenSSL (not LibreSSL) because we need AES-OCB via the EVP API. Link
# it statically to avoid conflicting with shared libcrypto from the base
Expand All @@ -153,8 +154,16 @@ def lib_ext_kwargs(pc, prefix_env_var, lib_name, lib_pkg_name, pc_version, lib_s
include_dirs=[os.path.join(openssl_prefix, "include", openssl_name)],
extra_objects=[os.path.join(openssl_prefix, "lib", openssl_name, "libcrypto.a")],
)
elif is_netbsd and os.environ.get("BORG_OPENSSL_PREFIX"):
# Similarly for NetBSD, if we built a custom OpenSSL, link it statically
# to avoid dynamic linker conflicts with the system OpenSSL loaded by Python.
openssl_prefix = os.environ.get("BORG_OPENSSL_PREFIX")
crypto_ext_lib = dict(
include_dirs=[os.path.join(openssl_prefix, "include")],
extra_objects=[os.path.join(openssl_prefix, "lib", "libcrypto.a")],
)
else:
crypto_ext_lib = lib_ext_kwargs(pc, "BORG_OPENSSL_PREFIX", "crypto", "libcrypto", ">=1.1.1")
crypto_ext_lib = lib_ext_kwargs(pc, "BORG_OPENSSL_PREFIX", "crypto", "libcrypto", ">=3.2.0")

crypto_ext_kwargs = members_appended(
dict(sources=[crypto_ll_source]), crypto_ext_lib, dict(extra_compile_args=cflags)
Expand Down
5 changes: 4 additions & 1 deletion src/borg/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
setup_logging()

from borg.archiver import Archiver # noqa: E402
from borg.platform import set_flags # noqa: E402
from borg.testsuite import has_lchflags, has_llfuse, has_pyfuse3, has_mfusepy # noqa: E402
from borg.testsuite import are_symlinks_supported, are_hardlinks_supported, is_utime_fully_supported # noqa: E402
from borg.testsuite.archiver import BORG_EXES
Expand Down Expand Up @@ -197,8 +198,10 @@ def archiver(tmp_path, set_env_variables):
def maybe_clear_flags_and_retry(func, path, _exc_info):
if has_lchflags:
# Clear any BSD flags (e.g. UF_APPEND) that may have prevented removal, then retry once.
# Note: use borg's cross-platform set_flags, not os.lchflags - the latter does not exist
# on Linux even though has_lchflags is True there (Linux clears flags via ioctl).
try:
os.lchflags(path, 0)
set_flags(path, 0)
func(path)
except OSError:
pass
Expand Down
13 changes: 2 additions & 11 deletions src/borg/crypto/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
logger = create_logger()

from blake3 import blake3
import argon2.low_level

from ..constants import * # NOQA
from ..helpers import StableDict
Expand Down Expand Up @@ -483,17 +482,9 @@ def argon2(
parallelism = 1
# 8 is the smallest value that avoids the "Memory cost is too small" exception
memory_cost = 8
type_map = {"i": argon2.low_level.Type.I, "d": argon2.low_level.Type.D, "id": argon2.low_level.Type.ID}
key = argon2.low_level.hash_secret_raw(
secret=passphrase.encode("utf-8"),
hash_len=output_len_in_bytes,
salt=salt,
time_cost=time_cost,
memory_cost=memory_cost,
parallelism=parallelism,
type=type_map[type],
return low_level.argon2_hash(
passphrase.encode("utf-8"), salt, time_cost, memory_cost, parallelism, output_len_in_bytes, type
)
return key

def decrypt_key_file_argon2(self, encrypted_key, passphrase):
key = self.argon2(
Expand Down
3 changes: 3 additions & 0 deletions src/borg/crypto/low_level.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ def long_to_bytes(x: int) -> bytes: ...
def hmac_sha256(key: bytes, data: bytes) -> bytes: ...
def blake2b_256(key: bytes, data: bytes) -> bytes: ...
def blake2b_128(data: bytes) -> bytes: ...
def argon2_hash(
secret: bytes, salt: bytes, time_cost: int, memory_cost: int, parallelism: int, hash_len: int, type: str
) -> bytes: ...

# Exception classes
class CryptoError(Exception):
Expand Down
Loading
Loading