From nobody Thu Oct 9 02:57:41 2025 Received: from pf-012.whm.fr-par.scw.cloud (pf-012.whm.fr-par.scw.cloud [51.159.173.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4D0AE2989B3; Fri, 20 Jun 2025 14:46:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=51.159.173.17 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750430797; cv=none; b=fkdbRqmSFSybbQFl6sTuzRZZwb3XHNajEdfGZCiwXGA9EDNaUr90fi/OXnoemN9+Yb3h5APzJ/13KX3Esh0LxsAwwmVJwtgXhQasSoqEZDTsU43XNA7QVSSZO0k9YpoHBtFet/gIxyupFwxmxs3Zk4csbsCIEGC9D2RGgBIcprM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750430797; c=relaxed/simple; bh=GRkb/qq4Wx8i38tcL0LlieTRN7w3kZ3wMHjvWqCaOU4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=u4hMwVdFP1CxNd/WtKyYQgrjCidVb/r7jPG/4d/tTscUjRmTdqJeSPspae7pwvQcOQAmeYqIlk1l/J47Jc/BSOM6pAHDVdlWUcpCqAs+HbJ8F52FjTREC0wGNwMwsATVVdEf3eJyLVamrPXMKfZVOS0sg5NZBMI8hPKCQ4vDcPo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr; spf=pass smtp.mailfrom=oss.cyber.gouv.fr; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b=Ueo9+v7g; arc=none smtp.client-ip=51.159.173.17 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b="Ueo9+v7g" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=oss.cyber.gouv.fr; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=Usx8V9NjoNpeamL3vdDd1X0UGwTUjb1xoGpBSE/UIcY=; b=Ueo9+v7g6rPPzUzSYF85wKRol8 r9+mQ0C9PVcoC5V+26+PiXVcweDVF61IeVrUc2evC1Dq2yVTRMkngDFHlUGfHtHTJhzfVzWg0yS+i BdCetHT3eqJ8LT4A5WC0OglAgyFrO5r2zl2e5yM4zfkeH1CcEXZVPKzfFfWvGiIsAkHZutoWSCCsA M/wAP9zZvsQGSK4j5hTJywN0ZpMIPh7Pi7SirBAJAsZrIm3ArIynr93fc+w5pz1kKrKJJX/AJDGz9 8KEMp4iL6rSFp2CKLDzWzH+kRS0HkIV7YEZwacCnsx6U3sa9g+Vwo02DW+8Z1bS1mm35NBWCNMQDa YLakarhw==; Received: from laubervilliers-658-1-215-187.w90-63.abo.wanadoo.fr ([90.63.246.187]:43571 helo=[10.224.8.110]) by pf-012.whm.fr-par.scw.cloud with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1uSciZ-00000007rNQ-3MCS; Fri, 20 Jun 2025 16:27:45 +0200 From: nicolas.bouchinet@oss.cyber.gouv.fr Date: Fri, 20 Jun 2025 16:27:16 +0200 Subject: [RFC PATCH 1/4] usb: core: Introduce netlink usb authentication policy engine Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250620-usb_authentication-v1-1-0d92261a5779@ssi.gouv.fr> References: <20250620-usb_authentication-v1-0-0d92261a5779@ssi.gouv.fr> In-Reply-To: <20250620-usb_authentication-v1-0-0d92261a5779@ssi.gouv.fr> To: Greg Kroah-Hartman Cc: Alan Stern , Kannappan R , Sabyrzhan Tasbolatov , Krzysztof Kozlowski , Stefan Eichenberger , Thomas Gleixner , Pawel Laszczak , Ma Ke , Jeff Johnson , Luc Bonnafoux , Luc Bonnafoux , Nicolas Bouchinet , linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org X-Mailer: b4 0.14.2 X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - pf-012.whm.fr-par.scw.cloud X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - oss.cyber.gouv.fr X-Get-Message-Sender-Via: pf-012.whm.fr-par.scw.cloud: authenticated_id: nicolas.bouchinet@oss.cyber.gouv.fr X-Authenticated-Sender: pf-012.whm.fr-par.scw.cloud: nicolas.bouchinet@oss.cyber.gouv.fr X-Source: X-Source-Args: X-Source-Dir: From: Nicolas Bouchinet The usb authentication feature needs a policy engine in order to authorize or deny usb devices based on a user defined policy. In order to reduce the attack surface and in-kernel complexity, policy management, crypto operations and complex parsing have been implemented in userspace using the generic netlink API. The full authentication protocol is kernel driven. The following unicast netlink commands have been defined in order to fulfill device authentication : - USBAUTH_CMD_REGISTER This is the beginning of any authentication. The kernel first wait for the userspace service to connect to the socket using the `USBAUTH_CMD_REGISTER` netlink command. Upon connection, the kernel check that the userspace service has the `CAP_SYS_ADMIN` capability beforing enrolling the service. Only one userspace service can be registered. - USBAUTH_CMD_CHECK_DIGEST The kernel then sends a `USBAUTH_CMD_CHECK_DIGEST` netlink command to the policy engine to be verified. The policy engine checks if the device ceritificates has already been encountered. - USBAUTH_CMD_RESP_DIGEST After the policy engine has received an usb device certificate digest list from kernel, it needs to reply if it knows one of them using the `USBAUTH_CMD_RESP_DIGEST` netlink command. - USBAUTH_CMD_CHECK_CERTIFICATE The kernel then sends a `USBAUTH_CMD_CHECK_CERTIFICATE` netlink command to the policy engine. Each command contains one certificate chain. The policy engine verifies if the device certificate chain is trusted. - USBAUTH_CMD_RESP_CERTIFICATE After checking the certificate chain, the policy engine sends a `USBAUTH_CMD_RESP_CERTIFICATE` response. It tells the kernel if the device certificate chain is trusted and thus if the device authentication should continue. Once device has been validated either through the digest or certificate chain validation, an authentication session is started and a device ID is associated for this session. The ID will then be used in all the following commands. - USBAUTH_CMD_GEN_NONCE Kernel then asks for a nonce generation in order to challenge the device using the `USBAUTH_GEN_NONCE` netlink command. - USBAUTH_CMD_RESP_GEN_NONCE When the nonce has been generated by the policy engine it is sent back to the kernel using the `USBAUTH_CMD_RESP_GEN_NONCE` netlink command. - USBAUTH_CMD_CHECK_CHALL Once the kernel has received a device challenge response, it forwards the response to the policy engine for validation using the `USBAUTH_CMD_CHECK_CHALL` netlink command. - USBAUTH_CMD_RESP_CHECK_CHALL The policy engine then verifies the challenge and replies its decision to the kernel using the `USBAUTH_CMD_RESP_CHECK_CHALL` netlink command. - USBAUTH_CMD_REMOVE_DEV - USBAUTH_CMD_RESP_REMOVE_DEV Those two commands have been provionned but have not been implemented yet. If at any time, the policy engine wants to remove the trust in a device, then the `USBAUTH_CMD_REMOVE_DEV` would to be sent, the kernel replies with an error status through the `USBAUTH_CMD_RESP_REMOVE_DEV` command. Co-developed-by: Luc Bonnafoux Signed-off-by: Luc Bonnafoux Signed-off-by: Nicolas Bouchinet --- drivers/usb/core/authent_netlink.c | 1080 +++++++++++++++++++++++++= ++++ drivers/usb/core/authent_netlink.h | 157 +++++ include/uapi/linux/usb/usb_auth_netlink.h | 67 ++ 3 files changed, 1304 insertions(+) diff --git a/drivers/usb/core/authent_netlink.c b/drivers/usb/core/authent_= netlink.c new file mode 100644 index 0000000000000000000000000000000000000000..d53a220c762ffc1bd9aeb95bf90= dc0dd79c43f09 --- /dev/null +++ b/drivers/usb/core/authent_netlink.c @@ -0,0 +1,1080 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SPDX-FileCopyrightText: (C) 2025 ANSSI + * + * USB Authentication netlink interface + * + * Author: Luc Bonnafoux + * Author: Nicolas Bouchinet + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "authent_netlink.h" + +#define WAIT_USERSPACE_TIMEOUT 30 +#define WAIT_RESPONSE_TIMEOUT 300 +#define USB_AUTH_MAX_RESP_SIZE 128 + +/** + * Define an outstanding request between the kernel and userspace + */ +struct usb_auth_req { + uint8_t used; /**< 1 if the slot is currently used, access must be protec= ted */ + uint8_t done; /**< 1 if the response has been received, used as wait cond= ition */ + uint8_t error; /**< userspace response error code */ + uint8_t resp[USB_AUTH_MAX_RESP_SIZE]; /**< arbitrary response buffer */ +}; + +static struct genl_family usbauth_genl_fam; + +// TODO: add mutex for PID access +static u32 pol_eng_pid; +static struct net *pol_eng_net; + +static wait_queue_head_t usb_req_wq; + +#define USB_AUTH_MAX_OUTSTANDING_REQS 10 +// Mutex is used to protect access to the `used` field +DEFINE_MUTEX(usb_auth_reqs_mutex); +static struct usb_auth_req usb_auth_outstanding_reqs[USB_AUTH_MAX_OUTSTAND= ING_REQS]; + +//////////////////////////////////////////////////////////////////////////= ////// +// USB request utilities +//////////////////////////////////////////////////////////////////////////= ////// + +/** + * @brief Find the first available slot in the outstanding requests array = and + * reserve it. + * + * CAUTION: this function will block on the request list mutex + * + * Possible error codes: + * - EXFULL : too many outstanding requests already + * + * @param [out] index : reserved slot index, valid if return equals 0 + * + * @return 0 on SUCCESS or error code + */ +static int usb_auth_get_reqs_slot(uint32_t *index) +{ + int ret =3D -EXFULL; + uint32_t i =3D 0; + + mutex_lock(&usb_auth_reqs_mutex); + + // Take the first available slot + for (i =3D 0; i < USB_AUTH_MAX_OUTSTANDING_REQS; i++) { + if (usb_auth_outstanding_reqs[i].used =3D=3D 0) { + usb_auth_outstanding_reqs[i].used =3D 1; + usb_auth_outstanding_reqs[i].done =3D 0; + usb_auth_outstanding_reqs[i].error =3D USBAUTH_OK; + memset(usb_auth_outstanding_reqs[i].resp, 0, + USB_AUTH_MAX_RESP_SIZE); + *index =3D i; + ret =3D 0; + break; + } + } + + mutex_unlock(&usb_auth_reqs_mutex); + + return ret; +} + +/** + * @brief release a request slot + * + * CAUTION: this function will block on the request list mutex + * + * @param [in] index : slot index to be released + */ +static void usb_auth_release_reqs_slot(const uint32_t index) +{ + mutex_lock(&usb_auth_reqs_mutex); + + usb_auth_outstanding_reqs[index].used =3D 0; + + mutex_unlock(&usb_auth_reqs_mutex); +} + +//////////////////////////////////////////////////////////////////////////= ////// +// Generic netlink socket utilities +//////////////////////////////////////////////////////////////////////////= ////// + +/** + * @brief Handle a registration request from userspace + * + * It will overwrite the current userspace registered PID with the one pro= vided + * in the request + */ +static int usb_auth_register_req_doit(struct sk_buff *skb, struct genl_inf= o *info) +{ + int ret =3D 0; + void *hdr =3D NULL; + struct sk_buff *msg =3D NULL; + + pr_info("message received\n"); + + if (!capable(CAP_SYS_ADMIN)) { + pr_err("invalid permissions\n"); + return -EPERM; + } + + // Register Policy engine PID, overwrite value if already set + pol_eng_pid =3D info->snd_portid; + pol_eng_net =3D genl_info_net(info); + + wake_up_all(&usb_req_wq); + + msg =3D nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (msg =3D=3D NULL) { + pr_err("failed to allocate message buffer\n"); + return -ENOMEM; + } + + hdr =3D genlmsg_put(msg, info->snd_portid, info->snd_seq, + &usbauth_genl_fam, 0, USBAUTH_CMD_REGISTER); + if (hdr =3D=3D NULL) { + pr_err("failed to create genetlink header\n"); + nlmsg_free(msg); + return -EMSGSIZE; + } + + genlmsg_end(msg, hdr); + + ret =3D genlmsg_reply(msg, info); + + pr_info("reply sent\n"); + + return ret; +} + +/** + * @brief Handle a CHECK_DIGEST response from userspace + * + * The response must contain: + * - USBAUTH_A_REQ_ID + * - USBAUTH_A_ERROR_CODE + * - USBAUTH_A_DEV_ID + * - USBAUTH_A_KNOWN + * - USBAUTH_A_BLOCKED + * + */ +static int usb_auth_digest_resp_doit(struct sk_buff *skb, struct genl_info= *info) +{ + uint32_t index =3D 0; + + pr_info("message received\n"); + + if (!capable(CAP_SYS_ADMIN)) { + pr_err("invalid permissions\n"); + return -EPERM; + } + + if (!info->attrs[USBAUTH_A_REQ_ID]) { + pr_err("digest_resp_doit: invalid response: no req ID\n"); + return -EINVAL; + } + + index =3D nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]); + + // Test for error + if (!info->attrs[USBAUTH_A_ERROR_CODE]) { + pr_err("digest_resp_doit: invalid response: missing attributes\n"); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].error =3D + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]); + + if (usb_auth_outstanding_reqs[index].error !=3D USBAUTH_OK) { + pr_err("digest_resp_doit: response error\n"); + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + if (!info->attrs[USBAUTH_A_DEV_ID] || !info->attrs[USBAUTH_A_KNOWN] || + !info->attrs[USBAUTH_A_BLOCKED]) { + pr_err("digest_resp_doit: invalid response: missing attributes\n"); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].resp[0] =3D + nla_get_u8(info->attrs[USBAUTH_A_KNOWN]); + usb_auth_outstanding_reqs[index].resp[1] =3D + nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]); + ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0] =3D + nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]); + + usb_auth_outstanding_reqs[index].done =3D 1; + + wake_up_all(&usb_req_wq); + + return 0; +} + +/** + * @brief Handle a CHECK_CERTIFICATE response from userspace + * + * The response must contain: + * - USBAUTH_A_REQ_ID + * - USBAUTH_A_ERROR_CODE + * - USBAUTH_A_VALID + * - USBAUTH_A_BLOCKED + * - USBAUTH_A_DEV_ID + * + */ +static int usb_auth_cert_resp_doit(struct sk_buff *skb, struct genl_info *= info) +{ + uint32_t index =3D 0; + + pr_info("message received\n"); + + if (!capable(CAP_SYS_ADMIN)) { + pr_err("invalid permissions\n"); + return -EPERM; + } + + if (!info->attrs[USBAUTH_A_REQ_ID]) { + pr_err("invalid response: no req ID\n"); + return -EINVAL; + } + + index =3D nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]); + + // Test for error + if (!info->attrs[USBAUTH_A_ERROR_CODE]) { + pr_err("invalid response: missing attributes\n"); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].error =3D + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]); + + if (usb_auth_outstanding_reqs[index].error !=3D USBAUTH_OK) { + pr_err("response error\n"); + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + if (!info->attrs[USBAUTH_A_DEV_ID] || !info->attrs[USBAUTH_A_VALID] || + !info->attrs[USBAUTH_A_BLOCKED]) { + pr_err("invalid response: missing attributes\n"); + usb_auth_outstanding_reqs[index].done =3D 1; + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].resp[0] =3D + nla_get_u8(info->attrs[USBAUTH_A_VALID]); + usb_auth_outstanding_reqs[index].resp[1] =3D + nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]); + ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0] =3D + nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]); + + usb_auth_outstanding_reqs[index].done =3D 1; + + wake_up_all(&usb_req_wq); + + return 0; +} + +/** + * @brief Handle a REMOVE_DEV response from userspace + * + * The response must contain: + * - USBAUTH_A_REQ_ID + * - USBAUTH_A_ERROR_CODE + * - USBAUTH_A_VALID + * + */ +static int usb_auth_remove_dev_doit(struct sk_buff *skb, struct genl_info = *info) +{ + uint32_t index =3D 0; + + pr_info("message received\n"); + + if (!capable(CAP_SYS_ADMIN)) { + pr_err("invalid permissions\n"); + return -EPERM; + } + + if (!info->attrs[USBAUTH_A_REQ_ID]) { + pr_err("invalid response: no req ID\n"); + return -EINVAL; + } + + index =3D nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]); + + // Test for error + if (!info->attrs[USBAUTH_A_ERROR_CODE]) { + pr_err("invalid response: missing attributes\n"); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].error =3D + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]); + + if (usb_auth_outstanding_reqs[index].error !=3D USBAUTH_OK) { + pr_err("response error\n"); + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + if (!info->attrs[USBAUTH_A_VALID]) { + pr_err("invalid response: missing attributes\n"); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].resp[0] =3D + nla_get_u8(info->attrs[USBAUTH_A_VALID]); + + usb_auth_outstanding_reqs[index].done =3D 1; + + wake_up_all(&usb_req_wq); + + return 0; +} + +/** + * @brief Handle a GEN_NONCE response from userspace + * + * The response must contain: + * - USBAUTH_A_REQ_ID + * - USBAUTH_A_ERROR_CODE + * - USBAUTH_A_NONCE + * + */ +static int usb_auth_gen_nonce_doit(struct sk_buff *skb, struct genl_info *= info) +{ + uint32_t index =3D 0; + + pr_info("message received\n"); + + if (!capable(CAP_SYS_ADMIN)) { + pr_err("invalid permissions\n"); + return -EPERM; + } + + if (!info->attrs[USBAUTH_A_REQ_ID]) { + pr_err("invalid response: no req ID\n"); + return -EINVAL; + } + + index =3D nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]); + + // Test for error + if (!info->attrs[USBAUTH_A_ERROR_CODE]) { + pr_err("invalid response: missing attributes\n"); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].error =3D + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]); + + if (usb_auth_outstanding_reqs[index].error !=3D USBAUTH_OK) { + pr_err("response error\n"); + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + if (!info->attrs[USBAUTH_A_NONCE]) { + pr_err("invalid response: missing attributes\n"); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + nla_memcpy(usb_auth_outstanding_reqs[index].resp, info->attrs[USBAUTH_A_N= ONCE], 32); + + usb_auth_outstanding_reqs[index].done =3D 1; + + wake_up_all(&usb_req_wq); + + return 0; +} + +/** + * @brief Handle a CHECK_CHALL response from userspace + * + * The response must contain: + * - USBAUTH_A_REQ_ID + * - USBAUTH_A_ERROR_CODE + * - USBAUTH_A_VALID + * + */ +static int usb_auth_check_chall_doit(struct sk_buff *skb, struct genl_info= *info) +{ + uint32_t index =3D 0; + + pr_info("message received\n"); + + if (!capable(CAP_SYS_ADMIN)) { + pr_err("invalid permissions\n"); + return -EPERM; + } + + if (!info->attrs[USBAUTH_A_REQ_ID]) { + pr_err("invalid response: no req ID\n"); + return -EINVAL; + } + + index =3D nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]); + + // Test for error + if (!info->attrs[USBAUTH_A_ERROR_CODE]) { + pr_err("invalid response: missing attributes\n"); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].error =3D + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]); + + if (usb_auth_outstanding_reqs[index].error !=3D USBAUTH_OK) { + pr_err("response error\n"); + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + if (!info->attrs[USBAUTH_A_VALID]) { + pr_err("invalid response: missing attributes\n"); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].resp[0] =3D + nla_get_u8(info->attrs[USBAUTH_A_VALID]); + + usb_auth_outstanding_reqs[index].done =3D 1; + + wake_up_all(&usb_req_wq); + + return 0; +} + +/* Attribute validation policy */ +static struct nla_policy usbauth_attr_pol[USBAUTH_A_MAX + 1] =3D { + [USBAUTH_A_REQ_ID] =3D {.type =3D NLA_U32,}, + [USBAUTH_A_DEV_ID] =3D {.type =3D NLA_U32,}, + [USBAUTH_A_DIGEST] =3D {.type =3D NLA_UNSPEC, .len =3D 32,}, + [USBAUTH_A_DIGESTS] =3D {.type =3D NLA_UNSPEC, .len =3D 256,}, + [USBAUTH_A_SLOT_MASK] =3D {.type =3D NLA_U8,}, + [USBAUTH_A_KNOWN] =3D {.type =3D NLA_U8,}, + [USBAUTH_A_BLOCKED] =3D {.type =3D NLA_U8,}, + [USBAUTH_A_VALID] =3D {.type =3D NLA_U8,}, + [USBAUTH_A_CERTIFICATE] =3D {.type =3D NLA_UNSPEC, .max =3D 4096,}, + [USBAUTH_A_CERT_LEN] =3D {.type =3D NLA_U32}, + [USBAUTH_A_ROUTE] =3D {.type =3D NLA_U32}, + [USBAUTH_A_NONCE] =3D {.type =3D NLA_BINARY, .len =3D 32,}, + [USBAUTH_A_CHALL] =3D {.type =3D NLA_UNSPEC, .len =3D 204,}, + [USBAUTH_A_DESCRIPTOR] =3D {.type =3D NLA_UNSPEC, .len =3D USBAUTH_MAX_DE= SC_SIZE}, + [USBAUTH_A_BOS] =3D {.type =3D NLA_UNSPEC, .len =3D USBAUTH_MAX_BOS_SIZE}, + [USBAUTH_A_ERROR_CODE] =3D {.type =3D NLA_U8}, +}; + +/* Operations */ +static struct genl_ops usbauth_genl_ops[] =3D { + { + .cmd =3D USBAUTH_CMD_REGISTER, + .policy =3D usbauth_attr_pol, + .doit =3D usb_auth_register_req_doit, + }, + { + .cmd =3D USBAUTH_CMD_RESP_DIGEST, + .policy =3D usbauth_attr_pol, + .doit =3D usb_auth_digest_resp_doit, + }, + { + .cmd =3D USBAUTH_CMD_RESP_CERTIFICATE, + .policy =3D usbauth_attr_pol, + .doit =3D usb_auth_cert_resp_doit, + }, + { + .cmd =3D USBAUTH_CMD_RESP_REMOVE_DEV, + .policy =3D usbauth_attr_pol, + .doit =3D usb_auth_remove_dev_doit, + }, + { + .cmd =3D USBAUTH_CMD_RESP_GEN_NONCE, + .policy =3D usbauth_attr_pol, + .doit =3D usb_auth_gen_nonce_doit, + }, + { + .cmd =3D USBAUTH_CMD_RESP_CHECK_CHALL, + .policy =3D usbauth_attr_pol, + .doit =3D usb_auth_check_chall_doit, + } +}; + +/* USB Authentication netlink family definition */ +static struct genl_family usbauth_genl_fam =3D { + .name =3D USBAUTH_GENL_NAME, + .version =3D USBAUTH_GENL_VERSION, + .maxattr =3D USBAUTH_A_MAX, + .ops =3D usbauth_genl_ops, + .n_ops =3D ARRAY_SIZE(usbauth_genl_ops), + .mcgrps =3D NULL, + .n_mcgrps =3D 0, +}; + +int usb_auth_init_netlink(void) +{ + int ret =3D 0; + uint8_t i =3D 0; + + for (i =3D 0; i < USB_AUTH_MAX_OUTSTANDING_REQS; i++) + usb_auth_outstanding_reqs[i].used =3D 0; + + init_waitqueue_head(&usb_req_wq); + + ret =3D genl_register_family(&usbauth_genl_fam); + if (unlikely(ret)) { + pr_err("failed to init netlink: %d\n", + ret); + return ret; + } + + pr_info("initialized\n"); + + return ret; +} + +//////////////////////////////////////////////////////////////////////////= ////// +// Policy engine API +//////////////////////////////////////////////////////////////////////////= ////// + +int usb_policy_engine_check_digest(const uint32_t route, const uint8_t *co= nst digests, + const uint8_t mask, uint8_t *is_known, uint8_t *is_blocked, uint32_t *id) +{ + int ret =3D -1; + void *hdr =3D NULL; + struct sk_buff *skb =3D NULL; + uint32_t index =3D 0; + + if (digests =3D=3D NULL) { + pr_err("invalid inputs\n"); + return -EINVAL; + } + + // Arbitrary 30s wait before giving up + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + pr_err("userspace not available\n"); + return -ECOMM; + } + + pr_info("got connection to userspace\n"); + + // Get a slot in the outstanding request list + if (usb_auth_get_reqs_slot(&index)) { + pr_err("failed to get request slot\n"); + return -ECOMM; + } + pr_info("got request slot\n"); + + // Create digests check request + skb =3D genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb =3D=3D NULL) { + pr_err("failed to allocated buffer\n"); + return -ENOMEM; + } + + hdr =3D genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0, + USBAUTH_CMD_CHECK_DIGEST); + if (unlikely(hdr =3D=3D NULL)) { + pr_err("failed to place header\n"); + nlmsg_free(skb); + return -ENOMEM; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_REQ_ID, index); + if (ret) { + pr_err("failed to place req ID\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_ROUTE, route); + if (ret) { + pr_err("failed to place route\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put(skb, USBAUTH_A_DIGESTS, 260, digests); + if (ret) { + pr_err("failed to place digests\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u8(skb, USBAUTH_A_SLOT_MASK, mask); + if (ret) { + pr_err("failed to place slot mask\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + genlmsg_end(skb, hdr); + + // Send message to userspace + ret =3D genlmsg_unicast(pol_eng_net, skb, pol_eng_pid); + if (ret !=3D 0) { + pr_err("failed to send message: %d\n", + ret); + return -ECOMM; + } + pr_info("sent CHECK_DIGEST request\n"); + + // Wait for userspace response + // Arbitrary 5 min wait before giving up + if (!wait_event_timeout(usb_req_wq, + usb_auth_outstanding_reqs[index].done =3D=3D 1, + HZ * WAIT_RESPONSE_TIMEOUT)) { + pr_err("userspace response not available\n"); + usb_auth_release_reqs_slot(index); + return -ECOMM; + } + + pr_info("received CHECK_DIGEST response\n"); + + // Get response + if (usb_auth_outstanding_reqs[index].error =3D=3D USBAUTH_INVRESP) { + pr_err("userspace response error: %d\n", + usb_auth_outstanding_reqs[index].error); + usb_auth_release_reqs_slot(index); + return -ECOMM; + } + + *is_known =3D usb_auth_outstanding_reqs[index].resp[0]; + *is_blocked =3D usb_auth_outstanding_reqs[index].resp[1]; + *id =3D ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0]; + + // Release request slot + usb_auth_release_reqs_slot(index); + + return 0; +} + +int usb_policy_engine_check_cert_chain(const uint32_t route, + const uint8_t *const digest, const uint8_t *const chain, + const size_t chain_len, uint8_t *is_valid, uint8_t *is_blocked, uint32_t = *id) +{ + int ret =3D -1; + void *hdr =3D NULL; + struct sk_buff *skb =3D NULL; + uint32_t index =3D 0; + + if (chain =3D=3D NULL || chain_len > 4096 || digest =3D=3D NULL) { + pr_err("invalid inputs\n"); + return -EINVAL; + } + + // Arbitrary 30s wait before giving up + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + pr_err("userspace not available\n"); + return -ECOMM; + } + + pr_info("got connection to userspace\n"); + + // Get a slot in the outstanding request list + if (usb_auth_get_reqs_slot(&index)) { + pr_err("failed to get request slot\n"); + return -ECOMM; + } + pr_info("got request slot\n"); + + // Create digest check request + skb =3D genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb =3D=3D NULL) { + pr_err("failed to allocated buffer\n"); + return -ENOMEM; + } + + hdr =3D genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0, + USBAUTH_CMD_CHECK_CERTIFICATE); + if (unlikely(hdr =3D=3D NULL)) { + pr_err("failed to place header\n"); + nlmsg_free(skb); + return -ENOMEM; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_REQ_ID, index); + if (ret) { + pr_err("failed to place req ID\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_ROUTE, route); + if (ret) { + pr_err("failed to place route\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put(skb, USBAUTH_A_DIGEST, 32, digest); + if (ret) { + pr_err("failed to place digest\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put(skb, USBAUTH_A_CERTIFICATE, chain_len, chain); + if (ret) { + pr_err("failed to place certificate\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_CERT_LEN, chain_len); + if (ret) { + pr_err("failed to place chain length\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + genlmsg_end(skb, hdr); + + // Send message to userspace + ret =3D genlmsg_unicast(pol_eng_net, skb, pol_eng_pid); + if (ret !=3D 0) { + pr_err("failed to send message: %d\n", + ret); + return -ECOMM; + } + pr_info("sent CHECK_CERTIFICATE request\n"); + + // Wait for userspace response + // Arbitrary 5 min wait before giving up + if (!wait_event_timeout(usb_req_wq, + usb_auth_outstanding_reqs[index].done =3D=3D 1, + HZ * WAIT_RESPONSE_TIMEOUT)) { + pr_err("userspace response not available\n"); + usb_auth_release_reqs_slot(index); + return -ECOMM; + } + + pr_info("received CHECK_CERTIFICATE response\n"); + + // Get response + *is_valid =3D usb_auth_outstanding_reqs[index].resp[0]; + *is_blocked =3D usb_auth_outstanding_reqs[index].resp[1]; + *id =3D ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0]; + + // Release request slot + usb_auth_release_reqs_slot(index); + + return 0; +} + +int usb_policy_engine_remove_dev(const uint32_t id) +{ + int ret =3D -1; + void *hdr =3D NULL; + struct sk_buff *skb =3D NULL; + uint32_t index =3D 0; + + // Arbitrary 30s wait before giving up + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + pr_err("userspace not available\n"); + return -ECOMM; + } + + pr_info("got connection to userspace\n"); + + // Get a slot in the outstanding request list + if (usb_auth_get_reqs_slot(&index)) { + pr_err("failed to get request slot\n"); + return -ECOMM; + } + pr_info("got request slot\n"); + + // Create digest check request + skb =3D genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb =3D=3D NULL) { + pr_err("failed to allocated buffer\n"); + return -ENOMEM; + } + + hdr =3D genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0, + USBAUTH_CMD_REMOVE_DEV); + if (unlikely(hdr =3D=3D NULL)) { + pr_err("failed to place header\n"); + nlmsg_free(skb); + return -ENOMEM; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_REQ_ID, index); + if (ret) { + pr_err("failed to place req ID\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_DEV_ID, id); + if (ret) { + pr_err("failed to place dev ID\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + genlmsg_end(skb, hdr); + + // Send message to userspace + ret =3D genlmsg_unicast(pol_eng_net, skb, pol_eng_pid); + if (ret !=3D 0) { + pr_err("failed to send message: %d\n", + ret); + return -ECOMM; + } + pr_info("sent REMOVE_DEV request\n"); + + // Wait for userspace response + // Arbitrary 5 min wait before giving up + if (!wait_event_timeout(usb_req_wq, + usb_auth_outstanding_reqs[index].done =3D=3D 1, + HZ * WAIT_RESPONSE_TIMEOUT)) { + pr_err("userspace response not available\n"); + usb_auth_release_reqs_slot(index); + return -ECOMM; + } + + pr_info("received REMOVE_DEV response\n"); + + // Release request slot + usb_auth_release_reqs_slot(index); + + return 0; +} + +int usb_policy_engine_generate_challenge(const uint32_t id, uint8_t *nonce) +{ + int ret =3D -1; + void *hdr =3D NULL; + struct sk_buff *skb =3D NULL; + uint32_t index =3D 0; + + // Arbitrary 30s wait before giving up + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + pr_err("userspace not available\n"); + return -ECOMM; + } + + pr_info("got connection to userspace\n"); + + // Get a slot in the outstanding request list + if (usb_auth_get_reqs_slot(&index)) { + pr_err("failed to get request slot\n"); + return -ECOMM; + } + pr_info("got request slot\n"); + + // Create digest check request + skb =3D genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb =3D=3D NULL) { + pr_err("failed to allocated buffer\n"); + return -ENOMEM; + } + + hdr =3D genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0, + USBAUTH_CMD_GEN_NONCE); + if (unlikely(hdr =3D=3D NULL)) { + pr_err("failed to place header\n"); + nlmsg_free(skb); + return -ENOMEM; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_REQ_ID, index); + if (ret) { + pr_err("failed to place req ID\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_DEV_ID, id); + if (ret) { + pr_err("failed to place dev ID\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + genlmsg_end(skb, hdr); + + // Send message to userspace + ret =3D genlmsg_unicast(pol_eng_net, skb, pol_eng_pid); + if (ret !=3D 0) { + pr_err("failed to send message: %d\n", ret); + return -ECOMM; + } + pr_info("sent GEN_NONCE request\n"); + + // Wait for userspace response + // Arbitrary 5 min wait before giving up + if (!wait_event_timeout(usb_req_wq, + usb_auth_outstanding_reqs[index].done =3D=3D 1, + HZ * WAIT_RESPONSE_TIMEOUT)) { + pr_err("userspace response not available\n"); + usb_auth_release_reqs_slot(index); + return -ECOMM; + } + + pr_info("received GEN_NONCE response\n"); + + // Get response + memcpy(nonce, usb_auth_outstanding_reqs[index].resp, 32); + + // Release request slot + usb_auth_release_reqs_slot(index); + + return 0; +} + +int usb_policy_engine_check_challenge(const uint32_t id, + const uint8_t *const challenge, const uint8_t *const context, + const size_t context_size, uint8_t *is_valid) +{ + int ret =3D -1; + void *hdr =3D NULL; + struct sk_buff *skb =3D NULL; + uint32_t index =3D 0; + + if (unlikely(challenge =3D=3D NULL || context =3D=3D NULL || + context_size > USBAUTH_MAX_BOS_SIZE)) { + pr_err("invalid inputs\n"); + return -EINVAL; + } + + // Arbitrary 30s wait before giving up + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + pr_err("userspace not available\n"); + return -ECOMM; + } + + pr_info("got connection to userspace\n"); + + // Get a slot in the outstanding request list + if (usb_auth_get_reqs_slot(&index)) { + pr_err("failed to get request slot\n"); + return -ECOMM; + } + pr_info("got request slot\n"); + + // Create digest check request + skb =3D genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb =3D=3D NULL) { + pr_err("failed to allocated buffer\n"); + return -ENOMEM; + } + + hdr =3D genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0, + USBAUTH_CMD_CHECK_CHALL); + if (unlikely(hdr =3D=3D NULL)) { + pr_err("failed to place header\n"); + nlmsg_free(skb); + return -ENOMEM; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_REQ_ID, index); + if (ret) { + pr_err("failed to place req ID\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put(skb, USBAUTH_A_CHALL, 204, challenge); + if (ret) { + pr_err("failed to place challenge\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put(skb, USBAUTH_A_DESCRIPTOR, context_size, context); + if (ret) { + pr_err("failed to place descriptor\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_DEV_ID, id); + if (ret) { + pr_err("failed to place dev ID\n"); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + genlmsg_end(skb, hdr); + + // Send message to userspace + ret =3D genlmsg_unicast(pol_eng_net, skb, pol_eng_pid); + if (ret !=3D 0) { + pr_err("failed to send message: %d\n", + ret); + return -ECOMM; + } + pr_info("sent CHECK_CHALL request\n"); + + // Wait for userspace response + // Arbitrary 5 min wait before giving up + if (!wait_event_timeout(usb_req_wq, + usb_auth_outstanding_reqs[index].done =3D=3D 1, + HZ * WAIT_RESPONSE_TIMEOUT)) { + pr_err("userspace response not available\n"); + usb_auth_release_reqs_slot(index); + return -ECOMM; + } + + pr_info("received CHECK_CHALL response\n"); + + // Get response + *is_valid =3D usb_auth_outstanding_reqs[index].resp[0]; + + // Release request slot + usb_auth_release_reqs_slot(index); + + return 0; +} diff --git a/drivers/usb/core/authent_netlink.h b/drivers/usb/core/authent_= netlink.h new file mode 100644 index 0000000000000000000000000000000000000000..504da32547b75b85b4328f3ea7d= f43b0a565dd18 --- /dev/null +++ b/drivers/usb/core/authent_netlink.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SPDX-FileCopyrightText: (C) 2025 ANSSI + * + * USB Authentication netlink interface + * + * Author: Luc Bonnafoux + * Author: Nicolas Bouchinet + * + */ + +#ifndef __USB_CORE_AUTHENT_NETLINK_H_ +#define __USB_CORE_AUTHENT_NETLINK_H_ + +int usb_auth_init_netlink(void); + +/** + * @brief Check if a digest match a device + * + * This function blocks until a response has been received from userspace = or in + * case of timeout. + * The function blocks if no policy engine is registered with a timeout. + * + * Context: task context, might sleep. + * + * Possible errors: + * - EINVAL : if digest is NULL + * - ECOMM : if no userspace policy engine is available + * or already userspace is busy + * or message transmission failed + * - ENOMEM : if message creation failed + * - EMSGSIZE : if message creation failed + * + * @param [in] digest : USB Authentication digest, must be 256 B + * @param [in] mask : USB Authentication slot mask + * @param [out] is_known : 1 at each index with a known digest, 0 otherw= ise + * @param [out] is_blocked : 1 if the device is known and banned, 0 otherw= ise + * @param [out] id : if is_known and !is_blocked then contains the= device handle + * + * @return 0 on SUCCESS else error code + */ +int usb_policy_engine_check_digest(const uint32_t route, + const uint8_t *const digests, + const uint8_t mask, uint8_t *is_known, + uint8_t *is_blocked, uint32_t *id); + +/** + * @brief Check if a certificate chain is valid and authorized + * + * A certificate chain is valid if it can be successfully verified with on= e of the + * root CA in store. + * A certificate chain is blocked if one of the certificate of chain is bl= ocked, + * due to revocation, blacklist... + * + * Context: task context, might sleep. + * + * Possible errors: + * - EINVAL : if digest is NULL + * - ECOMM : if no userspace policy engine is available + * or already userspace is busy + * or message transmission failed + * - ENOMEM : if message creation failed + * - EMSGSIZE : if message creation failed + * + * TODO: see if it is necessary to have a separate boolean for is_blocked + * + * @param [in] route : Information on the device to construct the ID + * @param [in] digest : Digest corresponding to the certificate chain + * @param [in] chain : Certificate chain to check, at most 4096 byt= es + * @param [in] chain_length : Certificate chain length + * @param [out] is_valid : 1 if the certificate chain can be validated + * @param [out] is_blocked : 1 if the chain is valid but one of the certi= ficate is blocked + * @param [out] id : if is_known and !is_blocked then contains th= e device handle + * + * @return 0 on SUCCESS else -1 + */ +int usb_policy_engine_check_cert_chain(const uint32_t route, + const uint8_t *const digest, + const uint8_t *const chain, + const size_t chain_len, + uint8_t *is_valid, uint8_t *is_blocked, + uint32_t *id); + +/** + * @brief Remove a device from the policy engine + * + * Context: task context, might sleep. + * + * Possible errors: + * - EINVAL : if digest is NULL + * - ECOMM : if no userspace policy engine is available + * or already userspace is busy + * or message transmission failed + * - ENOMEM : if message creation failed + * - EMSGSIZE : if message creation failed + * + * @param [in] id : Device handle + * + * @return 0 on SUCCESS else -1 + */ +int usb_policy_engine_remove_dev(const uint32_t id); + +/** + * @brief Generate a nonce for the authentication challenge + * + * Context: task context, might sleep. + * + * Possible errors: + * - EINVAL : if digest is NULL + * - ECOMM : if no userspace policy engine is available + * or already userspace is busy + * or message transmission failed + * - ENOMEM : if message creation failed + * - EMSGSIZE : if message creation failed + * + * @param [in] id : device ID + * @param [out] nonce : 32 bytes nonce buffer, caller allocated + * + * @return 0 on SUCCESS else -1 + */ +int usb_policy_engine_generate_challenge(const uint32_t id, uint8_t *nonce= ); + +/** + * @brief Validate the authentication challenge + * + * Context: task context, might sleep. + * + * Possible errors: + * - EINVAL : if challenge, desc or bos is NULL or invalid parameter si= ze + * - ECOMM : if no userspace policy engine is available + * or already userspace is busy + * or message transmission failed + * - ENOMEM : if message creation failed + * - EMSGSIZE : if message creation failed + * + * Challenge is the concatenation of : message (140B) | signature (64B) + * + * Check that the response challenge contains the right nonce + * Check that the device signature is valid + * + * @param [in] id : device handle + * @param [in] challenge : challenge response, must be 204 bytes + * @param [in] desc : device descriptor + * @param [in] desc_size : descriptor size in bytes + * @param [in] bos : device BOS + * @param [in] bos_size : BOS size in bytes + * @param [out] is_valid : 1 if the signature is valid, 0 otherwise + * + * @return 0 on SUCCESS else -1 + */ +int usb_policy_engine_check_challenge(const uint32_t id, + const uint8_t *const challenge, + const uint8_t *const context, + const size_t context_size, + uint8_t *is_valid); + +#endif /* __USB_CORE_AUTHENT_NETLINK_H_ */ diff --git a/include/uapi/linux/usb/usb_auth_netlink.h b/include/uapi/linux= /usb/usb_auth_netlink.h new file mode 100644 index 0000000000000000000000000000000000000000..e5b1e0e130a1ffb320aac480516= 1d579923a5b29 --- /dev/null +++ b/include/uapi/linux/usb/usb_auth_netlink.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SPDX-FileCopyrightText: (C) 2025 ANSSI + * + * USB Authentication netlink interface definitions shared with userspace + * + * Author: Luc Bonnafoux + * Author: Nicolas Bouchinet + * + */ + +#ifndef __USB_AUTHENT_NETLINK_H_ +#define __USB_AUTHENT_NETLINK_H_ + +#define USBAUTH_GENL_NAME "usb_auth_genl" +#define USBAUTH_GENL_VERSION 1 + +/* Attributes */ +enum usbauth_genl_attrs { + USBAUTH_A_REQ_ID =3D 1, + USBAUTH_A_DEV_ID, + USBAUTH_A_DIGEST, + USBAUTH_A_DIGESTS, + USBAUTH_A_SLOT_MASK, + USBAUTH_A_KNOWN, + USBAUTH_A_BLOCKED, + USBAUTH_A_VALID, + USBAUTH_A_CERTIFICATE, + USBAUTH_A_CERT_LEN, + USBAUTH_A_ROUTE, + USBAUTH_A_NONCE, + USBAUTH_A_CHALL, + USBAUTH_A_DESCRIPTOR, + USBAUTH_A_BOS, + USBAUTH_A_ERROR_CODE, + __USBAUTH_A_MAX, +}; + +#define USBAUTH_MAX_DESC_SIZE 1024 +#define USBAUTH_MAX_BOS_SIZE 1024 + +#define USBAUTH_A_MAX (__USBAUTH_A_MAX - 1) + +/* Commands */ +enum usbauth_genl_cmds { + USBAUTH_CMD_REGISTER, + USBAUTH_CMD_CHECK_DIGEST, + USBAUTH_CMD_CHECK_CERTIFICATE, + USBAUTH_CMD_REMOVE_DEV, + USBAUTH_CMD_GEN_NONCE, + USBAUTH_CMD_CHECK_CHALL, + USBAUTH_CMD_RESP_DIGEST, + USBAUTH_CMD_RESP_CERTIFICATE, + USBAUTH_CMD_RESP_CREATE_DEV, + USBAUTH_CMD_RESP_REMOVE_DEV, + USBAUTH_CMD_RESP_GEN_NONCE, + USBAUTH_CMD_RESP_CHECK_CHALL, + __USBAUTH_CMD_MAX, +}; + +#define USBAUTH_CMD_MAX (__USBAUTH_CMD_MAX - 1) + +/* Error codes */ +#define USBAUTH_OK 0 +#define USBAUTH_INVRESP 1 + +#endif /* __USB_AUTHENT_NETLINK_H_ */ --=20 2.50.0 From nobody Thu Oct 9 02:57:41 2025 Received: from pf-012.whm.fr-par.scw.cloud (pf-012.whm.fr-par.scw.cloud [51.159.173.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9B3805BAF0; Fri, 20 Jun 2025 14:46:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=51.159.173.17 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750430787; cv=none; b=sjCOa5f3llNghv2oQHeD3fhrxPV6y8MMOwUOxoajwRaqhB6+KDAG00cPzjNfVxKFuwhu07fDDpqWqZ6QVjC5yDPYbcekwkrYn7D+ljsJ3WCfjPzjXzFHtR9t0gW/Lyvg07c882RPtM5cWon53fzgi/O8BHKB+pYEUAecV/GyWsY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750430787; c=relaxed/simple; bh=yzeVQTdQwADV4Gx0Z05e+eiEWWbtUZVwyeZPebXd+6Q=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=mr+LDgp0vieIkJByfvl1+KL5Aaje8MyE6/pl6q9bcrR0LMcnMLlTas6TJEwDPejbf/uVulwr0CslbdHOCRbGRyFlIadeiVy4UihAt0VocNAT39/S5ORXbU2sAg5eCVZOwddipM65pR85qLIYNF46CC2JQvs79daVZ/26lztSupQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr; spf=pass smtp.mailfrom=oss.cyber.gouv.fr; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b=ZagTZ+bU; arc=none smtp.client-ip=51.159.173.17 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b="ZagTZ+bU" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=oss.cyber.gouv.fr; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=Pp29a4g8JficuKSL2u5VIwEE+Icw/oQ7+A+IOyYra9E=; b=ZagTZ+bUT8VPEQspzsQDyEq3tU 6zd5/QCGzLvlkbjmRXqTHycDmC0RT63C3SsVUY3487HX3M+w1gGlfZs50fkACZiUsSuJ0wZAgGrxh ZIMFFMr5eqkmALhHgsecdD3G9k9DUZxibIZTZ5nK/tPd5lO20QhJfO1feOa+2AKau91WAUTCojGZR cg7Nrx7xbBVWdZtDKxJ/rH2AUySFdYoc5Jx84aW8yBu3JHmBmTj+Lajd6q9CXxxmLca17+WaMLbKc zIDsnFt3rZmzaoapnnTDh0YP7EJYW8lBlFzALyaaVQ5ZS2jxjZrCE08AudOyxpi3lr47WIuEULUyF +h5Dht6A==; Received: from laubervilliers-658-1-215-187.w90-63.abo.wanadoo.fr ([90.63.246.187]:43571 helo=[10.224.8.110]) by pf-012.whm.fr-par.scw.cloud with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1uScib-00000007rNQ-09BD; Fri, 20 Jun 2025 16:27:46 +0200 From: nicolas.bouchinet@oss.cyber.gouv.fr Date: Fri, 20 Jun 2025 16:27:17 +0200 Subject: [RFC PATCH 2/4] usb: core: Introduce usb authentication feature Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250620-usb_authentication-v1-2-0d92261a5779@ssi.gouv.fr> References: <20250620-usb_authentication-v1-0-0d92261a5779@ssi.gouv.fr> In-Reply-To: <20250620-usb_authentication-v1-0-0d92261a5779@ssi.gouv.fr> To: Greg Kroah-Hartman Cc: Alan Stern , Kannappan R , Sabyrzhan Tasbolatov , Krzysztof Kozlowski , Stefan Eichenberger , Thomas Gleixner , Pawel Laszczak , Ma Ke , Jeff Johnson , Luc Bonnafoux , Luc Bonnafoux , Nicolas Bouchinet , linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org X-Mailer: b4 0.14.2 X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - pf-012.whm.fr-par.scw.cloud X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - oss.cyber.gouv.fr X-Get-Message-Sender-Via: pf-012.whm.fr-par.scw.cloud: authenticated_id: nicolas.bouchinet@oss.cyber.gouv.fr X-Authenticated-Sender: pf-012.whm.fr-par.scw.cloud: nicolas.bouchinet@oss.cyber.gouv.fr X-Source: X-Source-Args: X-Source-Dir: From: Nicolas Bouchinet This includes the usb authentication protocol implementation bulk exposed by the public usb_authenticate_device function. The protocol exchange is driven by the host and can be decomposed into three, mostly independent, phases: - The Host can request a digest of each certificate own by the peripheral. - If the Host does not recognize the peripheral from one of its digests, it can read one or more certificates from the device until a valid one is found. - The Host can issue an authentication challenge to the peripheral. The usb_authenticate_device function implements the usb authentication protocol. It implements the three phases of the protocol : First, it needs to communicate with the usb device in order to fetch its certificate digests (usb_authent_req_digest). Then if the device is unknown, the host fetches the device certificate chains (usb_authent_read_cert_part, usb_authent_read_certificate). Once at least a digest has been recognized or a certificate chain has been validated the host challenges the device in order to authenticate it (usb_authent_challenge_dev). It also needs to communicate with a policy engine using the following functions : usb_policy_engine_check_digest usb_policy_engine_check_cert_chain usb_policy_engine_generate_challenge usb_policy_engine_check_challenge Co-developed-by: Luc Bonnafoux Signed-off-by: Luc Bonnafoux Signed-off-by: Nicolas Bouchinet --- drivers/usb/core/authent.c | 631 +++++++++++++++++++++++++++++++++++++++++= ++++ drivers/usb/core/authent.h | 166 ++++++++++++ 2 files changed, 797 insertions(+) diff --git a/drivers/usb/core/authent.c b/drivers/usb/core/authent.c new file mode 100644 index 0000000000000000000000000000000000000000..46f048d45a909e0fef504d71072= eb7854320d271 --- /dev/null +++ b/drivers/usb/core/authent.c @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SPDX-FileCopyrightText: (C) 2025 ANSSI + * + * USB Authentication protocol implementation + * + * Author: Luc Bonnafoux + * Author: Nicolas Bouchinet + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "authent_netlink.h" + +#include "authent.h" + +/** + * usb_authent_req_digest - Check if device is known via its digest + * @dev: [in] pointer to the usb device to query + * @buffer: [inout] buffer to hold request data + * @digest: [out] device digest + * + * Context: task context, might sleep. + * + * This function sends a digest request to the usb device. + * + * Possible errors: + * - ECOMM : failed to send or received a message to the device + * - EINVAL : if buffer or mask is NULL + * + * Return: If successful, zero. Otherwise, a negative error number. + */ +static int usb_authent_req_digest(struct usb_device *dev, uint8_t *const b= uffer, + uint8_t digest[256], uint8_t *mask) +{ + int ret =3D 0; + struct usb_authent_digest_resp *digest_resp =3D NULL; + + if (unlikely((buffer =3D=3D NULL || mask =3D=3D NULL))) { + pr_err("invalid arguments\n"); + return -EINVAL; + } + ret =3D usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), AUTH_IN, USB_DIR_IN, + (USB_SECURITY_PROTOCOL_VERSION << 8) + + USB_AUTHENT_DIGEST_REQ_TYPE, + 0, buffer, 260, USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + pr_err("Failed to get digest: %d\n", ret); + ret =3D -ECOMM; + goto exit; + } + + // Parse received digest response + digest_resp =3D (struct usb_authent_digest_resp *)buffer; + pr_notice("received digest response:\n"); + pr_notice(" protocolVersion: %x\n", digest_resp->protocolVersion); + pr_notice(" messageType: %x\n", digest_resp->messageType); + pr_notice(" capability: %x\n", digest_resp->capability); + pr_notice(" slotMask: %x\n", digest_resp->slotMask); + + *mask =3D digest_resp->slotMask; + memcpy(digest, digest_resp->digests, 256); + + ret =3D 0; + +exit: + + return ret; +} + +struct usb_auth_cert_req { + uint16_t offset; + uint16_t length; +} __packed; + +/** + * @brief Request a specific part of a certificate chain from the device + * + * Context: task context, might sleep + * + * Possible errors: + * - ECOMM : failed to send or receive a message to the device + * - EINVAL : if buffer or cert_part is NULL + * + * @param [in] dev : handle to the USB device + * @param [in,out] buffer : buffer used for communication, caller alloc= ated + * @param [in] slot : slot in which to read the certificate + * @param [in] offset : at which the certificate fragment must be r= ead + * @param [in] length : of the certificate fragment to read + * @param [out] cert_part : buffer to hold the fragment, caller allocat= ed + * + * @return 0 on SUCCESS else an error code + */ +static int usb_auth_read_cert_part(struct usb_device *dev, uint8_t *const = buffer, + const uint8_t slot, const uint16_t offset, + const uint16_t length, uint8_t *cert_part) +{ + struct usb_auth_cert_req cert_req =3D { 0 }; + int ret =3D -1; + + if (unlikely(buffer =3D=3D NULL || cert_part =3D=3D NULL)) { + pr_err("invalid argument\n"); + return -EINVAL; + } + + cert_req.offset =3D offset; + cert_req.length =3D length; + + // AUTH OUT request transfer + memcpy(buffer, &cert_req, sizeof(struct usb_auth_cert_req)); + ret =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), AUTH_OUT, + USB_DIR_OUT, + (USB_SECURITY_PROTOCOL_VERSION << 8) + + USB_AUTHENT_CERTIFICATE_REQ_TYPE, + (slot << 8), buffer, + sizeof(struct usb_auth_cert_req), + USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + pr_err("Failed to send certificate request: %d\n", ret); + ret =3D -ECOMM; + goto cleanup; + } + + // AUTH IN certificate read + ret =3D usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), AUTH_IN, USB_DIR_IN, + (USB_SECURITY_PROTOCOL_VERSION << 8) + + USB_AUTHENT_CERTIFICATE_RESP_TYPE, + (slot << 8), buffer, length + 4, + USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + pr_notice("Failed to get certificate from peripheral: %d\n", ret); + ret =3D -ECOMM; + goto cleanup; + } + + // TODO: parse received header + memcpy(cert_part, buffer + 4, length); + + ret =3D 0; + +cleanup: + + return ret; +} + +/** + * usb_authent_read_certificate - Read a device certificate + * @dev: [in] pointer to the usb device to query + * @buffer: [inout] buffer to hold request data, caller allocated + * @slot: [in] certificate chain to be read + * @cert_der: [out] buffer to hold received certificate chain + * @cert_len: [out] length of received certificate + * + * Context: task context, might sleep. + * + * Possible errors: + * - EINVAL : NULL pointer or invalid slot value + * - ECOMM : failed to send request to device + * - ENOMEM : failed to allocate memory for certificate + * + * Return: If successful, zero. Otherwise, a negative error number. + */ +static int usb_authent_read_certificate(struct usb_device *dev, uint8_t *c= onst buffer, + uint8_t slot, uint8_t **cert_der, size_t *cert_len) +{ + uint16_t read_offset =3D 0; + uint16_t read_length =3D 0; + uint8_t chain_part[64] =3D { 0 }; + + if (unlikely(slot >=3D 8 || buffer =3D=3D NULL || cert_der =3D=3D NULL ||= cert_len =3D=3D NULL)) { + pr_err("invalid arguments\n"); + return -EINVAL; + } + + // First request to get certificate chain length + if (usb_auth_read_cert_part(dev, buffer, slot, 0, + USB_AUTH_CHAIN_HEADER_SIZE, + chain_part) !=3D 0) { + pr_err("Failed to get first certificate part\n"); + return -ECOMM; + } + + // Extract total length + *cert_len =3D ((uint16_t *)chain_part)[0]; + pr_notice("Received header of chain with length: %ld\n", + (*cert_len) + USB_AUTH_CHAIN_HEADER_SIZE); + + // Allocate certificate DER buffer + *cert_der =3D kzalloc(*cert_len, GFP_KERNEL); + if (!(*cert_der)) + return -ENOMEM; + + // Write the chain header at the beginning of the chain. + memcpy(*cert_der, chain_part, USB_AUTH_CHAIN_HEADER_SIZE); + // Read the certificate chain starting after the header. + read_offset =3D USB_AUTH_CHAIN_HEADER_SIZE; + + while (read_offset < *cert_len) { + read_length =3D (*cert_len - read_offset) >=3D 64 ? 64 : (*cert_len - re= ad_offset); + + if (usb_auth_read_cert_part(dev, buffer, slot, read_offset, + read_length, chain_part) !=3D 0) { + pr_err("USB AUTH: Failed to get certificate part\n"); + return -ECOMM; + } + + memcpy(*cert_der + read_offset, chain_part, read_length); + + read_offset +=3D read_length; + } + + return 0; +} + +/** + * usb_authent_challenge_dev - Challenge a device + * @dev: [in] pointer to the usb device to query + * @buffer: [in] pointer to the buffer allocated for USB query + * @slot: [in] certificate chain to be used + * @slot_mask: [in] slot mask of the device + * @nonce: [in] nonce to use for the challenge, 32 bytes long + * @chall: [out] buffer for chall response, 204 bytes long, caller alloc= ated + * + * Context: task context, might sleep. + * + * Possible errors: + * - EINVAL : NULL input pointer or invalid slot value + * - ECOMM : failed to send or receive message from the device + * + * Return: If successful, zero. Otherwise, a negative error number. + */ +static int usb_authent_challenge_dev(struct usb_device *dev, uint8_t *buff= er, + const uint8_t slot, const uint8_t slot_mask, const uint8_t *const nonce, + uint8_t *const chall) +{ + int ret =3D -1; + + if (unlikely(buffer =3D=3D NULL || slot >=3D 8 || nonce =3D=3D NULL)) { + pr_err("invalid arguments\n"); + return -EINVAL; + } + + // AUTH OUT challenge request transfer + memcpy(buffer, nonce, 32); + ret =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), AUTH_OUT, + USB_DIR_OUT, + (USB_SECURITY_PROTOCOL_VERSION << 8) + + USB_AUTHENT_CHALLENGE_REQ_TYPE, + (slot << 8), buffer, 32, USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + pr_err("Failed to send challenge request: %d\n", ret); + ret =3D -ECOMM; + goto cleanup; + } + + // Complete the challenge with the request + chall[1] =3D USB_SECURITY_PROTOCOL_VERSION; + chall[0] =3D USB_AUTHENT_CHALLENGE_REQ_TYPE; + chall[2] =3D slot; + chall[3] =3D 0x00; + memcpy(chall+4, nonce, 32); + + // AUTH IN challenge response transfer + ret =3D usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), AUTH_IN, USB_DIR_IN, + (USB_SECURITY_PROTOCOL_VERSION << 8) + + USB_AUTHENT_CHALLENGE_RESP_TYPE, + (slot << 8) + slot_mask, buffer, 168, + USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + pr_err("Failed to get challenge response: %d\n", ret); + ret =3D -ECOMM; + goto cleanup; + } + + pr_notice("received challenge response\n"); + + // Complete last part of the challenge with what is returned by the device + memcpy(chall+USB_AUTH_CHAIN_HEADER_SIZE, buffer, 168); + + ret =3D 0; + +cleanup: + + return ret; +} + +/** + * @brief Create a device context according to USB Type-C Authentication S= pecification, chapter 5.5 + * 1. Device Descriptor + * 2. Complete BOS Descriptor (if present) + * 3. Complete Configuration 1 Descriptor + * 4. Complete Configuration 2 Descriptor (if present) + * 5. ... + * 6. Complete Configuration n Descriptor (if present) + * + * Possible error codes: + * - EINVAL : invalid dev, ctx or size + * + * @param [in] dev : handle to the USB device + * @param [in, out] ctx : buffer to hold the device context, caller alloc= ated + * @param [in] buf_size : available size in the context buffer + * @param [out] ctx_size : total size of the context if return equals 0 + * + * @return 0 or error code + */ +static int usb_auth_create_dev_ctx(struct usb_device *dev, uint8_t *ctx, + const size_t buf_size, size_t *ctx_size) +{ + int desc_size =3D 0; + + if (unlikely(dev =3D=3D NULL || ctx =3D=3D NULL)) { + pr_err("invalid inputs\n"); + return -EINVAL; + } + + *ctx_size =3D 0; + + // Device descriptor + if (buf_size < (size_t)dev->descriptor.bLength) { + pr_err("buffer too small\n"); + return -EINVAL; + } + + memcpy(ctx, (void *) &dev->descriptor, (size_t) dev->descriptor.bLength); + + *ctx_size +=3D (size_t) dev->descriptor.bLength; + + // Device BOS and capabilities + if (unlikely(dev->bos =3D=3D NULL || dev->bos->desc =3D=3D NULL)) { + pr_err("invalid BOS\n"); + return -EINVAL; + } + + desc_size =3D le16_to_cpu(dev->bos->desc->wTotalLength); + + if (buf_size < (*ctx_size + desc_size)) { + pr_err("buffer too small\n"); + return -EINVAL; + } + + memcpy(ctx + (*ctx_size), (void *) dev->bos->desc, desc_size); + + *ctx_size +=3D desc_size; + + // Device configuration descriptor + if (unlikely(dev->config =3D=3D NULL)) { + pr_err("invalid configuration\n"); + return -EINVAL; + } + + desc_size =3D le16_to_cpu(dev->config->desc.wTotalLength); + + if (buf_size < (*ctx_size + desc_size)) { + pr_err("buffer too small\n"); + return -EINVAL; + } + + memcpy(ctx + (*ctx_size), (void *) &dev->config->desc, 9); + + *ctx_size +=3D 9; + + return 0; +} + +/** + * @brief Check that the authentication can resume after a sleep + * + * @param [in] dev : the usb device + * @param [in] hub : the parent hub + * + * Possible error codes: + * - ENODEV : hub has been disconnected + * + * @return 0 if possible to resume, else an error code + */ +static int usb_auth_try_resume(struct usb_device *dev, struct usb_device *= hub) +{ + // Test if the hub or the device has been disconnected + if (unlikely(hub =3D=3D NULL || dev =3D=3D NULL || + dev->port_is_suspended =3D=3D 1 || + dev->reset_in_progress =3D=3D 1)) { + return -ENODEV; + } + + // TODO: test if the device has not been disconnected + // TODO: test if the device has not been disconnected then replaced with = another one + + return 0; +} + +/** + * usb_authenticate_device - Challenge a device + * @dev: [inout] pointer to device + * + * Context: task context, might sleep. + * + * Authentication is done in the following steps: + * 1. Get device certificates digest to determine if it is already known + * if yes, go to 3. + * 2. Get device certificates + * 3. Challenge device + * 4. Based on previous result, determine if device is allowed under local + * security policy. + * + * Possible error code: + * - ENOMEM : failed to allocate memory for exchange + * - TODO: complete all possible error case + * + * Return: If successful, zero. Otherwise, a negative error number. + */ +int usb_authenticate_device(struct usb_device *dev) +{ + int ret =3D 0; + + uint8_t is_valid =3D 0; + uint8_t is_known =3D 0; + uint8_t is_blocked =3D 0; + uint8_t chain_nb =3D 0; + uint8_t slot_mask =3D 0; + uint8_t slot =3D 0; + uint8_t digests[256] =3D { 0 }; + uint8_t nonce[32] =3D {0}; + uint8_t chall[204] =3D {0}; + uint32_t dev_id =3D 0; + size_t ctx_size =3D 0; + int i =3D 0; + + uint8_t *cert_der =3D NULL; + size_t cert_len =3D 0; + + if (unlikely(dev =3D=3D NULL || dev->parent =3D=3D NULL)) + return -ENODEV; + + struct usb_device *hub =3D dev->parent; + + // By default set authorization status at false + dev->authorized =3D 0; + dev->authenticated =3D 0; + + uint8_t *buffer =3D NULL; + // Buffer to hold responses + buffer =3D kzalloc(512, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + pr_notice("start of device authentication\n"); + + /* + * Send DIGEST request to determine if it is a known device + */ + ret =3D usb_authent_req_digest(dev, buffer, digests, &slot_mask); + if (ret !=3D 0) { + pr_err("failed to get digest: %d\n", ret); + goto cleanup; + } + pr_notice("received digest\n"); + + usb_unlock_device(hub); + ret =3D usb_policy_engine_check_digest(dev->route, digests, slot_mask, + &is_known, &is_blocked, &dev_id); + if (ret !=3D 0) { + pr_err("failed to check digest: %d\n", ret); + usb_lock_device(hub); + goto cleanup; + } + pr_info("waking up\n"); + usb_lock_device(hub); + ret =3D usb_auth_try_resume(dev, hub); + if (unlikely(ret !=3D 0)) { + pr_err("failed to resume: %d\n", ret); + goto cleanup; + } + + pr_info("resuming\n"); + + /* + * If the device is already known and blocked, reject it + */ + if (is_known && is_blocked) { + ret =3D 0; + goto cleanup; + } + + /* + * If device is not already known try to obtain a valid certificate + * Iterate over every device certificate slots, it gets them one by one + * in order to avoid spamming the device. + */ + if (!is_known) { + // Iterate over slot containing a certificate until a valid one is found + for (i =3D 0; i < 8; i++) { + // Test if slot contains a certificate chain + if (1 =3D=3D ((slot_mask >> i) & 1)) { + ret =3D usb_authent_read_certificate(dev, buffer, + chain_nb, + &cert_der, + &cert_len); + if (ret !=3D 0) { + // Failed to read device certificate, abort authentication + // Apply security policy on failed device + goto cleanup; + } + pr_notice("received certificate\n"); + + // validate the certificate + usb_unlock_device(hub); + ret =3D usb_policy_engine_check_cert_chain( + dev->route, digests + i * 32, cert_der, + cert_len, &is_valid, &is_blocked, + &dev_id); + if (ret !=3D 0) { + pr_err("failed to validate certificate: %d\n", ret); + usb_lock_device(hub); + goto cleanup; + } + pr_notice("validated certificate\n"); + usb_lock_device(hub); + + ret =3D usb_auth_try_resume(dev, hub); + if (unlikely(ret !=3D 0)) { + pr_err("failed to resume: %d\n", ret); + goto cleanup; + } + + pr_info("resuming\n"); + + if (is_valid && !is_blocked) { + // Found a valid and authorized certificate, + // continue with challenge + slot =3D i; + break; + } else if (is_valid && is_blocked) { + // Found a valid and unauthorized certificate, + // reject device + ret =3D 0; + goto cleanup; + } + } + } + } else { + // Pick a slot among the valid ones, take first one + for (i =3D 0; i < 8; i++) { + if (1 =3D=3D ((is_known >> i) & 1)) { + slot =3D i; + break; + } + } + } + + /* + * Authenticate the device with a challenge request + */ + // Obtain a nonce for the challenge + usb_unlock_device(hub); + ret =3D usb_policy_engine_generate_challenge(dev_id, nonce); + if (ret !=3D 0) { + pr_err("failed to generate challenge: %d\n", ret); + usb_lock_device(hub); + goto cleanup; + } + pr_notice("generated challenge\n"); + usb_lock_device(hub); + + ret =3D usb_auth_try_resume(dev, hub); + if (unlikely(ret !=3D 0)) { + pr_err("failed to resume: %d\n", ret); + goto cleanup; + } + + pr_info("resuming\n"); + + // Send a challenge request + ret =3D usb_authent_challenge_dev(dev, buffer, slot, slot_mask, nonce, + chall); + if (ret !=3D 0) { + pr_err("failed to challenge device: %d\n", ret); + goto cleanup; + } + pr_notice("validated challenge\n"); + + // Create device context + ret =3D usb_auth_create_dev_ctx(dev, buffer, 512, &ctx_size); + if (ret !=3D 0) { + pr_err("failed to create context: %d\n", ret); + goto cleanup; + } + + // Validate the challenge + usb_unlock_device(hub); + ret =3D usb_policy_engine_check_challenge(dev_id, chall, buffer, ctx_size, + &is_valid); + if (ret !=3D 0) { + pr_err("failed to check challenge: %d\n", ret); + usb_lock_device(hub); + goto cleanup; + } + pr_notice("checked challenge\n"); + usb_lock_device(hub); + + ret =3D usb_auth_try_resume(dev, hub); + if (unlikely(ret !=3D 0)) { + pr_err("failed to resume: %d\n", ret); + goto cleanup; + } + + pr_info("resuming\n"); + + // Apply authorization decision + if (is_valid) { + dev->authorized =3D 1; + dev->authenticated =3D 1; + } + + ret =3D 0; + +cleanup: + kfree(buffer); + kfree(cert_der); + + return 0; +} diff --git a/drivers/usb/core/authent.h b/drivers/usb/core/authent.h new file mode 100644 index 0000000000000000000000000000000000000000..c3852636dbcea9150ed1663769e= 2a7b6348f528c --- /dev/null +++ b/drivers/usb/core/authent.h @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SPDX-FileCopyrightText: (C) 2025 ANSSI + * + * USB Authentication protocol definition + * + * Author: Luc Bonnafoux + * Author: Nicolas Bouchinet + * + */ + +#ifndef __USB_CORE_AUTHENT_H_ +#define __USB_CORE_AUTHENT_H_ + +#include +#include +#include +#include + +/* From USB Type-C Authentication spec, Table 5-2 */ +#define USB_AUTHENT_CAP_TYPE 0x0e + +/* From USB Security Foundation spec, Table 5-2 */ +#define USB_SECURITY_PROTOCOL_VERSION 0x10 + +#define AUTH_IN 0x18 +#define AUTH_OUT 0x19 + +/* USB_DT_AUTHENTICATION_CAP */ +struct usb_authent_cap_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDevCapabilityType; /* Shall be set to USB_AUTHENT_CAP_TYPE */ + /* + * bit 0: set to 1 if firmware can be updated + * bit 1: set to 1 to indicate the Device changes interface when updated + * bits 2-7: reserved, set to 0 + */ + __u8 bmAttributes; + __u8 bcdProtocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSIO= N */ + __u8 bcdCapability; /* Set to 0x01 */ + +} __packed; + +/* Certificate chain header, Table 3-1 */ +struct usb_cert_chain_hd { + __u16 length; /* Chain total length including header, little endian */ + __u16 reserved; /* Shall be set to zero */ + __u8 rootHash[32]; /* Hash of root certificate, big endian */ +} __packed; + +/* From USB Security Foundation spec, Table 5-3 and Table 5-9 */ +#define USB_AUTHENT_DIGEST_RESP_TYPE 0x01 +#define USB_AUTHENT_CERTIFICATE_RESP_TYPE 0x02 +#define USB_AUTHENT_CHALLENGE_RESP_TYPE 0x03 +#define USB_AUTHENT_ERROR_TYPE 0x7f +#define USB_AUTHENT_DIGEST_REQ_TYPE 0x81 +#define USB_AUTHENT_CERTIFICATE_REQ_TYPE 0x82 +#define USB_AUTHENT_CHALLENGE_REQ_TYPE 0x83 + +#define USB_AUTH_DIGEST_SIZE 32 +#define USB_AUTH_CHALL_SIZE 32 + +#define USB_AUTH_CHAIN_HEADER_SIZE 36 + +/* USB Authentication GET_DIGEST Request Header */ +struct usb_authent_digest_req_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_DIGEST_REQ_TYPE */ + __u8 param1; /* Reserved */ + __u8 param2; /* Reserved */ +} __packed; + +/* USB Authentication GET_CERTIFICATE Request Header */ +struct usb_authent_certificate_req_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CERTIFICATE_REQ_TYPE */ + __u8 certChainSlotNumber; /* Must be between 0 and 7 inclusive */ + __u8 param2; /* Reserved */ +} __packed; + +/* USB Authentication GET_CERTIFICATE Request */ +struct usb_authent_certificate_req { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CERTIFICATE_REQ_TYPE */ + __u8 certChainSlotNumber; /* Must be between 0 and 7 inclusive */ + __u8 param2; /* Reserved */ + __u16 offset; /* Read index of Certificate Chain in bytes and little endi= an*/ + __u16 length; /* Length of read request, little endian */ +} __packed; + +/* USB Authentication CHALLENGE Request Header */ +struct usb_authent_challenge_req_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CHALLENGE_REQ_TYPE */ + __u8 certChainSlotNumber; /* Must be between 0 and 7 inclusive */ + __u8 param2; /* Reserved */ +} __packed; + +/* USB Authentication CHALLENGE Request Header */ +struct usb_authent_challenge_req { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CHALLENGE_REQ_TYPE */ + __u8 certChainSlotNumber; /* Must be between 0 and 7 inclusive */ + __u8 param2; /* Reserved */ + __u32 nonce; /* Random Nonce chosen for the challenge */ +} __packed; + +/* USB Authentication DIGEST response Header */ +struct usb_authent_digest_resp { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_DIGEST_RESP_TYPE */ + __u8 capability; /* Shall be set to 0x01 */ + __u8 slotMask; /* Bit set to 1 if slot is set, indicates number of digest= s */ + __u8 digests[8][32]; /* List of digests */ +} __packed; + +/* USB Authentication CERTIFICATE response Header */ +struct usb_authent_certificate_resp_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CERTIFICATE_RESP_TYPE */ + __u8 slotNumber; /* Slot number of certificate chain returned */ + __u8 param2; /* Reserved */ +} __packed; + +/* USB Authentication CHALLENGE response Header */ +struct usb_authent_challenge_resp_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CHALLENGE_RESP_TYPE */ + __u8 slotNumber; /* Slot number of certificate chain returned */ + __u8 slotMask; /* Bit set to 1 if slot is set */ +} __packed; + +/* USB Authentication CHALLENGE response */ +struct usb_authent_challenge_resp { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CHALLENGE_RESP_TYPE */ + __u8 slotNumber; /* Slot number of certificate chain returned */ + __u8 slotMask; /* Bit set to 1 if slot is set */ + __u8 minProtocolVersion; + __u8 maxProtocolVersion; + __u8 capabilities; /* Shall be set to 0x01 */ + __u8 orgName; /* Organisation Name, USB-IF: 0 */ + __u32 certChainHash; /* SHA256 digest of certificate chain, big endian */ + __u32 salt; /* Chosen by responder */ + __u32 contextHash; /* SHA256 digest of product information, big endian */ + __u64 signature; /* ECDSA signature of request and response */ +} __packed; + +/* USB Authentication error codes, Foundation Table 5-18 */ +#define USB_AUTHENT_INVALID_REQUEST_ERROR 0x01 +#define USB_AUTHENT_UNSUPPORTED_PROTOCOL_ERROR 0x02 +#define USB_AUTHENT_BUSY_ERROR 0x03 +#define USB_AUTHENT_UNSPECIFIED_ERROR 0x04 + +/* USB Authentication response header */ +struct usb_authent_error_resp_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_ERROR_TYPE */ + __u8 errorCode; + __u8 errorData; +} __packed; + +int usb_authenticate_device(struct usb_device *dev); + +#endif /* __USB_CORE_AUTHENT_H_ */ --=20 2.50.0 From nobody Thu Oct 9 02:57:41 2025 Received: from pf-012.whm.fr-par.scw.cloud (pf-012.whm.fr-par.scw.cloud [51.159.173.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CA88A295DB8; Fri, 20 Jun 2025 14:46:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=51.159.173.17 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750430792; cv=none; b=ciHPFV8fONuB0Yx6lbHRcp+vJL3UAHFf6Oh9sZVgicAMYx6V646NFKCJjYUd0wuyL7kZGDRy/xYAGYiJjPtPm72FpxVC7Q+jc0Y+gEbLMrF3xvg5aFVzjuEK4P0Q2X50DH2Vd1qFBpOVMHw9FMgP6RRH38c1/0jWavM0GwEbH+E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750430792; c=relaxed/simple; bh=nKJpLyVb3Mf+ZB2BJ5zQqKhJeaFKSpYxxkFva1A7Y0U=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=EslalPNLirgvYc3h0B9XyTu5J/9Md9gpdOKLbh0qwnnXQxBurd4MV+CDYzMqSEhEgueyCqNY0/gxOmeHGIXZncJe77M8u1tyN327aY2wQKJP771x7uh2vF7gUsSOAFCMF2OaGF4SP60SQquTGHa+OjlpSbTih67h6Y/aQNtfcnw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr; spf=pass smtp.mailfrom=oss.cyber.gouv.fr; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b=q7QM1RgV; arc=none smtp.client-ip=51.159.173.17 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b="q7QM1RgV" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=oss.cyber.gouv.fr; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=y4GtbjwsgXd9i+tuWT0wCJrZu63vjEneWpfP0tEUUag=; b=q7QM1RgVzshpD+9pwDkduM7xk0 JQ63YK8yctjaazdpQsnz2BoHCC+sKC6m65lXI7hha3ab3YsncUFMfwdVV/uZcleBfcBaVgeaeVOWZ I9VAmGZq7thgN6PjqwWTh2S405ZASD9S3BGhb9gqdPH91b9ldj/w2wjuZVsKB95gafeh5f0kNKNwQ 0KaHm+puCji1UWUgLjJIEek/qhKmTbhlgJgnzIeJ931rcD9eaRaXAmmY6bC9ri0/MG+krzbPFyQor dmBSyLtWfuh1DQkyS52bCLyhO4Qs0olw+ATjv713SaaRAD/ALLxjEUdiQ0nEIILU9pdHGjnmOcyMp B+gIsCbA==; Received: from laubervilliers-658-1-215-187.w90-63.abo.wanadoo.fr ([90.63.246.187]:43571 helo=[10.224.8.110]) by pf-012.whm.fr-par.scw.cloud with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1uScib-00000007rNQ-3xdT; Fri, 20 Jun 2025 16:27:46 +0200 From: nicolas.bouchinet@oss.cyber.gouv.fr Date: Fri, 20 Jun 2025 16:27:18 +0200 Subject: [RFC PATCH 3/4] usb: core: Plug the usb authentication capability Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250620-usb_authentication-v1-3-0d92261a5779@ssi.gouv.fr> References: <20250620-usb_authentication-v1-0-0d92261a5779@ssi.gouv.fr> In-Reply-To: <20250620-usb_authentication-v1-0-0d92261a5779@ssi.gouv.fr> To: Greg Kroah-Hartman Cc: Alan Stern , Kannappan R , Sabyrzhan Tasbolatov , Krzysztof Kozlowski , Stefan Eichenberger , Thomas Gleixner , Pawel Laszczak , Ma Ke , Jeff Johnson , Luc Bonnafoux , Luc Bonnafoux , Nicolas Bouchinet , linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org X-Mailer: b4 0.14.2 X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - pf-012.whm.fr-par.scw.cloud X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - oss.cyber.gouv.fr X-Get-Message-Sender-Via: pf-012.whm.fr-par.scw.cloud: authenticated_id: nicolas.bouchinet@oss.cyber.gouv.fr X-Authenticated-Sender: pf-012.whm.fr-par.scw.cloud: nicolas.bouchinet@oss.cyber.gouv.fr X-Source: X-Source-Args: X-Source-Dir: From: Nicolas Bouchinet Plugs the usb authentication implementation in the usb stack and more particularly in the usb_parse_configuration function after the BOS has been parsed and the usb authentication capacity has been controlled. The authentication bulk is implemented by the usb_authenticate_device function. The authorization decision enforcement is done via the authorized field of the usb_device and the associated authorization and deauthorization functio= ns. The usb_device also contains an authenticated field that could be used to t= rack the result of the authentication process and allow for more complex security policy: the user could manually authorize a device that failed the authentication or manually deauthorize a device that was previously authenticated. The usb_authenticate_device returns 0 or an error code. If 0 is returned, the authorized and authenticated fields of the usb_device are updated with the result of the authentication. Co-developed-by: Luc Bonnafoux Signed-off-by: Luc Bonnafoux Signed-off-by: Nicolas Bouchinet --- drivers/usb/core/config.c | 51 +++++++++++++++++++++++++++++++++++++++++++= +++- drivers/usb/core/hub.c | 6 ++++++ drivers/usb/core/usb.c | 5 +++++ include/linux/usb.h | 2 ++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 13bd4ec4ea5f7a6fef615b6f50b1acb3bbe44ba4..45ee8e93e263c41f1bf4271be4e= 69ccfcac3f650 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -14,6 +14,7 @@ #include #include "usb.h" =20 +#include "authent.h" =20 #define USB_MAXALTSETTING 128 /* Hard limit */ =20 @@ -824,7 +825,50 @@ static int usb_parse_configuration(struct usb_device *= dev, int cfgidx, kref_init(&intfc->ref); } =20 - /* FIXME: parse the BOS descriptor */ + /* If device USB version is above 2.0, get BOS descriptor */ + /* + * Requirement for bcdUSB >=3D 2.10 is defined in USB 3.2 =C2=A79.2.6.6 + * "Devices with a value of at least 0210H in the bcdUSB field of their + * device descriptor shall support GetDescriptor (BOS Descriptor) request= s." + * + * To discuss, BOS request could be also sent for bcdUSB >=3D 0x0201 + */ + // Set a default value for authenticated at true in order not to block de= vices + // that do not support the authentication + dev->authenticated =3D 1; + + if (le16_to_cpu(dev->descriptor.bcdUSB) >=3D 0x0201) { + pr_notice("bcdUSB >=3D 0x0201\n"); + retval =3D usb_get_bos_descriptor(dev); + if (!retval) { + pr_notice("found BOS\n"); +#ifdef CONFIG_USB_AUTHENTICATION + if (dev->bos->authent_cap) { + /* If authentication cap is present, start device authent */ + pr_notice("found Authent BOS\n"); + retval =3D usb_authenticate_device(dev); + if (retval !=3D 0) { + pr_err("failed to authenticate the device: %d\n", + retval); + } else if (!dev->authenticated) { + pr_notice("device has been rejected\n"); + // return early from the configuration process + return 0; + } else { + pr_notice("device has been authorized\n"); + } + } else { + // USB authentication unsupported + // Apply security policy on failed devices + pr_notice("no authentication capability\n"); + } +#endif + } else { + // Older USB version, authentication not supported + // Apply security policy on failed devices + pr_notice("device does not have a BOS descriptor\n"); + } + } =20 /* Skip over any Class Specific or Vendor Specific descriptors; * find the first interface descriptor */ @@ -1051,6 +1095,7 @@ int usb_get_bos_descriptor(struct usb_device *dev) length =3D bos->bLength; total_len =3D le16_to_cpu(bos->wTotalLength); num =3D bos->bNumDeviceCaps; + kfree(bos); if (total_len < length) return -EINVAL; @@ -1122,6 +1167,10 @@ int usb_get_bos_descriptor(struct usb_device *dev) dev->bos->ptm_cap =3D (struct usb_ptm_cap_descriptor *)buffer; break; + case USB_AUTHENT_CAP_TYPE: + dev->bos->authent_cap =3D + (struct usb_authent_cap_descriptor *)buffer; + break; default: break; } diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 0e1dd6ef60a719f59a22d86caeb20f86991b5b29..753e55155ea34a7739b5f530dad= 429534e60842d 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2640,6 +2640,12 @@ int usb_new_device(struct usb_device *udev) udev->dev.devt =3D MKDEV(USB_DEVICE_MAJOR, (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); =20 + // TODO: Check the device state, we want to avoid semi-initialized device= to userspace. + if (!udev->authenticated) { + // If the device is not authenticated, abort the procedure + goto fail; + } + /* Tell the world! */ announce_device(udev); =20 diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 0b4685aad2d50337f3cacb2198c95a68ae8eff86..76847c01d3493e2527992a3bb92= 7422519d9a974 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -46,6 +46,7 @@ #include =20 #include "hub.h" +#include "authent_netlink.h" =20 const char *usbcore_name =3D "usbcore"; =20 @@ -1080,6 +1081,10 @@ static int __init usb_init(void) usb_debugfs_init(); =20 usb_acpi_register(); + + // TODO : check error case + usb_auth_init_netlink(); + retval =3D bus_register(&usb_bus_type); if (retval) goto bus_register_failed; diff --git a/include/linux/usb.h b/include/linux/usb.h index b46738701f8dc46085f251379873b6a8a008d99d..e9037c8120b43556f8610f9acb3= ad4129e847b98 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -431,6 +431,8 @@ struct usb_host_bos { struct usb_ssp_cap_descriptor *ssp_cap; struct usb_ss_container_id_descriptor *ss_id; struct usb_ptm_cap_descriptor *ptm_cap; + /* Authentication capability */ + struct usb_authent_cap_descriptor *authent_cap; }; =20 int __usb_get_extra_descriptor(char *buffer, unsigned size, --=20 2.50.0 From nobody Thu Oct 9 02:57:41 2025 Received: from pf-012.whm.fr-par.scw.cloud (pf-012.whm.fr-par.scw.cloud [51.159.173.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5F5BE1E47B7; Fri, 20 Jun 2025 14:27:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=51.159.173.17 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750429676; cv=none; b=HBbsoDLnuwhhDg9oiKcFLxCacT69oMEYIvBVZPlFxFxsEdwSdpc2Lbt/0zjzNNuh0YqKr1PDIJWee79JbbDxoVXyv0PlhTTg8AMpbhGayS6svGDnQ/zD3E2FtIQVPXhVdsdB8bTA3/W8LKxSyo08nbS179GPhU8jv7rl5jL+yiI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750429676; c=relaxed/simple; bh=E8Plfmdgq+6cfcuVqI3CT/7JUjKbKcYEKI5VFigoooQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=pKPlEUCM2xY10KjHOmmBjy4IAlWdaxeV5y9/0bWrUu495csf3kGMqfgV1U+u2VFGgSyvtEfI0Eau0Wu7v8aFbTG0vTHOTnXHPogDRyArfCrc3KqW3A/S8hmfOYSj+fUE7qWuMcXKyHJ0hbRY2zIYs7iqz3ePR8x+87cKunDrDsw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr; spf=pass smtp.mailfrom=oss.cyber.gouv.fr; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b=aWTkQErB; arc=none smtp.client-ip=51.159.173.17 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b="aWTkQErB" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=oss.cyber.gouv.fr; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=0rsZWx+4JUDSAaqG2b4ZgDsynw5G2Gsxxx00bsx043I=; b=aWTkQErBolZRqk+F0wSvZ4NH3q 38tFF81DsQDeWA4kuJabfhUG28xB17CVwQLjprdJ8BkwK4carGQIhla14DB2afqsWyAeZ2087VjIa mvj15H1nKIIic4iluUJMKb/gNa42vvPs/ZeWOoluTwpoXhw/Z2oZbbljK9zRw9MaaLz6tLpyIn1Uy U38AehgKnZpaSb6LgxNO7JhyZG6NfeB8YDTOoOI/o8wXVuoVfXVoPdgo/JyqFabJmP4w+Dsi9jLfM iP4CJO2hSUbffGU7ITrvyZ1WADgvW905Zyd8tUXmzILPUOyrTFSse1jHBSYiTvzxvUQlVliPaD3Nj CZ7UgA4w==; Received: from laubervilliers-658-1-215-187.w90-63.abo.wanadoo.fr ([90.63.246.187]:43571 helo=[10.224.8.110]) by pf-012.whm.fr-par.scw.cloud with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1uScih-00000007rNQ-1ges; Fri, 20 Jun 2025 16:27:52 +0200 From: nicolas.bouchinet@oss.cyber.gouv.fr Date: Fri, 20 Jun 2025 16:27:19 +0200 Subject: [RFC PATCH 4/4] usb: core: Add Kconfig option to compile usb authorization Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250620-usb_authentication-v1-4-0d92261a5779@ssi.gouv.fr> References: <20250620-usb_authentication-v1-0-0d92261a5779@ssi.gouv.fr> In-Reply-To: <20250620-usb_authentication-v1-0-0d92261a5779@ssi.gouv.fr> To: Greg Kroah-Hartman Cc: Alan Stern , Kannappan R , Sabyrzhan Tasbolatov , Krzysztof Kozlowski , Stefan Eichenberger , Thomas Gleixner , Pawel Laszczak , Ma Ke , Jeff Johnson , Luc Bonnafoux , Luc Bonnafoux , Nicolas Bouchinet , linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org X-Mailer: b4 0.14.2 X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - pf-012.whm.fr-par.scw.cloud X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - oss.cyber.gouv.fr X-Get-Message-Sender-Via: pf-012.whm.fr-par.scw.cloud: authenticated_id: nicolas.bouchinet@oss.cyber.gouv.fr X-Authenticated-Sender: pf-012.whm.fr-par.scw.cloud: nicolas.bouchinet@oss.cyber.gouv.fr X-Source: X-Source-Args: X-Source-Dir: From: Nicolas Bouchinet This enables the usb authentication protocol implementation. Co-developed-by: Luc Bonnafoux Signed-off-by: Luc Bonnafoux Signed-off-by: Nicolas Bouchinet --- drivers/usb/core/Kconfig | 8 ++++++++ drivers/usb/core/Makefile | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index 58e3ca7e479392112f656384c664efc36bb1151a..07ba67137b7fe16ecb1e993a51d= bbfd4dd3ada88 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -143,3 +143,11 @@ config USB_DEFAULT_AUTHORIZATION_MODE ACPI selecting value 2 is analogous to selecting value 0. =20 If unsure, keep the default value. + +config USB_AUTHENTICATION + bool "Enable USB authentication function" + default n + depends on USB + help + Enables the USB Authentication function. This activates the + hook points in the USB stack. diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index ac006abd13b3ad8362dc7baa115124c11eaafc84..7ba1a89cf3de7a398889eee1820= f2bfbbc4280f5 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -8,6 +8,10 @@ usbcore-y +=3D config.o file.o buffer.o sysfs.o endpoint.o usbcore-y +=3D devio.o notify.o generic.o quirks.o devices.o usbcore-y +=3D phy.o port.o =20 +ifdef CONFIG_USB_AUTHENTICATION +usbcore-y +=3D authent.o authent_netlink.o +endif + usbcore-$(CONFIG_OF) +=3D of.o usbcore-$(CONFIG_USB_PCI) +=3D hcd-pci.o usbcore-$(CONFIG_ACPI) +=3D usb-acpi.o --=20 2.50.0