From 27c80b4944310eaacdb7039a73214747f55524ce Mon Sep 17 00:00:00 2001 From: Arturo Naredo Date: Wed, 25 Mar 2026 05:18:29 +0100 Subject: [PATCH] feat: add auth_mode option to ONVIF config for Hikvision HTTP Digest support Some Hikvision PTZ cameras (e.g. DS-2SE4C425MWG-E) reject WSSE wsUsername tokens and return 401 on /onvif/Media and /onvif/PTZ endpoints. They require HTTP Digest authentication at the transport level before processing SOAP. Add `auth_mode` field to OnvifConfig (auto/digest/wsse, default: auto). When auth_mode=digest, inject aiohttp.DigestAuthMiddleware into the ONVIFCamera session so Digest challenge-response completes before SOAP. Usage: cameras: my_hikvision_ptz: onvif: host: 192.168.31.86 port: 80 user: admin password: yourpassword auth_mode: digest Fixes #22622 --- frigate/config/camera/onvif.py | 7 ++++++- frigate/ptz/onvif.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/frigate/config/camera/onvif.py b/frigate/config/camera/onvif.py index eb21e24bd..65a183e2b 100644 --- a/frigate/config/camera/onvif.py +++ b/frigate/config/camera/onvif.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Optional, Union +from typing import Literal, Optional, Union from pydantic import Field, field_validator @@ -117,6 +117,11 @@ class OnvifConfig(FrigateBaseModel): title="Disable TLS verify", description="Skip TLS verification and disable digest auth for ONVIF (unsafe; use in safe networks only).", ) + auth_mode: Literal["auto", "digest", "wsse"] = Field( + default="auto", + title="ONVIF authentication mode", + description="Authentication mode for ONVIF connections. 'auto' tries WSSE first (default behavior). 'digest' forces HTTP Digest auth at the transport level — required for some Hikvision cameras that reject WSSE wsUsername tokens and return 401. 'wsse' forces WSSE UsernameToken only.", + ) autotracking: PtzAutotrackConfig = Field( default_factory=PtzAutotrackConfig, title="Autotracking", diff --git a/frigate/ptz/onvif.py b/frigate/ptz/onvif.py index 488dbd278..2afa5b848 100644 --- a/frigate/ptz/onvif.py +++ b/frigate/ptz/onvif.py @@ -9,6 +9,7 @@ from importlib.util import find_spec from pathlib import Path from typing import Any +import aiohttp import numpy from onvif import ONVIFCamera, ONVIFError, ONVIFService from zeep.exceptions import Fault, TransportError @@ -104,6 +105,20 @@ class OnvifController: if password is not None and isinstance(password, bytes): password = password.decode("utf-8") + # Build extra kwargs for digest auth mode (Hikvision and similar cameras + # that reject WSSE wsUsername tokens and require HTTP Digest at transport level). + onvif_extra: dict[str, Any] = {} + if cam.onvif.auth_mode == "digest" and user and password: + try: + onvif_extra["middlewares"] = [ + aiohttp.DigestAuthMiddleware(user, password) + ] + except AttributeError: + logger.warning( + f"DigestAuthMiddleware not available in installed aiohttp version " + f"for camera {cam_name}; falling back to default auth." + ) + self.cams[cam_name] = { "onvif": ONVIFCamera( cam.onvif.host, @@ -113,6 +128,7 @@ class OnvifController: wsdl_dir=str(Path(find_spec("onvif").origin).parent / "wsdl"), adjust_time=cam.onvif.ignore_time_mismatch, encrypt=not cam.onvif.tls_insecure, + **onvif_extra, ), "init": False, "active": False,