Skip to content

Commit 9950f8b

Browse files
authored
Add server argument to specify custom PAA root certificate location (#622)
1 parent 39c5906 commit 9950f8b

File tree

5 files changed

+38
-29
lines changed

5 files changed

+38
-29
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

+3-5
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@
1111
# and a full re-interview is mandatory
1212
DATA_MODEL_SCHEMA_VERSION = 6
1313

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] = (
14+
# Keep default location inherited from early version of the Python
15+
# bindings.
16+
DEFAULT_PAA_ROOT_CERTS_DIR: Final[pathlib.Path] = (
1917
pathlib.Path(__file__)
2018
.parent.resolve()
2119
.parent.resolve()

matter_server/server/device_controller.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from datetime import datetime
1010
from functools import partial
1111
import logging
12+
from pathlib import Path
1213
from random import randint
1314
import time
1415
from typing import TYPE_CHECKING, Any, Callable, Iterable, TypeVar, cast
@@ -48,7 +49,7 @@
4849
MatterNodeEvent,
4950
NodePingResult,
5051
)
51-
from .const import DATA_MODEL_SCHEMA_VERSION, PAA_ROOT_CERTS_DIR
52+
from .const import DATA_MODEL_SCHEMA_VERSION
5253
from .helpers.paa_certificates import fetch_certificates
5354

5455
if TYPE_CHECKING:
@@ -117,15 +118,15 @@ def __init__(
117118
self._mdns_event_timer: dict[str, asyncio.TimerHandle] = {}
118119
self._node_lock: dict[int, asyncio.Lock] = {}
119120

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

126127
# Instantiate the underlying ChipDeviceController instance on the Fabric
127128
self.chip_controller = self.server.stack.fabric_admin.NewController(
128-
paaTrustStorePath=str(PAA_ROOT_CERTS_DIR)
129+
paaTrustStorePath=str(paa_root_cert_dir)
129130
)
130131
self.compressed_fabric_id = cast(
131132
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

+7-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
ServerInfoMessage,
3030
)
3131
from ..server.client_handler import WebsocketClientHandler
32-
from .const import MIN_SCHEMA_VERSION
32+
from .const import DEFAULT_PAA_ROOT_CERTS_DIR, MIN_SCHEMA_VERSION
3333
from .device_controller import MatterDeviceController
3434
from .stack import MatterStack
3535
from .storage import StorageController
@@ -100,6 +100,7 @@ def __init__(
100100
port: int,
101101
listen_addresses: list[str] | None = None,
102102
primary_interface: str | None = None,
103+
paa_root_cert_dir: Path | None = None,
103104
) -> None:
104105
"""Initialize the Matter Server."""
105106
self.storage_path = storage_path
@@ -108,6 +109,10 @@ def __init__(
108109
self.port = port
109110
self.listen_addresses = listen_addresses
110111
self.primary_interface = primary_interface
112+
if paa_root_cert_dir is None:
113+
self.paa_root_cert_dir = DEFAULT_PAA_ROOT_CERTS_DIR
114+
else:
115+
self.paa_root_cert_dir = Path(paa_root_cert_dir).absolute()
111116
self.logger = logging.getLogger(__name__)
112117
self.app = web.Application()
113118
self.loop: asyncio.AbstractEventLoop | None = None
@@ -134,7 +139,7 @@ async def start(self) -> None:
134139
self.loop = asyncio.get_running_loop()
135140
self.loop.set_exception_handler(_global_loop_exception_handler)
136141
self.loop.set_debug(os.environ.get("PYTHONDEBUG", "") != "")
137-
await self.device_controller.initialize()
142+
await self.device_controller.initialize(self.paa_root_cert_dir)
138143
await self.storage.start()
139144
await self.device_controller.start()
140145
await self.vendor_info.start()

0 commit comments

Comments
 (0)