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
2 changes: 1 addition & 1 deletion .bumpversion.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tool.bumpversion]
current_version = "1.19.0+260822"
current_version = "1.19.2+260912"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)\\+(?P<build>\\d+)"
serialize = ["{major}.{minor}.{patch}+{build}"]
search = "{current_version}"
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "rasenmaeher_api"
version = "1.19.0+260822"
version = "1.19.2+260912"
description = "python-rasenmaeher-api"
authors = [
{ name = "Aciid", email = "703382+Aciid@users.noreply.github.com" },
Expand All @@ -17,10 +17,10 @@ dependencies = [
"pydantic>=2.0,<3.0",
"pydantic-settings>=2.0,<3.0",
"requests>=2.31,<3.0",
"multikeyjwt>=1.5,<2.0",
"multikeyjwt>=1.7,<2.0",
"uvicorn[standard]>=0.47,<1.0",
"gunicorn>=26.0",
"libpvarki>=2.2,<3.0",
"libpvarki>=2.4,<3.0",
"openapi-readme>=0.2,<0.3",
"python-multipart>=0.0.21,<1.0.0",
"aiohttp>=3.11.10,<4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/rasenmaeher_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""python-rasenmaeher-api"""

__version__ = "1.19.0+260822" # NOTE Use `bump-my-version` to bump versions correctly
__version__ = "1.19.2+260912" # NOTE Use `bump-my-version` to bump versions correctly
3 changes: 2 additions & 1 deletion src/rasenmaeher_api/cert/cfssl/private.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from ...rmsettings import RMSettings
from .base import CFSSLError, DBLocked, NoResult, base_url, default_timeout, get_result, get_result_cert, ocsprest_base
from .mtls import mtls_session
from .profiles import signing_profile

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -54,7 +55,7 @@ async def sign_csr(csr: str, bundle: bool = True) -> str:
"""
async with await mtls_session() as session:
url = f"{ocsprest_base()}/api/v1/csr/sign"
payload = {"certificate_request": csr, "profile": "client", "bundle": bundle}
payload = {"certificate_request": csr, "profile": signing_profile(csr), "bundle": bundle}
try:
LOGGER.debug(f"Calling {url}")
async with session.post(url, json=payload, timeout=default_timeout()) as response:
Expand Down
18 changes: 18 additions & 0 deletions src/rasenmaeher_api/cert/cfssl/profiles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Select a CFSSL TLS profile from the CSR's requested extended key usages."""

from typing import Literal

from cryptography import x509
from cryptography.x509.oid import ExtendedKeyUsageOID


def signing_profile(csr_pem: str) -> Literal["client", "server", "peer"]:
"""Keep client signing as the default and honor explicit server-auth requests."""
csr = x509.load_pem_x509_csr(csr_pem.encode("utf-8"))
try:
usages = csr.extensions.get_extension_for_class(x509.ExtendedKeyUsage).value
except x509.ExtensionNotFound:
return "client"
if ExtendedKeyUsageOID.SERVER_AUTH not in usages:
return "client"
return "peer" if ExtendedKeyUsageOID.CLIENT_AUTH in usages else "server"
2 changes: 1 addition & 1 deletion src/rasenmaeher_api/rmsettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def log_level_int(self) -> int:
ldap_client_secret: str | None = None

# Tilauspalvelu integration
tilauspalvelu_jwt: str = "https://tilaa.pvarki.fi/api/v1/config/jwtPublicKey.pem"
tilauspalvelu_jwt: str = "" # "https://tilaa.pvarki.fi/api/v1/config/jwtPublicKey.pem"
kraftwerk_announce: str | None = None # When KRAFTWERK actually exists
kraftwerk_timeout: float = 2.0

Expand Down
31 changes: 31 additions & 0 deletions tests/cert/test_cfssl_profiles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""CFSSL must issue the server authentication requested by product CSRs."""

import pytest
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, rsa
from cryptography.x509.oid import ExtendedKeyUsageOID, NameOID

from rasenmaeher_api.cert.cfssl.profiles import signing_profile


@pytest.mark.parametrize("keytype", ["EC", "RSA"])
@pytest.mark.parametrize(
("usages", "expected"),
[
(None, "client"),
([ExtendedKeyUsageOID.CLIENT_AUTH], "client"),
([ExtendedKeyUsageOID.SERVER_AUTH], "server"),
([ExtendedKeyUsageOID.SERVER_AUTH, ExtendedKeyUsageOID.CLIENT_AUTH], "peer"),
],
)
def test_signing_profile(keytype: str, usages: list[x509.ObjectIdentifier] | None, expected: str) -> None:
"""Preserve client defaults and select dual-purpose certificates for TAK."""
key = ec.generate_private_key(ec.SECP256R1()) if keytype == "EC" else rsa.generate_private_key(65537, 2048)
builder = x509.CertificateSigningRequestBuilder().subject_name(
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "tak.example.test")])
)
if usages is not None:
builder = builder.add_extension(x509.ExtendedKeyUsage(usages), critical=True)
csr = builder.sign(key, hashes.SHA256()).public_bytes(serialization.Encoding.PEM).decode("utf-8")
assert signing_profile(csr) == expected
2 changes: 1 addition & 1 deletion tests/test_rasenmaeher_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

def test_version() -> None:
"""Make sure version matches expected"""
assert __version__ == "1.19.0+260822"
assert __version__ == "1.19.2+260912"


@pytest.mark.asyncio(loop_scope="session")
Expand Down
18 changes: 9 additions & 9 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading