Skip to content

Commit 14651ac

Browse files
committed
Add server argument to specify custom PAA root certificate location
Add a new server argument --paa-root-cert-dir to allow a custom location for PAA root certificates. This allows to store them in a location which is preserved.
1 parent 74f984c commit 14651ac

File tree

5 files changed

+41
-37
lines changed

5 files changed

+41
-37
lines changed

matter_server/server/__main__.py

+7
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@
9191
default=None,
9292
help="Primary network interface for link-local addresses (optional).",
9393
)
94+
parser.add_argument(
95+
"--paa-root-cert-dir",
96+
type=str,
97+
default=None,
98+
help="Directory where PAA root certificates are stored.",
99+
)
94100

95101
args = parser.parse_args()
96102

@@ -175,6 +181,7 @@ def main() -> None:
175181
int(args.port),
176182
args.listen_address,
177183
args.primary_interface,
184+
args.paa_root_cert_dir,
178185
)
179186

180187
async def handle_stop(loop: asyncio.AbstractEventLoop) -> None:

matter_server/server/const.py

-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Server-only constants for the Python Matter Server."""
22

3-
import pathlib
4-
from typing import Final
53

64
# The minimum schema version (of a client) the server can support
75
MIN_SCHEMA_VERSION = 5
@@ -10,15 +8,3 @@
108
# only bump if the format of the data in MatterNodeData changed
119
# and a full re-interview is mandatory
1210
DATA_MODEL_SCHEMA_VERSION = 6
13-
14-
# the paa-root-certs path is hardcoded in the sdk at this time
15-
# and always uses the development subfolder
16-
# regardless of anything you pass into instantiating the controller
17-
# revisit this once matter 1.1 is released
18-
PAA_ROOT_CERTS_DIR: Final[pathlib.Path] = (
19-
pathlib.Path(__file__)
20-
.parent.resolve()
21-
.parent.resolve()
22-
.parent.resolve()
23-
.joinpath("credentials/development/paa-root-certs")
24-
)

matter_server/server/device_controller.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from datetime import datetime
1111
from functools import partial
1212
import logging
13+
from pathlib import Path
1314
from random import randint
1415
import time
1516
from typing import TYPE_CHECKING, Any, Callable, Iterable, TypeVar, cast
@@ -49,7 +50,7 @@
4950
MatterNodeEvent,
5051
NodePingResult,
5152
)
52-
from .const import DATA_MODEL_SCHEMA_VERSION, PAA_ROOT_CERTS_DIR
53+
from .const import DATA_MODEL_SCHEMA_VERSION
5354
from .helpers.paa_certificates import fetch_certificates
5455

5556
if TYPE_CHECKING:
@@ -121,15 +122,15 @@ def __init__(
121122
self._node_setup_throttle = asyncio.Semaphore(10)
122123
self._mdns_event_timer: dict[str, asyncio.TimerHandle] = {}
123124

124-
async def initialize(self) -> None:
125+
async def initialize(self, paa_root_cert_dir: Path) -> None:
125126
"""Async initialize of controller."""
126127
# (re)fetch all PAA certificates once at startup
127128
# NOTE: this must be done before initializing the controller
128-
await fetch_certificates()
129+
await fetch_certificates(paa_root_cert_dir)
129130

130131
# Instantiate the underlying ChipDeviceController instance on the Fabric
131132
self.chip_controller = self.server.stack.fabric_admin.NewController(
132-
paaTrustStorePath=str(PAA_ROOT_CERTS_DIR)
133+
paaTrustStorePath=str(paa_root_cert_dir)
133134
)
134135
self.compressed_fabric_id = cast(
135136
int, await self._call_sdk(self.chip_controller.GetCompressedFabricId)

matter_server/server/helpers/paa_certificates.py

+16-18
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@
1111
from datetime import UTC, datetime, timedelta
1212
import logging
1313
from os import makedirs
14+
from pathlib import Path
1415
import re
1516

1617
from aiohttp import ClientError, ClientSession
1718
from cryptography import x509
1819
from cryptography.hazmat.primitives import serialization
1920

20-
from matter_server.server.const import PAA_ROOT_CERTS_DIR
21-
2221
# Git repo details
2322
OWNER = "project-chip"
2423
REPO = "connectedhomeip"
@@ -33,14 +32,16 @@
3332
LAST_CERT_IDS: set[str] = set()
3433

3534

36-
async def write_paa_root_cert(certificate: str, subject: str) -> None:
35+
async def write_paa_root_cert(
36+
paa_root_cert_dir: Path, certificate: str, subject: str
37+
) -> None:
3738
"""Write certificate from string to file."""
3839

3940
def _write() -> None:
4041
filename_base = "dcld_mirror_" + re.sub(
4142
"[^a-zA-Z0-9_-]", "", re.sub("[=, ]", "_", subject)
4243
)
43-
filepath_base = PAA_ROOT_CERTS_DIR.joinpath(filename_base)
44+
filepath_base = paa_root_cert_dir.joinpath(filename_base)
4445
# handle PEM certificate file
4546
file_path_pem = f"{filepath_base}.pem"
4647
LOGGER.debug("Writing certificate %s", file_path_pem)
@@ -58,6 +59,7 @@ def _write() -> None:
5859

5960

6061
async def fetch_dcl_certificates(
62+
paa_root_cert_dir: Path,
6163
fetch_test_certificates: bool = True,
6264
fetch_production_certificates: bool = True,
6365
) -> int:
@@ -99,6 +101,7 @@ async def fetch_dcl_certificates(
99101
certificate = certificate.rstrip("\n")
100102

101103
await write_paa_root_cert(
104+
paa_root_cert_dir,
102105
certificate,
103106
subject,
104107
)
@@ -119,7 +122,7 @@ async def fetch_dcl_certificates(
119122
# are correctly captured
120123

121124

122-
async def fetch_git_certificates() -> int:
125+
async def fetch_git_certificates(paa_root_cert_dir: Path) -> int:
123126
"""Fetch Git PAA Certificates."""
124127
fetch_count = 0
125128
LOGGER.info("Fetching the latest PAA root certificates from Git.")
@@ -137,7 +140,7 @@ async def fetch_git_certificates() -> int:
137140
continue
138141
async with http_session.get(f"{GIT_URL}/{cert}.pem") as response:
139142
certificate = await response.text()
140-
await write_paa_root_cert(certificate, cert)
143+
await write_paa_root_cert(paa_root_cert_dir, certificate, cert)
141144
LAST_CERT_IDS.add(cert)
142145
fetch_count += 1
143146
except (ClientError, TimeoutError) as err:
@@ -150,24 +153,18 @@ async def fetch_git_certificates() -> int:
150153
return fetch_count
151154

152155

153-
async def _get_certificate_age() -> datetime:
154-
"""Get last time PAA Certificates have been fetched."""
155-
loop = asyncio.get_running_loop()
156-
stat = await loop.run_in_executor(None, PAA_ROOT_CERTS_DIR.stat)
157-
return datetime.fromtimestamp(stat.st_mtime, tz=UTC)
158-
159-
160156
async def fetch_certificates(
157+
paa_root_cert_dir: Path,
161158
fetch_test_certificates: bool = True,
162159
fetch_production_certificates: bool = True,
163160
) -> int:
164161
"""Fetch PAA Certificates."""
165162
loop = asyncio.get_running_loop()
166163

167-
if not PAA_ROOT_CERTS_DIR.is_dir():
168-
await loop.run_in_executor(None, makedirs, PAA_ROOT_CERTS_DIR)
164+
if not paa_root_cert_dir.is_dir():
165+
await loop.run_in_executor(None, makedirs, paa_root_cert_dir)
169166
else:
170-
stat = await loop.run_in_executor(None, PAA_ROOT_CERTS_DIR.stat)
167+
stat = await loop.run_in_executor(None, paa_root_cert_dir.stat)
171168
last_fetch = datetime.fromtimestamp(stat.st_mtime, tz=UTC)
172169
if last_fetch > datetime.now(tz=UTC) - timedelta(days=1):
173170
LOGGER.info(
@@ -176,13 +173,14 @@ async def fetch_certificates(
176173
return 0
177174

178175
fetch_count = await fetch_dcl_certificates(
176+
paa_root_cert_dir=paa_root_cert_dir,
179177
fetch_test_certificates=fetch_test_certificates,
180178
fetch_production_certificates=fetch_production_certificates,
181179
)
182180

183181
if fetch_test_certificates:
184-
fetch_count += await fetch_git_certificates()
182+
fetch_count += await fetch_git_certificates(paa_root_cert_dir)
185183

186-
await loop.run_in_executor(None, PAA_ROOT_CERTS_DIR.touch)
184+
await loop.run_in_executor(None, paa_root_cert_dir.touch)
187185

188186
return fetch_count

matter_server/server/server.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import ipaddress
88
import logging
99
import os
10+
import pathlib
1011
from pathlib import Path
1112
import traceback
1213
from typing import TYPE_CHECKING, Any, Callable, Set, cast
@@ -100,6 +101,7 @@ def __init__(
100101
port: int,
101102
listen_addresses: list[str] | None = None,
102103
primary_interface: str | None = None,
104+
paa_root_cert_dir: Path | None = None,
103105
) -> None:
104106
"""Initialize the Matter Server."""
105107
self.storage_path = storage_path
@@ -108,6 +110,16 @@ def __init__(
108110
self.port = port
109111
self.listen_addresses = listen_addresses
110112
self.primary_interface = primary_interface
113+
if paa_root_cert_dir is None:
114+
self.paa_root_cert_dir = (
115+
pathlib.Path(__file__)
116+
.parent.resolve()
117+
.parent.resolve()
118+
.parent.resolve()
119+
.joinpath("credentials/development/paa-root-certs")
120+
)
121+
else:
122+
self.paa_root_cert_dir = Path(paa_root_cert_dir).absolute()
111123
self.logger = logging.getLogger(__name__)
112124
self.app = web.Application()
113125
self.loop: asyncio.AbstractEventLoop | None = None
@@ -134,7 +146,7 @@ async def start(self) -> None:
134146
self.loop = asyncio.get_running_loop()
135147
self.loop.set_exception_handler(_global_loop_exception_handler)
136148
self.loop.set_debug(os.environ.get("PYTHONDEBUG", "") != "")
137-
await self.device_controller.initialize()
149+
await self.device_controller.initialize(self.paa_root_cert_dir)
138150
await self.storage.start()
139151
await self.device_controller.start()
140152
await self.vendor_info.start()

0 commit comments

Comments
 (0)