* [RFC PATCH 0/4] Support for usb authentication
@ 2025-06-20 14:27 nicolas.bouchinet
2025-06-20 14:27 ` [RFC PATCH 1/4] usb: core: Introduce netlink usb authentication policy engine nicolas.bouchinet
` (3 more replies)
0 siblings, 4 replies; 24+ messages in thread
From: nicolas.bouchinet @ 2025-06-20 14:27 UTC (permalink / raw)
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, linux-usb
We have been working on the implementation of the USB authentication
protocol in the kernel.
You can find our work here https://github.com/ANSSI-FR/usb_authentication.
It is still work in progress but we would like to start discussions
about the implementation design and its possible integration to the
Linux kernel.
Best regards,
Nicolas and Luc
USB peripherals are an important attack vector in personal computers and
pose a risk to the cyber security of companies and organizations.
The USB foundation has published a standard to allow the authentication
of USB peripherals ([1] and [2]). It defines a mechanism for the host to
request credentials and issue an authentication challenge to USB-2 or
USB-3 peripherals, either upon connection or later during the use of the
peripheral.
We currently envision the following use cases for USB authentication:
- company networks where computers and peripherals can be privately
controlled and administered;
- USB cleaning or decontamination stations;
- individuals who want to prevent unauthorized device plug-in into their
machine.
The implementation of this feature will obviously necessitate efforts
from both the kernel community and peripherals vendors. We believe that
providing an implementation of the host side of the protocol in the
Linux kernel will encourage constructors to include this feature in
their devices. On the other hand, we are working on implementing
reference code for embedded devices, notably for Zephyr OS.
The USB authentication protocol is based on a simple signature
challenge. Devices hold between 1 and 8 pairs of private signing key and
x509 certificate. Hosts must possess a store of root Certificate
Authority certificates provided by device vendors.
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 owned 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.
On the host side, this requires the following functions:
- handling the protocol exchange with the peripheral;
- X509 certificates validation and administration (root CA loading,
certificate revocation…);
- cryptographic functions for the challenge (random number generation
and ECDSA with the NIST P256 -secp256r1- curve);
- security policy management;
- authorization decision enforcement.
We chose to implement the authentication protocol exchange directly in
the kernel USB stack during the device enumeration. This is done by
first requesting the device BOS to detect its capacity at handling
authentication, then if supported starting the authentication sequence
with a digest request.
The implementation of the other functions is open to several design
alternatives, mainly based on their distribution between kernel and user
space. In this first implementation, we chose to implement most (all) of
the cryptographic functions, certificate management and security policy
management in user space in order to limit impact on the kernel side.
This allows for more personalization later on. The communication between
the kernel USB stack authentication function and user space is done via
a generic netlink socket.
The authorization decision enforcement can be done via the authorized
field of the usb_device and the associated authorization and
deauthorization functions. The usb_device also contains an authenticated
field that could be used to track 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 authentication protocol come with some inherent limitations, [3]
does a good job at describing most of them. During the implementation,
we also found that the value encoding of the Validity field in the x509
certificate differs from the RFC5280 [4]. This has prevented us from
using the x509 parser included in the Linux kernel or OpenSSL, we chose
to use the mbedtls library instead [5]. This obviously does not prevent
others to replace it with their preferred implementation. It will also
open discussions on the protocol enhancement.
The architectural choice to place most of the cryptographic and security
management functions in user space comes with its own limitations.
First it introduces a dependency on the user space program availability.
It will probably be necessary to introduce a fail-safe mechanism if the
authentication can not be completed. Also, during early boot stages the
user space service will be needed in one form or another in the
initramfs.
The second limitation is that the device initialization process is
paused multiple times. Each time, the hub lock is released in order not
to block the rest of the stack; and then reacquired when a response has
been received from user space. The resuming of the operation on the
device must be done with great care.
Last, we do not yet interface properly with the rest of the usb stack
and thus do not enforce a strict control of the two authenticated and
authorized fields. Other sections of the kernel or userspace are able to
overwrite those fields using the sysfs exposed files for example.
The current kernel implementation of the USB authentication protocol is
experimental and has the following limitations:
- It does not yet handle all possible protocol errors.
- It has been tested with a QEMU mock device, but tests with real
hardware are still in progress. As such, the over-the-wire protocol
has not yet been fully validated.
- The kernel/user space communication has not yet been completely
validated, including the interruption of the worker thread and its
resuming.
- Device authorization and deauthorization has not been completely
implemented.
- It lacks an overall documentation and test suite.
Our current kernel patch is obviously a work-in-progress and not yet
ready for merging. We feel it is best to start a discussion on the
architectural choices and gather early comments that could be used to
improve the design.
Concerning the user space functions, they are currently implemented in a
small independent executable as a proof-of-concept. In the future,
integrating it in existing larger projects, like USBGuard [6], would
allow presenting a homogeneous USB administration interface to the user.
We would like to get comments on the proposed architectural choices
regarding the repartition of functions between kernel and user space and
on the implementation in the USB stack, mostly concerning the releasing
and reacquiring the hub lock multiple times during the authentication
process.
You can find in the following repository [7] the necessary code for
creating a test environment:
- the Linux kernel patches;
- a python utility to generate a small PKI for device enrollment;
- a C minimalist service to implement the USB policy engine;
- patches for QEMU to implement a mock USB device with the
authentication capability;
- a testbed to compile and test the project.
- [1] “Universal Serial Bus Security Foundation Specification”, Revision
1.0 with ECN and Errata through January 7, 2019
- [2] “Universal Serial Bus Type-C Authentication Specification”,
Revision 1.0 with ECN and Errata through January 7, 2019
- [3] J. Tian, N. Scaife, D. Kumar, M. Bailey, A. Bates and K. Butler,
"SoK: "Plug & Pray" Today – Understanding USB Insecurity in Versions 1
Through C," 2018 IEEE Symposium on Security and Privacy (SP), San
Francisco, CA, USA, 2018, pp. 1032-1047, doi: 10.1109/SP.2018.00037
- [4] RFC 5280, Internet X.509 Public Key Infrastructure Certificate and
Certificate Revocation List (CRL) Profile, May 2008
- [5] https://www.trustedfirmware.org/projects/mbed-tls/
- [6] https://usbguard.github.io/
- [7] https://github.com/ANSSI-FR/usb_authentication
Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
---
Nicolas Bouchinet (4):
usb: core: Introduce netlink usb authentication policy engine
usb: core: Introduce usb authentication feature
usb: core: Plug the usb authentication capability
usb: core: Add Kconfig option to compile usb authorization
drivers/usb/core/Kconfig | 8 +
drivers/usb/core/Makefile | 4 +
drivers/usb/core/authent.c | 631 +++++++++++++++++
drivers/usb/core/authent.h | 166 +++++
drivers/usb/core/authent_netlink.c | 1080 +++++++++++++++++++++++++++++
drivers/usb/core/authent_netlink.h | 157 +++++
drivers/usb/core/config.c | 51 +-
drivers/usb/core/hub.c | 6 +
drivers/usb/core/usb.c | 5 +
include/linux/usb.h | 2 +
include/uapi/linux/usb/usb_auth_netlink.h | 67 ++
11 files changed, 2176 insertions(+), 1 deletion(-)
---
base-commit: fc85704c3dae5ac1cb3c94045727241cd72871ff
change-id: 20250620-usb_authentication-333bda6f33fe
Best regards,
--
Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [RFC PATCH 1/4] usb: core: Introduce netlink usb authentication policy engine
2025-06-20 14:27 [RFC PATCH 0/4] Support for usb authentication nicolas.bouchinet
@ 2025-06-20 14:27 ` nicolas.bouchinet
2025-06-21 9:37 ` Sabyrzhan Tasbolatov
2025-06-20 14:27 ` [RFC PATCH 2/4] usb: core: Introduce usb authentication feature nicolas.bouchinet
` (2 subsequent siblings)
3 siblings, 1 reply; 24+ messages in thread
From: nicolas.bouchinet @ 2025-06-20 14:27 UTC (permalink / raw)
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, linux-usb
From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
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 <luc.bonnafoux@ssi.gouv.fr>
Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
---
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..d53a220c762ffc1bd9aeb95bf90dc0dd79c43f09
--- /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 <luc.bonnafoux@ssi.gouv.fr>
+ * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
+ *
+ */
+
+#include <linux/sched.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/capability.h>
+
+#include <net/genetlink.h>
+
+#include <uapi/linux/usb/usb_auth_netlink.h>
+
+#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 protected */
+ uint8_t done; /**< 1 if the response has been received, used as wait condition */
+ 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_OUTSTANDING_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 = -EXFULL;
+ uint32_t i = 0;
+
+ mutex_lock(&usb_auth_reqs_mutex);
+
+ // Take the first available slot
+ for (i = 0; i < USB_AUTH_MAX_OUTSTANDING_REQS; i++) {
+ if (usb_auth_outstanding_reqs[i].used == 0) {
+ usb_auth_outstanding_reqs[i].used = 1;
+ usb_auth_outstanding_reqs[i].done = 0;
+ usb_auth_outstanding_reqs[i].error = USBAUTH_OK;
+ memset(usb_auth_outstanding_reqs[i].resp, 0,
+ USB_AUTH_MAX_RESP_SIZE);
+ *index = i;
+ ret = 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 = 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 provided
+ * in the request
+ */
+static int usb_auth_register_req_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ int ret = 0;
+ void *hdr = NULL;
+ struct sk_buff *msg = 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 = info->snd_portid;
+ pol_eng_net = genl_info_net(info);
+
+ wake_up_all(&usb_req_wq);
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (msg == NULL) {
+ pr_err("failed to allocate message buffer\n");
+ return -ENOMEM;
+ }
+
+ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+ &usbauth_genl_fam, 0, USBAUTH_CMD_REGISTER);
+ if (hdr == NULL) {
+ pr_err("failed to create genetlink header\n");
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+ }
+
+ genlmsg_end(msg, hdr);
+
+ ret = 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 = 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 = 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 = USBAUTH_INVRESP;
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ usb_auth_outstanding_reqs[index].error =
+ nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
+
+ if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
+ pr_err("digest_resp_doit: response error\n");
+ usb_auth_outstanding_reqs[index].done = 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 = USBAUTH_INVRESP;
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ usb_auth_outstanding_reqs[index].resp[0] =
+ nla_get_u8(info->attrs[USBAUTH_A_KNOWN]);
+ usb_auth_outstanding_reqs[index].resp[1] =
+ nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]);
+ ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0] =
+ nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]);
+
+ usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ usb_auth_outstanding_reqs[index].error =
+ nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
+
+ if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
+ pr_err("response error\n");
+ usb_auth_outstanding_reqs[index].done = 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 = 1;
+ usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
+ return -EINVAL;
+ }
+
+ usb_auth_outstanding_reqs[index].resp[0] =
+ nla_get_u8(info->attrs[USBAUTH_A_VALID]);
+ usb_auth_outstanding_reqs[index].resp[1] =
+ nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]);
+ ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0] =
+ nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]);
+
+ usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ usb_auth_outstanding_reqs[index].error =
+ nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
+
+ if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
+ pr_err("response error\n");
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ if (!info->attrs[USBAUTH_A_VALID]) {
+ pr_err("invalid response: missing attributes\n");
+ usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ usb_auth_outstanding_reqs[index].resp[0] =
+ nla_get_u8(info->attrs[USBAUTH_A_VALID]);
+
+ usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ usb_auth_outstanding_reqs[index].error =
+ nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
+
+ if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
+ pr_err("response error\n");
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ if (!info->attrs[USBAUTH_A_NONCE]) {
+ pr_err("invalid response: missing attributes\n");
+ usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ nla_memcpy(usb_auth_outstanding_reqs[index].resp, info->attrs[USBAUTH_A_NONCE], 32);
+
+ usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ usb_auth_outstanding_reqs[index].error =
+ nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
+
+ if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
+ pr_err("response error\n");
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ if (!info->attrs[USBAUTH_A_VALID]) {
+ pr_err("invalid response: missing attributes\n");
+ usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
+ usb_auth_outstanding_reqs[index].done = 1;
+ return -EINVAL;
+ }
+
+ usb_auth_outstanding_reqs[index].resp[0] =
+ nla_get_u8(info->attrs[USBAUTH_A_VALID]);
+
+ usb_auth_outstanding_reqs[index].done = 1;
+
+ wake_up_all(&usb_req_wq);
+
+ return 0;
+}
+
+/* Attribute validation policy */
+static struct nla_policy usbauth_attr_pol[USBAUTH_A_MAX + 1] = {
+ [USBAUTH_A_REQ_ID] = {.type = NLA_U32,},
+ [USBAUTH_A_DEV_ID] = {.type = NLA_U32,},
+ [USBAUTH_A_DIGEST] = {.type = NLA_UNSPEC, .len = 32,},
+ [USBAUTH_A_DIGESTS] = {.type = NLA_UNSPEC, .len = 256,},
+ [USBAUTH_A_SLOT_MASK] = {.type = NLA_U8,},
+ [USBAUTH_A_KNOWN] = {.type = NLA_U8,},
+ [USBAUTH_A_BLOCKED] = {.type = NLA_U8,},
+ [USBAUTH_A_VALID] = {.type = NLA_U8,},
+ [USBAUTH_A_CERTIFICATE] = {.type = NLA_UNSPEC, .max = 4096,},
+ [USBAUTH_A_CERT_LEN] = {.type = NLA_U32},
+ [USBAUTH_A_ROUTE] = {.type = NLA_U32},
+ [USBAUTH_A_NONCE] = {.type = NLA_BINARY, .len = 32,},
+ [USBAUTH_A_CHALL] = {.type = NLA_UNSPEC, .len = 204,},
+ [USBAUTH_A_DESCRIPTOR] = {.type = NLA_UNSPEC, .len = USBAUTH_MAX_DESC_SIZE},
+ [USBAUTH_A_BOS] = {.type = NLA_UNSPEC, .len = USBAUTH_MAX_BOS_SIZE},
+ [USBAUTH_A_ERROR_CODE] = {.type = NLA_U8},
+};
+
+/* Operations */
+static struct genl_ops usbauth_genl_ops[] = {
+ {
+ .cmd = USBAUTH_CMD_REGISTER,
+ .policy = usbauth_attr_pol,
+ .doit = usb_auth_register_req_doit,
+ },
+ {
+ .cmd = USBAUTH_CMD_RESP_DIGEST,
+ .policy = usbauth_attr_pol,
+ .doit = usb_auth_digest_resp_doit,
+ },
+ {
+ .cmd = USBAUTH_CMD_RESP_CERTIFICATE,
+ .policy = usbauth_attr_pol,
+ .doit = usb_auth_cert_resp_doit,
+ },
+ {
+ .cmd = USBAUTH_CMD_RESP_REMOVE_DEV,
+ .policy = usbauth_attr_pol,
+ .doit = usb_auth_remove_dev_doit,
+ },
+ {
+ .cmd = USBAUTH_CMD_RESP_GEN_NONCE,
+ .policy = usbauth_attr_pol,
+ .doit = usb_auth_gen_nonce_doit,
+ },
+ {
+ .cmd = USBAUTH_CMD_RESP_CHECK_CHALL,
+ .policy = usbauth_attr_pol,
+ .doit = usb_auth_check_chall_doit,
+ }
+};
+
+/* USB Authentication netlink family definition */
+static struct genl_family usbauth_genl_fam = {
+ .name = USBAUTH_GENL_NAME,
+ .version = USBAUTH_GENL_VERSION,
+ .maxattr = USBAUTH_A_MAX,
+ .ops = usbauth_genl_ops,
+ .n_ops = ARRAY_SIZE(usbauth_genl_ops),
+ .mcgrps = NULL,
+ .n_mcgrps = 0,
+};
+
+int usb_auth_init_netlink(void)
+{
+ int ret = 0;
+ uint8_t i = 0;
+
+ for (i = 0; i < USB_AUTH_MAX_OUTSTANDING_REQS; i++)
+ usb_auth_outstanding_reqs[i].used = 0;
+
+ init_waitqueue_head(&usb_req_wq);
+
+ ret = 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 *const digests,
+ const uint8_t mask, uint8_t *is_known, uint8_t *is_blocked, uint32_t *id)
+{
+ int ret = -1;
+ void *hdr = NULL;
+ struct sk_buff *skb = NULL;
+ uint32_t index = 0;
+
+ if (digests == NULL) {
+ pr_err("invalid inputs\n");
+ return -EINVAL;
+ }
+
+ // Arbitrary 30s wait before giving up
+ if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (skb == NULL) {
+ pr_err("failed to allocated buffer\n");
+ return -ENOMEM;
+ }
+
+ hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
+ USBAUTH_CMD_CHECK_DIGEST);
+ if (unlikely(hdr == NULL)) {
+ pr_err("failed to place header\n");
+ nlmsg_free(skb);
+ return -ENOMEM;
+ }
+
+ ret = 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 = 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 = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
+ if (ret != 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 == 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 == 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 = usb_auth_outstanding_reqs[index].resp[0];
+ *is_blocked = usb_auth_outstanding_reqs[index].resp[1];
+ *id = ((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 = -1;
+ void *hdr = NULL;
+ struct sk_buff *skb = NULL;
+ uint32_t index = 0;
+
+ if (chain == NULL || chain_len > 4096 || digest == NULL) {
+ pr_err("invalid inputs\n");
+ return -EINVAL;
+ }
+
+ // Arbitrary 30s wait before giving up
+ if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (skb == NULL) {
+ pr_err("failed to allocated buffer\n");
+ return -ENOMEM;
+ }
+
+ hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
+ USBAUTH_CMD_CHECK_CERTIFICATE);
+ if (unlikely(hdr == NULL)) {
+ pr_err("failed to place header\n");
+ nlmsg_free(skb);
+ return -ENOMEM;
+ }
+
+ ret = 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 = 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 = 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 = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
+ if (ret != 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 == 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 = usb_auth_outstanding_reqs[index].resp[0];
+ *is_blocked = usb_auth_outstanding_reqs[index].resp[1];
+ *id = ((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 = -1;
+ void *hdr = NULL;
+ struct sk_buff *skb = NULL;
+ uint32_t index = 0;
+
+ // Arbitrary 30s wait before giving up
+ if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (skb == NULL) {
+ pr_err("failed to allocated buffer\n");
+ return -ENOMEM;
+ }
+
+ hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
+ USBAUTH_CMD_REMOVE_DEV);
+ if (unlikely(hdr == NULL)) {
+ pr_err("failed to place header\n");
+ nlmsg_free(skb);
+ return -ENOMEM;
+ }
+
+ ret = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
+ if (ret != 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 == 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 = -1;
+ void *hdr = NULL;
+ struct sk_buff *skb = NULL;
+ uint32_t index = 0;
+
+ // Arbitrary 30s wait before giving up
+ if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (skb == NULL) {
+ pr_err("failed to allocated buffer\n");
+ return -ENOMEM;
+ }
+
+ hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
+ USBAUTH_CMD_GEN_NONCE);
+ if (unlikely(hdr == NULL)) {
+ pr_err("failed to place header\n");
+ nlmsg_free(skb);
+ return -ENOMEM;
+ }
+
+ ret = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
+ if (ret != 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 == 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 = -1;
+ void *hdr = NULL;
+ struct sk_buff *skb = NULL;
+ uint32_t index = 0;
+
+ if (unlikely(challenge == NULL || context == 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 != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (skb == NULL) {
+ pr_err("failed to allocated buffer\n");
+ return -ENOMEM;
+ }
+
+ hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
+ USBAUTH_CMD_CHECK_CHALL);
+ if (unlikely(hdr == NULL)) {
+ pr_err("failed to place header\n");
+ nlmsg_free(skb);
+ return -ENOMEM;
+ }
+
+ ret = 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 = 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 = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
+ if (ret != 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 == 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 = 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..504da32547b75b85b4328f3ea7df43b0a565dd18
--- /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 <luc.bonnafoux@ssi.gouv.fr>
+ * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
+ *
+ */
+
+#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 otherwise
+ * @param [out] is_blocked : 1 if the device is known and banned, 0 otherwise
+ * @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 one of the
+ * root CA in store.
+ * A certificate chain is blocked if one of the certificate of chain is blocked,
+ * 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 bytes
+ * @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 certificate is blocked
+ * @param [out] id : if is_known and !is_blocked then contains the 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 size
+ * - 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..e5b1e0e130a1ffb320aac4805161d579923a5b29
--- /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 <luc.bonnafoux@ssi.gouv.fr>
+ * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
+ *
+ */
+
+#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 = 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_ */
--
2.50.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 2/4] usb: core: Introduce usb authentication feature
2025-06-20 14:27 [RFC PATCH 0/4] Support for usb authentication nicolas.bouchinet
2025-06-20 14:27 ` [RFC PATCH 1/4] usb: core: Introduce netlink usb authentication policy engine nicolas.bouchinet
@ 2025-06-20 14:27 ` nicolas.bouchinet
2025-06-20 14:54 ` Greg Kroah-Hartman
` (2 more replies)
2025-06-20 14:27 ` [RFC PATCH 3/4] usb: core: Plug the usb authentication capability nicolas.bouchinet
2025-06-20 14:27 ` [RFC PATCH 4/4] usb: core: Add Kconfig option to compile usb authorization nicolas.bouchinet
3 siblings, 3 replies; 24+ messages in thread
From: nicolas.bouchinet @ 2025-06-20 14:27 UTC (permalink / raw)
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, linux-usb
From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
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 <luc.bonnafoux@ssi.gouv.fr>
Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
---
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..46f048d45a909e0fef504d71072eb7854320d271
--- /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 <luc.bonnafoux@ssi.gouv.fr>
+ * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/quirks.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <asm/byteorder.h>
+
+#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 buffer,
+ uint8_t digest[256], uint8_t *mask)
+{
+ int ret = 0;
+ struct usb_authent_digest_resp *digest_resp = NULL;
+
+ if (unlikely((buffer == NULL || mask == NULL))) {
+ pr_err("invalid arguments\n");
+ return -EINVAL;
+ }
+ ret = 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 = -ECOMM;
+ goto exit;
+ }
+
+ // Parse received digest response
+ digest_resp = (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 = digest_resp->slotMask;
+ memcpy(digest, digest_resp->digests, 256);
+
+ ret = 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 allocated
+ * @param [in] slot : slot in which to read the certificate
+ * @param [in] offset : at which the certificate fragment must be read
+ * @param [in] length : of the certificate fragment to read
+ * @param [out] cert_part : buffer to hold the fragment, caller allocated
+ *
+ * @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 = { 0 };
+ int ret = -1;
+
+ if (unlikely(buffer == NULL || cert_part == NULL)) {
+ pr_err("invalid argument\n");
+ return -EINVAL;
+ }
+
+ cert_req.offset = offset;
+ cert_req.length = length;
+
+ // AUTH OUT request transfer
+ memcpy(buffer, &cert_req, sizeof(struct usb_auth_cert_req));
+ ret = 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 = -ECOMM;
+ goto cleanup;
+ }
+
+ // AUTH IN certificate read
+ ret = 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 = -ECOMM;
+ goto cleanup;
+ }
+
+ // TODO: parse received header
+ memcpy(cert_part, buffer + 4, length);
+
+ ret = 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 *const buffer,
+ uint8_t slot, uint8_t **cert_der, size_t *cert_len)
+{
+ uint16_t read_offset = 0;
+ uint16_t read_length = 0;
+ uint8_t chain_part[64] = { 0 };
+
+ if (unlikely(slot >= 8 || buffer == NULL || cert_der == NULL || cert_len == 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) != 0) {
+ pr_err("Failed to get first certificate part\n");
+ return -ECOMM;
+ }
+
+ // Extract total length
+ *cert_len = ((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 = 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 = USB_AUTH_CHAIN_HEADER_SIZE;
+
+ while (read_offset < *cert_len) {
+ read_length = (*cert_len - read_offset) >= 64 ? 64 : (*cert_len - read_offset);
+
+ if (usb_auth_read_cert_part(dev, buffer, slot, read_offset,
+ read_length, chain_part) != 0) {
+ pr_err("USB AUTH: Failed to get certificate part\n");
+ return -ECOMM;
+ }
+
+ memcpy(*cert_der + read_offset, chain_part, read_length);
+
+ read_offset += 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 allocated
+ *
+ * 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 *buffer,
+ const uint8_t slot, const uint8_t slot_mask, const uint8_t *const nonce,
+ uint8_t *const chall)
+{
+ int ret = -1;
+
+ if (unlikely(buffer == NULL || slot >= 8 || nonce == NULL)) {
+ pr_err("invalid arguments\n");
+ return -EINVAL;
+ }
+
+ // AUTH OUT challenge request transfer
+ memcpy(buffer, nonce, 32);
+ ret = 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 = -ECOMM;
+ goto cleanup;
+ }
+
+ // Complete the challenge with the request
+ chall[1] = USB_SECURITY_PROTOCOL_VERSION;
+ chall[0] = USB_AUTHENT_CHALLENGE_REQ_TYPE;
+ chall[2] = slot;
+ chall[3] = 0x00;
+ memcpy(chall+4, nonce, 32);
+
+ // AUTH IN challenge response transfer
+ ret = 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 = -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 = 0;
+
+cleanup:
+
+ return ret;
+}
+
+/**
+ * @brief Create a device context according to USB Type-C Authentication Specification, 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 allocated
+ * @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 = 0;
+
+ if (unlikely(dev == NULL || ctx == NULL)) {
+ pr_err("invalid inputs\n");
+ return -EINVAL;
+ }
+
+ *ctx_size = 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 += (size_t) dev->descriptor.bLength;
+
+ // Device BOS and capabilities
+ if (unlikely(dev->bos == NULL || dev->bos->desc == NULL)) {
+ pr_err("invalid BOS\n");
+ return -EINVAL;
+ }
+
+ desc_size = 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 += desc_size;
+
+ // Device configuration descriptor
+ if (unlikely(dev->config == NULL)) {
+ pr_err("invalid configuration\n");
+ return -EINVAL;
+ }
+
+ desc_size = 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 += 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 == NULL || dev == NULL ||
+ dev->port_is_suspended == 1 ||
+ dev->reset_in_progress == 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 = 0;
+
+ uint8_t is_valid = 0;
+ uint8_t is_known = 0;
+ uint8_t is_blocked = 0;
+ uint8_t chain_nb = 0;
+ uint8_t slot_mask = 0;
+ uint8_t slot = 0;
+ uint8_t digests[256] = { 0 };
+ uint8_t nonce[32] = {0};
+ uint8_t chall[204] = {0};
+ uint32_t dev_id = 0;
+ size_t ctx_size = 0;
+ int i = 0;
+
+ uint8_t *cert_der = NULL;
+ size_t cert_len = 0;
+
+ if (unlikely(dev == NULL || dev->parent == NULL))
+ return -ENODEV;
+
+ struct usb_device *hub = dev->parent;
+
+ // By default set authorization status at false
+ dev->authorized = 0;
+ dev->authenticated = 0;
+
+ uint8_t *buffer = NULL;
+ // Buffer to hold responses
+ buffer = 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 = usb_authent_req_digest(dev, buffer, digests, &slot_mask);
+ if (ret != 0) {
+ pr_err("failed to get digest: %d\n", ret);
+ goto cleanup;
+ }
+ pr_notice("received digest\n");
+
+ usb_unlock_device(hub);
+ ret = usb_policy_engine_check_digest(dev->route, digests, slot_mask,
+ &is_known, &is_blocked, &dev_id);
+ if (ret != 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 = usb_auth_try_resume(dev, hub);
+ if (unlikely(ret != 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 = 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 = 0; i < 8; i++) {
+ // Test if slot contains a certificate chain
+ if (1 == ((slot_mask >> i) & 1)) {
+ ret = usb_authent_read_certificate(dev, buffer,
+ chain_nb,
+ &cert_der,
+ &cert_len);
+ if (ret != 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 = usb_policy_engine_check_cert_chain(
+ dev->route, digests + i * 32, cert_der,
+ cert_len, &is_valid, &is_blocked,
+ &dev_id);
+ if (ret != 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 = usb_auth_try_resume(dev, hub);
+ if (unlikely(ret != 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 = i;
+ break;
+ } else if (is_valid && is_blocked) {
+ // Found a valid and unauthorized certificate,
+ // reject device
+ ret = 0;
+ goto cleanup;
+ }
+ }
+ }
+ } else {
+ // Pick a slot among the valid ones, take first one
+ for (i = 0; i < 8; i++) {
+ if (1 == ((is_known >> i) & 1)) {
+ slot = i;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Authenticate the device with a challenge request
+ */
+ // Obtain a nonce for the challenge
+ usb_unlock_device(hub);
+ ret = usb_policy_engine_generate_challenge(dev_id, nonce);
+ if (ret != 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 = usb_auth_try_resume(dev, hub);
+ if (unlikely(ret != 0)) {
+ pr_err("failed to resume: %d\n", ret);
+ goto cleanup;
+ }
+
+ pr_info("resuming\n");
+
+ // Send a challenge request
+ ret = usb_authent_challenge_dev(dev, buffer, slot, slot_mask, nonce,
+ chall);
+ if (ret != 0) {
+ pr_err("failed to challenge device: %d\n", ret);
+ goto cleanup;
+ }
+ pr_notice("validated challenge\n");
+
+ // Create device context
+ ret = usb_auth_create_dev_ctx(dev, buffer, 512, &ctx_size);
+ if (ret != 0) {
+ pr_err("failed to create context: %d\n", ret);
+ goto cleanup;
+ }
+
+ // Validate the challenge
+ usb_unlock_device(hub);
+ ret = usb_policy_engine_check_challenge(dev_id, chall, buffer, ctx_size,
+ &is_valid);
+ if (ret != 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 = usb_auth_try_resume(dev, hub);
+ if (unlikely(ret != 0)) {
+ pr_err("failed to resume: %d\n", ret);
+ goto cleanup;
+ }
+
+ pr_info("resuming\n");
+
+ // Apply authorization decision
+ if (is_valid) {
+ dev->authorized = 1;
+ dev->authenticated = 1;
+ }
+
+ ret = 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..c3852636dbcea9150ed1663769e2a7b6348f528c
--- /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 <luc.bonnafoux@ssi.gouv.fr>
+ * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
+ *
+ */
+
+#ifndef __USB_CORE_AUTHENT_H_
+#define __USB_CORE_AUTHENT_H_
+
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/usb/ch11.h>
+#include <linux/usb/hcd.h>
+
+/* 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_VERSION */
+ __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 endian*/
+ __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 digests */
+ __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_ */
--
2.50.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 3/4] usb: core: Plug the usb authentication capability
2025-06-20 14:27 [RFC PATCH 0/4] Support for usb authentication nicolas.bouchinet
2025-06-20 14:27 ` [RFC PATCH 1/4] usb: core: Introduce netlink usb authentication policy engine nicolas.bouchinet
2025-06-20 14:27 ` [RFC PATCH 2/4] usb: core: Introduce usb authentication feature nicolas.bouchinet
@ 2025-06-20 14:27 ` nicolas.bouchinet
2025-06-20 19:11 ` Alan Stern
` (2 more replies)
2025-06-20 14:27 ` [RFC PATCH 4/4] usb: core: Add Kconfig option to compile usb authorization nicolas.bouchinet
3 siblings, 3 replies; 24+ messages in thread
From: nicolas.bouchinet @ 2025-06-20 14:27 UTC (permalink / raw)
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, linux-usb
From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
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 functions.
The usb_device also contains an authenticated field that could be used to track
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 <luc.bonnafoux@ssi.gouv.fr>
Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
---
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..45ee8e93e263c41f1bf4271be4e69ccfcac3f650 100644
--- a/drivers/usb/core/config.c
+++ b/drivers/usb/core/config.c
@@ -14,6 +14,7 @@
#include <asm/byteorder.h>
#include "usb.h"
+#include "authent.h"
#define USB_MAXALTSETTING 128 /* Hard limit */
@@ -824,7 +825,50 @@ static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
kref_init(&intfc->ref);
}
- /* FIXME: parse the BOS descriptor */
+ /* If device USB version is above 2.0, get BOS descriptor */
+ /*
+ * Requirement for bcdUSB >= 2.10 is defined in USB 3.2 §9.2.6.6
+ * "Devices with a value of at least 0210H in the bcdUSB field of their
+ * device descriptor shall support GetDescriptor (BOS Descriptor) requests."
+ *
+ * To discuss, BOS request could be also sent for bcdUSB >= 0x0201
+ */
+ // Set a default value for authenticated at true in order not to block devices
+ // that do not support the authentication
+ dev->authenticated = 1;
+
+ if (le16_to_cpu(dev->descriptor.bcdUSB) >= 0x0201) {
+ pr_notice("bcdUSB >= 0x0201\n");
+ retval = 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 = usb_authenticate_device(dev);
+ if (retval != 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");
+ }
+ }
/* 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 = bos->bLength;
total_len = le16_to_cpu(bos->wTotalLength);
num = 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 =
(struct usb_ptm_cap_descriptor *)buffer;
break;
+ case USB_AUTHENT_CAP_TYPE:
+ dev->bos->authent_cap =
+ (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..753e55155ea34a7739b5f530dad429534e60842d 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 = MKDEV(USB_DEVICE_MAJOR,
(((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
+ // 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);
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index 0b4685aad2d50337f3cacb2198c95a68ae8eff86..76847c01d3493e2527992a3bb927422519d9a974 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -46,6 +46,7 @@
#include <linux/dma-mapping.h>
#include "hub.h"
+#include "authent_netlink.h"
const char *usbcore_name = "usbcore";
@@ -1080,6 +1081,10 @@ static int __init usb_init(void)
usb_debugfs_init();
usb_acpi_register();
+
+ // TODO : check error case
+ usb_auth_init_netlink();
+
retval = 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..e9037c8120b43556f8610f9acb3ad4129e847b98 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;
};
int __usb_get_extra_descriptor(char *buffer, unsigned size,
--
2.50.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 4/4] usb: core: Add Kconfig option to compile usb authorization
2025-06-20 14:27 [RFC PATCH 0/4] Support for usb authentication nicolas.bouchinet
` (2 preceding siblings ...)
2025-06-20 14:27 ` [RFC PATCH 3/4] usb: core: Plug the usb authentication capability nicolas.bouchinet
@ 2025-06-20 14:27 ` nicolas.bouchinet
2025-06-21 7:22 ` Greg Kroah-Hartman
3 siblings, 1 reply; 24+ messages in thread
From: nicolas.bouchinet @ 2025-06-20 14:27 UTC (permalink / raw)
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, linux-usb
From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
This enables the usb authentication protocol implementation.
Co-developed-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
---
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..07ba67137b7fe16ecb1e993a51dbbfd4dd3ada88 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.
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..7ba1a89cf3de7a398889eee1820f2bfbbc4280f5 100644
--- a/drivers/usb/core/Makefile
+++ b/drivers/usb/core/Makefile
@@ -8,6 +8,10 @@ usbcore-y += config.o file.o buffer.o sysfs.o endpoint.o
usbcore-y += devio.o notify.o generic.o quirks.o devices.o
usbcore-y += phy.o port.o
+ifdef CONFIG_USB_AUTHENTICATION
+usbcore-y += authent.o authent_netlink.o
+endif
+
usbcore-$(CONFIG_OF) += of.o
usbcore-$(CONFIG_USB_PCI) += hcd-pci.o
usbcore-$(CONFIG_ACPI) += usb-acpi.o
--
2.50.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/4] usb: core: Introduce usb authentication feature
2025-06-20 14:27 ` [RFC PATCH 2/4] usb: core: Introduce usb authentication feature nicolas.bouchinet
@ 2025-06-20 14:54 ` Greg Kroah-Hartman
2025-06-30 11:07 ` Nicolas Bouchinet
2025-06-21 10:21 ` Sabyrzhan Tasbolatov
2025-06-25 9:59 ` Oliver Neukum
2 siblings, 1 reply; 24+ messages in thread
From: Greg Kroah-Hartman @ 2025-06-20 14:54 UTC (permalink / raw)
To: nicolas.bouchinet
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, linux-usb
First off, thanks so much for doing this work, I've been wondering if
anyone would ever do it :)
Just a few quick review comments that you might want to do for the next
round of your patches for some basic style things:
On Fri, Jun 20, 2025 at 04:27:17PM +0200, nicolas.bouchinet@oss.cyber.gouv.fr wrote:
> +#include <linux/types.h>
> +#include <linux/usb.h>
> +#include <linux/usb/ch9.h>
> +#include <linux/usb/hcd.h>
> +#include <linux/usb/quirks.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <asm/byteorder.h>
> +
> +#include "authent_netlink.h"
> +
> +#include "authent.h"
No need for the blank lines there.
> +static int usb_authent_req_digest(struct usb_device *dev, uint8_t *const buffer,
> + uint8_t digest[256], uint8_t *mask)
> +{
> + int ret = 0;
> + struct usb_authent_digest_resp *digest_resp = NULL;
> +
> + if (unlikely((buffer == NULL || mask == NULL))) {
> + pr_err("invalid arguments\n");
> + return -EINVAL;
> + }
> + ret = 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 = -ECOMM;
> + goto exit;
> + }
> +
> + // Parse received digest response
> + digest_resp = (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);
Always use the dev_*() macros instead of pr_*() ones as that way you
know what device is making the message please.
> +
> + *mask = digest_resp->slotMask;
> + memcpy(digest, digest_resp->digests, 256);
> +
> + ret = 0;
> +
> +exit:
> +
> + return ret;
> +}
> +
> +struct usb_auth_cert_req {
> + uint16_t offset;
> + uint16_t length;
> +} __packed;
Kernel types are u16, not uint16_t. The uint*_t types are from
userspace C code, not kernel code. Yes, they are slowly sliding in in
places, but let's not do that unless really required for some specific
reason.
And why packed?
And what about endian issues, the data from the devices should be in a
specific format, right?
> +
> +/**
> + * @brief Request a specific part of a certificate chain from the device
Are you sure this is proper kerneldoc style? Does this build properly?
> + *
> + * 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 allocated
> + * @param [in] slot : slot in which to read the certificate
> + * @param [in] offset : at which the certificate fragment must be read
> + * @param [in] length : of the certificate fragment to read
> + * @param [out] cert_part : buffer to hold the fragment, caller allocated
Again, I don't think this is kerneldoc format. Please build the kernel
documentation output and see what this results in.
> + *
> + * @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 = { 0 };
> + int ret = -1;
Use real error values, not random integers :)
> +
> + if (unlikely(buffer == NULL || cert_part == NULL)) {
Only use likely/unlikely if you can measure the speed difference. For
USB, and probe sequences, that will never be the casae.
> + pr_err("invalid argument\n");
Again, dev_err()?
But how can these parameters not be set properly? You control how they
are called, no need to always verify that you wrote the code properly :)
> + return -EINVAL;
> + }
> +
> + cert_req.offset = offset;
> + cert_req.length = length;
> +
> + // AUTH OUT request transfer
> + memcpy(buffer, &cert_req, sizeof(struct usb_auth_cert_req));
> + ret = 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 = -ECOMM;
> + goto cleanup;
> + }
> +
> + // AUTH IN certificate read
> + ret = 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 = -ECOMM;
> + goto cleanup;
> + }
> +
> + // TODO: parse received header
> + memcpy(cert_part, buffer + 4, length);
> +
> + ret = 0;
> +
> +cleanup:
> +
> + return ret;
As "cleanup:" does nothing, no need for it, just return the error value
above when you were doing a goto line.
> +}
> +
> +/**
> + * 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 *const buffer,
> + uint8_t slot, uint8_t **cert_der, size_t *cert_len)
> +{
> + uint16_t read_offset = 0;
> + uint16_t read_length = 0;
> + uint8_t chain_part[64] = { 0 };
Again, u16 and u8 please.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 3/4] usb: core: Plug the usb authentication capability
2025-06-20 14:27 ` [RFC PATCH 3/4] usb: core: Plug the usb authentication capability nicolas.bouchinet
@ 2025-06-20 19:11 ` Alan Stern
2025-06-30 11:20 ` Nicolas Bouchinet
2025-06-21 11:09 ` Sabyrzhan Tasbolatov
2025-06-23 18:15 ` Oliver Neukum
2 siblings, 1 reply; 24+ messages in thread
From: Alan Stern @ 2025-06-20 19:11 UTC (permalink / raw)
To: nicolas.bouchinet
Cc: Greg Kroah-Hartman, Kannappan R, Sabyrzhan Tasbolatov,
Krzysztof Kozlowski, Stefan Eichenberger, Thomas Gleixner,
Pawel Laszczak, Ma Ke, Jeff Johnson, Luc Bonnafoux, Luc Bonnafoux,
Nicolas Bouchinet, linux-kernel, linux-usb
On Fri, Jun 20, 2025 at 04:27:18PM +0200, nicolas.bouchinet@oss.cyber.gouv.fr wrote:
> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>
> 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 functions.
> The usb_device also contains an authenticated field that could be used to track
> 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 <luc.bonnafoux@ssi.gouv.fr>
> Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> ---
Here are some more stylistic suggestions, similar to what Greg said.
> @@ -824,7 +825,50 @@ static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
> kref_init(&intfc->ref);
> }
>
> - /* FIXME: parse the BOS descriptor */
> + /* If device USB version is above 2.0, get BOS descriptor */
> + /*
> + * Requirement for bcdUSB >= 2.10 is defined in USB 3.2 §9.2.6.6
> + * "Devices with a value of at least 0210H in the bcdUSB field of their
> + * device descriptor shall support GetDescriptor (BOS Descriptor) requests."
> + *
> + * To discuss, BOS request could be also sent for bcdUSB >= 0x0201
> + */
> + // Set a default value for authenticated at true in order not to block devices
> + // that do not support the authentication
It looks really weird to see three comment blocks, in three different
styles, right next to each other. In the kernel, we avoid C++-style //
comments. And two adjacent /**/-style comments would normally be
separated by a blank line or just merged into one bigger comment.
> + dev->authenticated = 1;
> +
> + if (le16_to_cpu(dev->descriptor.bcdUSB) >= 0x0201) {
> + pr_notice("bcdUSB >= 0x0201\n");
> + retval = 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 = usb_authenticate_device(dev);
> + if (retval != 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;
Do these two cases need to be handled separately? Regardless of whether
the function call fails, or succeeds but gives a negative result,
shouldn't the end result be the same?
> + } else {
> + pr_notice("device has been authorized\n");
> + }
Be careful not to mix up the two separate notions of authentication and
authorization. It's already difficult to keep them straight, because
the words are so similar.
> + } else {
> + // USB authentication unsupported
> + // Apply security policy on failed devices
> + pr_notice("no authentication capability\n");
> + }
> +#endif
We generally prefer to avoid #if or #ifdef blocks inside function
definitions, if at all possible. In this case, you could define a
separate function usb_handle_bos_authent() (or whatever you want to call
it) that does this work, all inside a #ifdef block, along with a #else
section that defines usb_handle_bos_authent() to be an inline empty
function.
> + } else {
> + // Older USB version, authentication not supported
> + // Apply security policy on failed devices
> + pr_notice("device does not have a BOS descriptor\n");
> + }
> + }
>
> /* 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 = bos->bLength;
> total_len = le16_to_cpu(bos->wTotalLength);
> num = bos->bNumDeviceCaps;
> +
Patches shouldn't make extraneous whitespace changes to existing code.
> kfree(bos);
> if (total_len < length)
> return -EINVAL;
> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index 0e1dd6ef60a719f59a22d86caeb20f86991b5b29..753e55155ea34a7739b5f530dad429534e60842d 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 = MKDEV(USB_DEVICE_MAJOR,
> (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
>
> + // 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;
A comment that simply repeats what the code already says is not very
useful. Such comments do exist here and there (I'm guilty of adding
some of them myself), but in general they should be avoided.
> diff --git a/include/linux/usb.h b/include/linux/usb.h
> index b46738701f8dc46085f251379873b6a8a008d99d..e9037c8120b43556f8610f9acb3ad4129e847b98 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;
None of the other entries here have a comment like this. Why does the
new entry need one?
Alan Stern
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 4/4] usb: core: Add Kconfig option to compile usb authorization
2025-06-20 14:27 ` [RFC PATCH 4/4] usb: core: Add Kconfig option to compile usb authorization nicolas.bouchinet
@ 2025-06-21 7:22 ` Greg Kroah-Hartman
2025-06-30 11:22 ` Nicolas Bouchinet
0 siblings, 1 reply; 24+ messages in thread
From: Greg Kroah-Hartman @ 2025-06-21 7:22 UTC (permalink / raw)
To: nicolas.bouchinet
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, linux-usb
On Fri, Jun 20, 2025 at 04:27:19PM +0200, nicolas.bouchinet@oss.cyber.gouv.fr wrote:
> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>
> This enables the usb authentication protocol implementation.
>
> Co-developed-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
> Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> ---
> 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..07ba67137b7fe16ecb1e993a51dbbfd4dd3ada88 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.
>
> If unsure, keep the default value.
> +
> +config USB_AUTHENTICATION
> + bool "Enable USB authentication function"
> + default n
Nit, "default n" is the default, no need to ever list it.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 1/4] usb: core: Introduce netlink usb authentication policy engine
2025-06-20 14:27 ` [RFC PATCH 1/4] usb: core: Introduce netlink usb authentication policy engine nicolas.bouchinet
@ 2025-06-21 9:37 ` Sabyrzhan Tasbolatov
2025-06-30 11:42 ` Nicolas Bouchinet
0 siblings, 1 reply; 24+ messages in thread
From: Sabyrzhan Tasbolatov @ 2025-06-21 9:37 UTC (permalink / raw)
To: nicolas.bouchinet
Cc: Greg Kroah-Hartman, Alan Stern, Kannappan R, Krzysztof Kozlowski,
Stefan Eichenberger, Thomas Gleixner, Pawel Laszczak, Ma Ke,
Jeff Johnson, Luc Bonnafoux, Luc Bonnafoux, Nicolas Bouchinet,
linux-kernel, linux-usb
On Fri, Jun 20, 2025 at 7:27 PM <nicolas.bouchinet@oss.cyber.gouv.fr> wrote:
>
> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>
> 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 <luc.bonnafoux@ssi.gouv.fr>
> Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> ---
> 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..d53a220c762ffc1bd9aeb95bf90dc0dd79c43f09
> --- /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 <luc.bonnafoux@ssi.gouv.fr>
> + * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> + *
> + */
> +
> +#include <linux/sched.h>
> +#include <linux/time.h>
> +#include <linux/delay.h>
> +#include <linux/slab.h>
> +#include <linux/mutex.h>
> +#include <linux/err.h>
> +#include <linux/capability.h>
> +
> +#include <net/genetlink.h>
> +
> +#include <uapi/linux/usb/usb_auth_netlink.h>
> +
> +#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 protected */
> + uint8_t done; /**< 1 if the response has been received, used as wait condition */
> + 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_OUTSTANDING_REQS];
Wonder, if hot-plugged low-speed hubs may exhaust the table and block every
other device for 5 min (according to the timeouts in this patch).
With the array hard-capped at 10, the 11-th concurrent device gets -EXFULL
and stalls enumeration. The capacity must become dynamic (e.g. xarray/idr)
or requests must be queued instead of rejected (?). IDR is deprecated,
though is used in drivers/usb.
> +
> +////////////////////////////////////////////////////////////////////////////////
> +// 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 = -EXFULL;
> + uint32_t i = 0;
> +
> + mutex_lock(&usb_auth_reqs_mutex);
> +
> + // Take the first available slot
> + for (i = 0; i < USB_AUTH_MAX_OUTSTANDING_REQS; i++) {
> + if (usb_auth_outstanding_reqs[i].used == 0) {
> + usb_auth_outstanding_reqs[i].used = 1;
> + usb_auth_outstanding_reqs[i].done = 0;
> + usb_auth_outstanding_reqs[i].error = USBAUTH_OK;
> + memset(usb_auth_outstanding_reqs[i].resp, 0,
> + USB_AUTH_MAX_RESP_SIZE);
> + *index = i;
> + ret = 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 = 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 provided
> + * in the request
> + */
> +static int usb_auth_register_req_doit(struct sk_buff *skb, struct genl_info *info)
> +{
> + int ret = 0;
> + void *hdr = NULL;
> + struct sk_buff *msg = 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 = info->snd_portid;
info->snd_portid is the per-net-ns socket port ID of the sender.
Any later caller, inside the same or a different network namespace
simply replaces the old value. Can we reject the second registration?
capable(CAP_SYS_ADMIN) succeeds in any user-namespace where the task
has that bit set. A container root therefore passes the check and can
issue USBAUTH_CMD_REGISTER, hijacking the channel.
> + pol_eng_net = genl_info_net(info);
Similarly, if that sender lives in a container’s net-namespace, pol_eng_net
now points there.
> +
> + wake_up_all(&usb_req_wq);
> +
> + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (msg == NULL) {
> + pr_err("failed to allocate message buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
> + &usbauth_genl_fam, 0, USBAUTH_CMD_REGISTER);
> + if (hdr == NULL) {
> + pr_err("failed to create genetlink header\n");
> + nlmsg_free(msg);
> + return -EMSGSIZE;
> + }
> +
> + genlmsg_end(msg, hdr);
> +
> + ret = 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 = 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 = 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 = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].error =
> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
AFAIK, `usb_auth_outstanding_reqs` fields `done/error/resp[]` are not protected
by the mutex that guards used. `usb_auth_reqs_mutex` is not held in
either place.
Potential race between any *_resp_doit() writer and the reader
in the matching usb_policy_engine_*() helper.
Perhaps, you can replace `error, resp[], done` by a single
`struct completion`, plus a private response struct that the reader accesses
only after some `complete()`.
> +
> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
> + pr_err("digest_resp_doit: response error\n");
> + usb_auth_outstanding_reqs[index].done = 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 = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].resp[0] =
> + nla_get_u8(info->attrs[USBAUTH_A_KNOWN]);
> + usb_auth_outstanding_reqs[index].resp[1] =
> + nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]);
> + ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0] =
> + nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]);
> +
> + usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].error =
> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
> +
> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
> + pr_err("response error\n");
> + usb_auth_outstanding_reqs[index].done = 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 = 1;
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].resp[0] =
> + nla_get_u8(info->attrs[USBAUTH_A_VALID]);
> + usb_auth_outstanding_reqs[index].resp[1] =
> + nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]);
> + ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0] =
> + nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]);
> +
> + usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].error =
> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
> +
> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
> + pr_err("response error\n");
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + if (!info->attrs[USBAUTH_A_VALID]) {
> + pr_err("invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].resp[0] =
> + nla_get_u8(info->attrs[USBAUTH_A_VALID]);
> +
> + usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].error =
> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
> +
> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
> + pr_err("response error\n");
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + if (!info->attrs[USBAUTH_A_NONCE]) {
> + pr_err("invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + nla_memcpy(usb_auth_outstanding_reqs[index].resp, info->attrs[USBAUTH_A_NONCE], 32);
> +
> + usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].error =
> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
> +
> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
> + pr_err("response error\n");
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + if (!info->attrs[USBAUTH_A_VALID]) {
> + pr_err("invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].resp[0] =
> + nla_get_u8(info->attrs[USBAUTH_A_VALID]);
> +
> + usb_auth_outstanding_reqs[index].done = 1;
> +
> + wake_up_all(&usb_req_wq);
> +
> + return 0;
> +}
> +
> +/* Attribute validation policy */
> +static struct nla_policy usbauth_attr_pol[USBAUTH_A_MAX + 1] = {
> + [USBAUTH_A_REQ_ID] = {.type = NLA_U32,},
> + [USBAUTH_A_DEV_ID] = {.type = NLA_U32,},
> + [USBAUTH_A_DIGEST] = {.type = NLA_UNSPEC, .len = 32,},
> + [USBAUTH_A_DIGESTS] = {.type = NLA_UNSPEC, .len = 256,},
> + [USBAUTH_A_SLOT_MASK] = {.type = NLA_U8,},
> + [USBAUTH_A_KNOWN] = {.type = NLA_U8,},
> + [USBAUTH_A_BLOCKED] = {.type = NLA_U8,},
> + [USBAUTH_A_VALID] = {.type = NLA_U8,},
> + [USBAUTH_A_CERTIFICATE] = {.type = NLA_UNSPEC, .max = 4096,},
> + [USBAUTH_A_CERT_LEN] = {.type = NLA_U32},
> + [USBAUTH_A_ROUTE] = {.type = NLA_U32},
> + [USBAUTH_A_NONCE] = {.type = NLA_BINARY, .len = 32,},
> + [USBAUTH_A_CHALL] = {.type = NLA_UNSPEC, .len = 204,},
> + [USBAUTH_A_DESCRIPTOR] = {.type = NLA_UNSPEC, .len = USBAUTH_MAX_DESC_SIZE},
> + [USBAUTH_A_BOS] = {.type = NLA_UNSPEC, .len = USBAUTH_MAX_BOS_SIZE},
> + [USBAUTH_A_ERROR_CODE] = {.type = NLA_U8},
> +};
> +
> +/* Operations */
> +static struct genl_ops usbauth_genl_ops[] = {
> + {
> + .cmd = USBAUTH_CMD_REGISTER,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_register_req_doit,
> + },
> + {
> + .cmd = USBAUTH_CMD_RESP_DIGEST,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_digest_resp_doit,
> + },
> + {
> + .cmd = USBAUTH_CMD_RESP_CERTIFICATE,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_cert_resp_doit,
> + },
> + {
> + .cmd = USBAUTH_CMD_RESP_REMOVE_DEV,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_remove_dev_doit,
> + },
> + {
> + .cmd = USBAUTH_CMD_RESP_GEN_NONCE,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_gen_nonce_doit,
> + },
> + {
> + .cmd = USBAUTH_CMD_RESP_CHECK_CHALL,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_check_chall_doit,
> + }
> +};
> +
> +/* USB Authentication netlink family definition */
> +static struct genl_family usbauth_genl_fam = {
> + .name = USBAUTH_GENL_NAME,
> + .version = USBAUTH_GENL_VERSION,
> + .maxattr = USBAUTH_A_MAX,
> + .ops = usbauth_genl_ops,
> + .n_ops = ARRAY_SIZE(usbauth_genl_ops),
> + .mcgrps = NULL,
> + .n_mcgrps = 0,
> +};
> +
> +int usb_auth_init_netlink(void)
> +{
> + int ret = 0;
> + uint8_t i = 0;
> +
> + for (i = 0; i < USB_AUTH_MAX_OUTSTANDING_REQS; i++)
> + usb_auth_outstanding_reqs[i].used = 0;
> +
> + init_waitqueue_head(&usb_req_wq);
> +
> + ret = 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 *const digests,
> + const uint8_t mask, uint8_t *is_known, uint8_t *is_blocked, uint32_t *id)
> +{
> + int ret = -1;
> + void *hdr = NULL;
> + struct sk_buff *skb = NULL;
> + uint32_t index = 0;
> +
> + if (digests == NULL) {
> + pr_err("invalid inputs\n");
> + return -EINVAL;
> + }
> +
> + // Arbitrary 30s wait before giving up
> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_TIMEOUT)) {
WAIT_USERSPACE_TIMEOUT is 30 sec and WAIT_RESPONSE_TIMEOUT is 5 min.
Can we keep a modest deadline, e.g. 5 sec for daemon presence and
10 sec for its reply, or something similar that makes sense?
> + 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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (skb == NULL) {
> + pr_err("failed to allocated buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
> + USBAUTH_CMD_CHECK_DIGEST);
> + if (unlikely(hdr == NULL)) {
> + pr_err("failed to place header\n");
> + nlmsg_free(skb);
> + return -ENOMEM;
> + }
> +
> + ret = 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 = 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 = nla_put(skb, USBAUTH_A_DIGESTS, 260, digests);
Attribute is declared as 256 in the policy
(usbauth_attr_pol[USBAUTH_A_DIGESTS].len = 256
but you emit 260 here. Is it wrong or where do 4 bytes come from?
> + if (ret) {
> + pr_err("failed to place digests\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
> + if (ret != 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 == 1,
> + HZ * WAIT_RESPONSE_TIMEOUT)) {
> + pr_err("userspace response not available\n");
> + usb_auth_release_reqs_slot(index);
Here we have 5 min enumeration stall while holding usb_lock_device().
Can we replace the blocking helper with a work-queue item?
So we could have pushed to the work and continue enumeration.
struct usb_auth_work {
struct work_struct work;
struct usb_device *udev;
/* request-specific data */
};
> + return -ECOMM;
> + }
> +
> + pr_info("received CHECK_DIGEST response\n");
> +
> + // Get response
> + if (usb_auth_outstanding_reqs[index].error == 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 = usb_auth_outstanding_reqs[index].resp[0];
> + *is_blocked = usb_auth_outstanding_reqs[index].resp[1];
> + *id = ((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 = -1;
> + void *hdr = NULL;
> + struct sk_buff *skb = NULL;
> + uint32_t index = 0;
> +
> + if (chain == NULL || chain_len > 4096 || digest == NULL) {
> + pr_err("invalid inputs\n");
> + return -EINVAL;
> + }
> +
> + // Arbitrary 30s wait before giving up
> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (skb == NULL) {
> + pr_err("failed to allocated buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
> + USBAUTH_CMD_CHECK_CERTIFICATE);
> + if (unlikely(hdr == NULL)) {
> + pr_err("failed to place header\n");
> + nlmsg_free(skb);
> + return -ENOMEM;
> + }
> +
> + ret = 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 = 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 = 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 = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
> + if (ret != 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 == 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 = usb_auth_outstanding_reqs[index].resp[0];
> + *is_blocked = usb_auth_outstanding_reqs[index].resp[1];
> + *id = ((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 = -1;
> + void *hdr = NULL;
> + struct sk_buff *skb = NULL;
> + uint32_t index = 0;
> +
> + // Arbitrary 30s wait before giving up
> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (skb == NULL) {
> + pr_err("failed to allocated buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
> + USBAUTH_CMD_REMOVE_DEV);
> + if (unlikely(hdr == NULL)) {
> + pr_err("failed to place header\n");
> + nlmsg_free(skb);
> + return -ENOMEM;
> + }
> +
> + ret = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
> + if (ret != 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 == 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 = -1;
> + void *hdr = NULL;
> + struct sk_buff *skb = NULL;
> + uint32_t index = 0;
> +
> + // Arbitrary 30s wait before giving up
> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (skb == NULL) {
> + pr_err("failed to allocated buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
> + USBAUTH_CMD_GEN_NONCE);
> + if (unlikely(hdr == NULL)) {
> + pr_err("failed to place header\n");
> + nlmsg_free(skb);
> + return -ENOMEM;
> + }
> +
> + ret = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
> + if (ret != 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 == 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 = -1;
> + void *hdr = NULL;
> + struct sk_buff *skb = NULL;
> + uint32_t index = 0;
> +
> + if (unlikely(challenge == NULL || context == 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 != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (skb == NULL) {
> + pr_err("failed to allocated buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
> + USBAUTH_CMD_CHECK_CHALL);
> + if (unlikely(hdr == NULL)) {
> + pr_err("failed to place header\n");
> + nlmsg_free(skb);
> + return -ENOMEM;
> + }
> +
> + ret = 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 = 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 = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
> + if (ret != 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 == 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 = 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..504da32547b75b85b4328f3ea7df43b0a565dd18
> --- /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 <luc.bonnafoux@ssi.gouv.fr>
> + * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> + *
> + */
> +
> +#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 otherwise
> + * @param [out] is_blocked : 1 if the device is known and banned, 0 otherwise
> + * @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 one of the
> + * root CA in store.
> + * A certificate chain is blocked if one of the certificate of chain is blocked,
> + * 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 bytes
> + * @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 certificate is blocked
> + * @param [out] id : if is_known and !is_blocked then contains the 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 size
> + * - 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..e5b1e0e130a1ffb320aac4805161d579923a5b29
> --- /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 <luc.bonnafoux@ssi.gouv.fr>
> + * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> + *
> + */
> +
> +#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 = 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_ */
>
> --
> 2.50.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/4] usb: core: Introduce usb authentication feature
2025-06-20 14:27 ` [RFC PATCH 2/4] usb: core: Introduce usb authentication feature nicolas.bouchinet
2025-06-20 14:54 ` Greg Kroah-Hartman
@ 2025-06-21 10:21 ` Sabyrzhan Tasbolatov
2025-06-30 11:56 ` Nicolas Bouchinet
2025-06-25 9:59 ` Oliver Neukum
2 siblings, 1 reply; 24+ messages in thread
From: Sabyrzhan Tasbolatov @ 2025-06-21 10:21 UTC (permalink / raw)
To: nicolas.bouchinet
Cc: Greg Kroah-Hartman, Alan Stern, Kannappan R, Krzysztof Kozlowski,
Stefan Eichenberger, Thomas Gleixner, Pawel Laszczak, Ma Ke,
Jeff Johnson, Luc Bonnafoux, Luc Bonnafoux, Nicolas Bouchinet,
linux-kernel, linux-usb
On Fri, Jun 20, 2025 at 7:27 PM <nicolas.bouchinet@oss.cyber.gouv.fr> wrote:
>
> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>
> 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 <luc.bonnafoux@ssi.gouv.fr>
> Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> ---
> 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..46f048d45a909e0fef504d71072eb7854320d271
> --- /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 <luc.bonnafoux@ssi.gouv.fr>
> + * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> + *
> + */
> +
> +#include <linux/types.h>
> +#include <linux/usb.h>
> +#include <linux/usb/ch9.h>
> +#include <linux/usb/hcd.h>
> +#include <linux/usb/quirks.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <asm/byteorder.h>
> +
> +#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 buffer,
> + uint8_t digest[256], uint8_t *mask)
> +{
> + int ret = 0;
> + struct usb_authent_digest_resp *digest_resp = NULL;
> +
> + if (unlikely((buffer == NULL || mask == NULL))) {
> + pr_err("invalid arguments\n");
> + return -EINVAL;
> + }
> + ret = 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 = -ECOMM;
> + goto exit;
> + }
> +
> + // Parse received digest response
> + digest_resp = (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 = digest_resp->slotMask;
Wonder, if we might have a bug for BE hosts here.
Spec says the slot bitmap is LE, for devices that set bit 7
it will be mis-interpreted on BE.
int usb_authenticate_device(struct usb_device *dev)
{
...
for (i = 0; i < 8; i++) {
if (1 == ((slot_mask >> i) & 1)) { /* bit-test */
/* use digest at digests[i * 32] */
...
}
}
}
That loop treats bit 0 as the LSB, so it does match the spec on both
little- and big-endian hosts. There is no endian bug in the mask
handling itself, AFAIU.
What can bite us, however, is positional coupling between the mask and
the digest array:
The spec guarantees that digest k appears only if bit k is set and
that digests are “returned in order of increasing slot number.”
So if a device leaves gap(s) (e.g. slots 0 and 2 populated, slot 1
empty) the firmware is allowed to pack the digests for 0 and 2
contiguously. Our code assumes digest[k] is always at offset k*32,
which fails in
that sparse case.
> + memcpy(digest, digest_resp->digests, 256);
> +
> + ret = 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 allocated
> + * @param [in] slot : slot in which to read the certificate
> + * @param [in] offset : at which the certificate fragment must be read
> + * @param [in] length : of the certificate fragment to read
> + * @param [out] cert_part : buffer to hold the fragment, caller allocated
> + *
> + * @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 = { 0 };
> + int ret = -1;
> +
> + if (unlikely(buffer == NULL || cert_part == NULL)) {
> + pr_err("invalid argument\n");
> + return -EINVAL;
> + }
> +
> + cert_req.offset = offset;
> + cert_req.length = length;
> +
> + // AUTH OUT request transfer
> + memcpy(buffer, &cert_req, sizeof(struct usb_auth_cert_req));
> + ret = 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 = -ECOMM;
> + goto cleanup;
> + }
> +
> + // AUTH IN certificate read
> + ret = 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 = -ECOMM;
> + goto cleanup;
> + }
> +
> + // TODO: parse received header
> + memcpy(cert_part, buffer + 4, length);
> +
> + ret = 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 *const buffer,
> + uint8_t slot, uint8_t **cert_der, size_t *cert_len)
> +{
> + uint16_t read_offset = 0;
> + uint16_t read_length = 0;
> + uint8_t chain_part[64] = { 0 };
> +
> + if (unlikely(slot >= 8 || buffer == NULL || cert_der == NULL || cert_len == 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) != 0) {
> + pr_err("Failed to get first certificate part\n");
> + return -ECOMM;
> + }
> +
> + // Extract total length
> + *cert_len = ((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 = 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 = USB_AUTH_CHAIN_HEADER_SIZE;
> +
> + while (read_offset < *cert_len) {
> + read_length = (*cert_len - read_offset) >= 64 ? 64 : (*cert_len - read_offset);
> +
> + if (usb_auth_read_cert_part(dev, buffer, slot, read_offset,
> + read_length, chain_part) != 0) {
> + pr_err("USB AUTH: Failed to get certificate part\n");
> + return -ECOMM;
> + }
> +
> + memcpy(*cert_der + read_offset, chain_part, read_length);
> +
> + read_offset += 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 allocated
> + *
> + * 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 *buffer,
> + const uint8_t slot, const uint8_t slot_mask, const uint8_t *const nonce,
> + uint8_t *const chall)
> +{
> + int ret = -1;
> +
> + if (unlikely(buffer == NULL || slot >= 8 || nonce == NULL)) {
> + pr_err("invalid arguments\n");
> + return -EINVAL;
> + }
> +
> + // AUTH OUT challenge request transfer
> + memcpy(buffer, nonce, 32);
> + ret = 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 = -ECOMM;
> + goto cleanup;
> + }
> +
> + // Complete the challenge with the request
> + chall[1] = USB_SECURITY_PROTOCOL_VERSION;
> + chall[0] = USB_AUTHENT_CHALLENGE_REQ_TYPE;
> + chall[2] = slot;
> + chall[3] = 0x00;
> + memcpy(chall+4, nonce, 32);
> +
> + // AUTH IN challenge response transfer
> + ret = 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 = -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 = 0;
> +
> +cleanup:
> +
> + return ret;
> +}
> +
> +/**
> + * @brief Create a device context according to USB Type-C Authentication Specification, 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 allocated
> + * @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 = 0;
> +
> + if (unlikely(dev == NULL || ctx == NULL)) {
> + pr_err("invalid inputs\n");
> + return -EINVAL;
> + }
> +
> + *ctx_size = 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 += (size_t) dev->descriptor.bLength;
> +
> + // Device BOS and capabilities
> + if (unlikely(dev->bos == NULL || dev->bos->desc == NULL)) {
> + pr_err("invalid BOS\n");
> + return -EINVAL;
> + }
> +
> + desc_size = 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 += desc_size;
> +
> + // Device configuration descriptor
> + if (unlikely(dev->config == NULL)) {
> + pr_err("invalid configuration\n");
> + return -EINVAL;
> + }
> +
> + desc_size = 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);
What is 9 byte size for? Can not find 9 in spec "chapter 5.5 Context Hash".
Do we want to copy the entire config descriptor(s) instead?
If yes, then we might use dev->rawdescriptors[] which has wTotalLength.
If no, the comment of 9 explanation will be useful here.
> +
> + *ctx_size += 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 == NULL || dev == NULL ||
> + dev->port_is_suspended == 1 ||
> + dev->reset_in_progress == 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 = 0;
> +
> + uint8_t is_valid = 0;
> + uint8_t is_known = 0;
> + uint8_t is_blocked = 0;
> + uint8_t chain_nb = 0;
> + uint8_t slot_mask = 0;
> + uint8_t slot = 0;
> + uint8_t digests[256] = { 0 };
> + uint8_t nonce[32] = {0};
> + uint8_t chall[204] = {0};
> + uint32_t dev_id = 0;
> + size_t ctx_size = 0;
> + int i = 0;
> +
> + uint8_t *cert_der = NULL;
> + size_t cert_len = 0;
> +
> + if (unlikely(dev == NULL || dev->parent == NULL))
> + return -ENODEV;
> +
> + struct usb_device *hub = dev->parent;
> +
> + // By default set authorization status at false
> + dev->authorized = 0;
> + dev->authenticated = 0;
> +
> + uint8_t *buffer = NULL;
> + // Buffer to hold responses
> + buffer = 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 = usb_authent_req_digest(dev, buffer, digests, &slot_mask);
> + if (ret != 0) {
> + pr_err("failed to get digest: %d\n", ret);
> + goto cleanup;
> + }
> + pr_notice("received digest\n");
> +
> + usb_unlock_device(hub);
> + ret = usb_policy_engine_check_digest(dev->route, digests, slot_mask,
> + &is_known, &is_blocked, &dev_id);
> + if (ret != 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 = usb_auth_try_resume(dev, hub);
> + if (unlikely(ret != 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 = 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 = 0; i < 8; i++) {
> + // Test if slot contains a certificate chain
> + if (1 == ((slot_mask >> i) & 1)) {
> + ret = usb_authent_read_certificate(dev, buffer,
> + chain_nb,
> + &cert_der,
> + &cert_len);
> + if (ret != 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 = usb_policy_engine_check_cert_chain(
> + dev->route, digests + i * 32, cert_der,
> + cert_len, &is_valid, &is_blocked,
> + &dev_id);
> + if (ret != 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 = usb_auth_try_resume(dev, hub);
> + if (unlikely(ret != 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 = i;
> + break;
> + } else if (is_valid && is_blocked) {
> + // Found a valid and unauthorized certificate,
> + // reject device
> + ret = 0;
> + goto cleanup;
> + }
> + }
> + }
> + } else {
> + // Pick a slot among the valid ones, take first one
> + for (i = 0; i < 8; i++) {
> + if (1 == ((is_known >> i) & 1)) {
> + slot = i;
> + break;
> + }
> + }
> + }
> +
> + /*
> + * Authenticate the device with a challenge request
> + */
> + // Obtain a nonce for the challenge
> + usb_unlock_device(hub);
> + ret = usb_policy_engine_generate_challenge(dev_id, nonce);
> + if (ret != 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 = usb_auth_try_resume(dev, hub);
> + if (unlikely(ret != 0)) {
> + pr_err("failed to resume: %d\n", ret);
> + goto cleanup;
> + }
> +
> + pr_info("resuming\n");
> +
> + // Send a challenge request
> + ret = usb_authent_challenge_dev(dev, buffer, slot, slot_mask, nonce,
> + chall);
> + if (ret != 0) {
> + pr_err("failed to challenge device: %d\n", ret);
> + goto cleanup;
> + }
> + pr_notice("validated challenge\n");
> +
> + // Create device context
> + ret = usb_auth_create_dev_ctx(dev, buffer, 512, &ctx_size);
> + if (ret != 0) {
> + pr_err("failed to create context: %d\n", ret);
> + goto cleanup;
> + }
> +
> + // Validate the challenge
> + usb_unlock_device(hub);
> + ret = usb_policy_engine_check_challenge(dev_id, chall, buffer, ctx_size,
> + &is_valid);
> + if (ret != 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 = usb_auth_try_resume(dev, hub);
> + if (unlikely(ret != 0)) {
> + pr_err("failed to resume: %d\n", ret);
> + goto cleanup;
> + }
> +
> + pr_info("resuming\n");
> +
> + // Apply authorization decision
> + if (is_valid) {
> + dev->authorized = 1;
> + dev->authenticated = 1;
> + }
> +
> + ret = 0;
> +
> +cleanup:
> + kfree(buffer);
> + kfree(cert_der);
> +
> + return 0;
In every early-error path `ret` is set but thrown away here.
Enumeration thus always continues and hub believes the device is authenticated.
Please return `ret`.
> +}
> diff --git a/drivers/usb/core/authent.h b/drivers/usb/core/authent.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..c3852636dbcea9150ed1663769e2a7b6348f528c
> --- /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 <luc.bonnafoux@ssi.gouv.fr>
> + * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> + *
> + */
> +
> +#ifndef __USB_CORE_AUTHENT_H_
> +#define __USB_CORE_AUTHENT_H_
> +
> +#include <linux/types.h>
> +#include <linux/usb.h>
> +#include <linux/usb/ch11.h>
> +#include <linux/usb/hcd.h>
> +
> +/* 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_VERSION */
> + __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 */
nit: shall we follow camelcase as in spec or keep Linux underscore style?
> + __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 endian*/
> + __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 digests */
> + __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_ */
>
> --
> 2.50.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 3/4] usb: core: Plug the usb authentication capability
2025-06-20 14:27 ` [RFC PATCH 3/4] usb: core: Plug the usb authentication capability nicolas.bouchinet
2025-06-20 19:11 ` Alan Stern
@ 2025-06-21 11:09 ` Sabyrzhan Tasbolatov
2025-06-30 12:25 ` Nicolas Bouchinet
2025-06-23 18:15 ` Oliver Neukum
2 siblings, 1 reply; 24+ messages in thread
From: Sabyrzhan Tasbolatov @ 2025-06-21 11:09 UTC (permalink / raw)
To: nicolas.bouchinet
Cc: Greg Kroah-Hartman, Alan Stern, Kannappan R, Krzysztof Kozlowski,
Stefan Eichenberger, Thomas Gleixner, Pawel Laszczak, Ma Ke,
Jeff Johnson, Luc Bonnafoux, Luc Bonnafoux, Nicolas Bouchinet,
linux-kernel, linux-usb
On Fri, Jun 20, 2025 at 7:27 PM <nicolas.bouchinet@oss.cyber.gouv.fr> wrote:
>
> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>
> 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 functions.
> The usb_device also contains an authenticated field that could be used to track
> 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 <luc.bonnafoux@ssi.gouv.fr>
> Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> ---
> 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..45ee8e93e263c41f1bf4271be4e69ccfcac3f650 100644
> --- a/drivers/usb/core/config.c
> +++ b/drivers/usb/core/config.c
> @@ -14,6 +14,7 @@
> #include <asm/byteorder.h>
> #include "usb.h"
>
> +#include "authent.h"
>
> #define USB_MAXALTSETTING 128 /* Hard limit */
>
> @@ -824,7 +825,50 @@ static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
> kref_init(&intfc->ref);
> }
>
> - /* FIXME: parse the BOS descriptor */
> + /* If device USB version is above 2.0, get BOS descriptor */
> + /*
> + * Requirement for bcdUSB >= 2.10 is defined in USB 3.2 §9.2.6.6
> + * "Devices with a value of at least 0210H in the bcdUSB field of their
> + * device descriptor shall support GetDescriptor (BOS Descriptor) requests."
> + *
> + * To discuss, BOS request could be also sent for bcdUSB >= 0x0201
> + */
> + // Set a default value for authenticated at true in order not to block devices
> + // that do not support the authentication
> + dev->authenticated = 1;
> +
> + if (le16_to_cpu(dev->descriptor.bcdUSB) >= 0x0201) {
> + pr_notice("bcdUSB >= 0x0201\n");
> + retval = 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 = usb_authenticate_device(dev);
> + if (retval != 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;
Returning 0 after rejecting a device leaves udev half-initialised and still
linked in usb_bus_type (the hub driver only aborts after this call).
The next hot-plug may oops on dangling pointers.
Please consider returning -EPERM instead of 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");
> + }
> + }
>
> /* 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 = bos->bLength;
> total_len = le16_to_cpu(bos->wTotalLength);
> num = 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 =
> (struct usb_ptm_cap_descriptor *)buffer;
> break;
> + case USB_AUTHENT_CAP_TYPE:
> + dev->bos->authent_cap =
> + (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..753e55155ea34a7739b5f530dad429534e60842d 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 = MKDEV(USB_DEVICE_MAJOR,
> (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
>
> + // 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);
>
> diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
> index 0b4685aad2d50337f3cacb2198c95a68ae8eff86..76847c01d3493e2527992a3bb927422519d9a974 100644
> --- a/drivers/usb/core/usb.c
> +++ b/drivers/usb/core/usb.c
> @@ -46,6 +46,7 @@
> #include <linux/dma-mapping.h>
>
> #include "hub.h"
> +#include "authent_netlink.h"
>
> const char *usbcore_name = "usbcore";
>
> @@ -1080,6 +1081,10 @@ static int __init usb_init(void)
> usb_debugfs_init();
>
> usb_acpi_register();
> +
> + // TODO : check error case
> + usb_auth_init_netlink();
> +
> retval = 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..e9037c8120b43556f8610f9acb3ad4129e847b98 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;
> };
>
> int __usb_get_extra_descriptor(char *buffer, unsigned size,
>
> --
> 2.50.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 3/4] usb: core: Plug the usb authentication capability
2025-06-20 14:27 ` [RFC PATCH 3/4] usb: core: Plug the usb authentication capability nicolas.bouchinet
2025-06-20 19:11 ` Alan Stern
2025-06-21 11:09 ` Sabyrzhan Tasbolatov
@ 2025-06-23 18:15 ` Oliver Neukum
2025-06-30 12:34 ` Nicolas Bouchinet
2 siblings, 1 reply; 24+ messages in thread
From: Oliver Neukum @ 2025-06-23 18:15 UTC (permalink / raw)
To: nicolas.bouchinet, 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, linux-usb
Hi,
I am afraid someone has to address this.
On 20.06.25 16:27, nicolas.bouchinet@oss.cyber.gouv.fr wrote:
> + // Set a default value for authenticated at true in order not to block devices
> + // that do not support the authentication
> + dev->authenticated = 1;
So the default is authenticated. OK.
> + if (le16_to_cpu(dev->descriptor.bcdUSB) >= 0x0201) {
> + pr_notice("bcdUSB >= 0x0201\n");
> + retval = usb_get_bos_descriptor(dev);
> + if (!retval) {
> + pr_notice("found BOS\n");
> +#ifdef CONFIG_USB_AUTHENTICATION
> + if (dev->bos->authent_cap) {
If the device claims not to support authentication ...
> + /* If authentication cap is present, start device authent */
> + pr_notice("found Authent BOS\n");
> + retval = usb_authenticate_device(dev);
> + if (retval != 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");
... we do nothing about it. We enumerate.
The purpose of authentication is guarding against unknown or malicious devices,
isn't it? This behavior seems to be kind of incompatible with the goal.
Regards
Oliver
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/4] usb: core: Introduce usb authentication feature
2025-06-20 14:27 ` [RFC PATCH 2/4] usb: core: Introduce usb authentication feature nicolas.bouchinet
2025-06-20 14:54 ` Greg Kroah-Hartman
2025-06-21 10:21 ` Sabyrzhan Tasbolatov
@ 2025-06-25 9:59 ` Oliver Neukum
2025-06-30 12:38 ` Nicolas Bouchinet
2 siblings, 1 reply; 24+ messages in thread
From: Oliver Neukum @ 2025-06-25 9:59 UTC (permalink / raw)
To: nicolas.bouchinet, 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, linux-usb
On 20.06.25 16:27, nicolas.bouchinet@oss.cyber.gouv.fr wrote:
> +/**
> + * 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 buffer,
How can buffer be const if it is used for output?
[..]
> +struct usb_auth_cert_req {
> + uint16_t offset;
> + uint16_t length;
> +} __packed;
Endianness?
> +/**
> + * 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 *const buffer,
> + uint8_t slot, uint8_t **cert_der, size_t *cert_len)
> +{
> + uint16_t read_offset = 0;
> + uint16_t read_length = 0;
> + uint8_t chain_part[64] = { 0 };
> +
> + if (unlikely(slot >= 8 || buffer == NULL || cert_der == NULL || cert_len == 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) != 0) {
> + pr_err("Failed to get first certificate part\n");
> + return -ECOMM;
> + }
> +
> + // Extract total length
> + *cert_len = ((uint16_t *)chain_part)[0];
Endianness
> +
> +/**
> + * 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 allocated
> + *
> + * 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 *buffer,
> + const uint8_t slot, const uint8_t slot_mask, const uint8_t *const nonce,
> + uint8_t *const chall)
> +{
> + int ret = -1;
> +
> + if (unlikely(buffer == NULL || slot >= 8 || nonce == NULL)) {
> + pr_err("invalid arguments\n");
> + return -EINVAL;
> + }
> +
> + // AUTH OUT challenge request transfer
> + memcpy(buffer, nonce, 32);
> + ret = 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 = -ECOMM;
> + goto cleanup;
> + }
> +
> + // Complete the challenge with the request
> + chall[1] = USB_SECURITY_PROTOCOL_VERSION;
> + chall[0] = USB_AUTHENT_CHALLENGE_REQ_TYPE;
> + chall[2] = slot;
> + chall[3] = 0x00;
> + memcpy(chall+4, nonce, 32);
This may be worth a definition.
> +
> + // AUTH IN challenge response transfer
> + ret = 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 = -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);
The 168 comes whence?
> +
> + ret = 0;
> +
> +cleanup:
> +
> + return ret;
> +}
> +/**
> + * @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 == NULL || dev == NULL ||
> + dev->port_is_suspended == 1 ||
> + dev->reset_in_progress == 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 = 0;
> +
> + uint8_t is_valid = 0;
> + uint8_t is_known = 0;
> + uint8_t is_blocked = 0;
> + uint8_t chain_nb = 0;
> + uint8_t slot_mask = 0;
> + uint8_t slot = 0;
> + uint8_t digests[256] = { 0 };
> + uint8_t nonce[32] = {0};
> + uint8_t chall[204] = {0};
> + uint32_t dev_id = 0;
> + size_t ctx_size = 0;
> + int i = 0;
> +
> + uint8_t *cert_der = NULL;
> + size_t cert_len = 0;
> +
> + if (unlikely(dev == NULL || dev->parent == NULL))
> + return -ENODEV;
> +
> + struct usb_device *hub = dev->parent;
> +
> + // By default set authorization status at false
> + dev->authorized = 0;
> + dev->authenticated = 0;
> +
> + uint8_t *buffer = NULL;
> + // Buffer to hold responses
> + buffer = kzalloc(512, GFP_KERNEL);
Should this not be cached for comparison after resume?
Regards
Oliver
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/4] usb: core: Introduce usb authentication feature
2025-06-20 14:54 ` Greg Kroah-Hartman
@ 2025-06-30 11:07 ` Nicolas Bouchinet
2025-06-30 11:43 ` Greg Kroah-Hartman
0 siblings, 1 reply; 24+ messages in thread
From: Nicolas Bouchinet @ 2025-06-30 11:07 UTC (permalink / raw)
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, linux-usb
Hi Greg,
Thank you very much for your review. We will take every style remarks into
account in the next patch version. Other responses are inline (there is
only one):
On 6/20/25 16:54, Greg Kroah-Hartman wrote:
> First off, thanks so much for doing this work, I've been wondering if
> anyone would ever do it :)
>
> Just a few quick review comments that you might want to do for the next
> round of your patches for some basic style things:
>
> On Fri, Jun 20, 2025 at 04:27:17PM +0200, nicolas.bouchinet@oss.cyber.gouv.fr wrote:
>> +#include <linux/types.h>
>> +#include <linux/usb.h>
>> +#include <linux/usb/ch9.h>
>> +#include <linux/usb/hcd.h>
>> +#include <linux/usb/quirks.h>
>> +#include <linux/module.h>
>> +#include <linux/slab.h>
>> +#include <linux/device.h>
>> +#include <linux/delay.h>
>> +#include <asm/byteorder.h>
>> +
>> +#include "authent_netlink.h"
>> +
>> +#include "authent.h"
> No need for the blank lines there.
>
>> +static int usb_authent_req_digest(struct usb_device *dev, uint8_t *const buffer,
>> + uint8_t digest[256], uint8_t *mask)
>> +{
>> + int ret = 0;
>> + struct usb_authent_digest_resp *digest_resp = NULL;
>> +
>> + if (unlikely((buffer == NULL || mask == NULL))) {
>> + pr_err("invalid arguments\n");
>> + return -EINVAL;
>> + }
>> + ret = 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 = -ECOMM;
>> + goto exit;
>> + }
>> +
>> + // Parse received digest response
>> + digest_resp = (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);
> Always use the dev_*() macros instead of pr_*() ones as that way you
> know what device is making the message please.
>
>> +
>> + *mask = digest_resp->slotMask;
>> + memcpy(digest, digest_resp->digests, 256);
>> +
>> + ret = 0;
>> +
>> +exit:
>> +
>> + return ret;
>> +}
>> +
>> +struct usb_auth_cert_req {
>> + uint16_t offset;
>> + uint16_t length;
>> +} __packed;
> Kernel types are u16, not uint16_t. The uint*_t types are from
> userspace C code, not kernel code. Yes, they are slowly sliding in in
> places, but let's not do that unless really required for some specific
> reason.
>
> And why packed?
Since this structure is part of the usb authentication protocol, we need
to be
sure it is sent as is on the usb bus.
>
> And what about endian issues, the data from the devices should be in a
> specific format, right?
>
>
>> +
>> +/**
>> + * @brief Request a specific part of a certificate chain from the device
> Are you sure this is proper kerneldoc style? Does this build properly?
>
>> + *
>> + * 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 allocated
>> + * @param [in] slot : slot in which to read the certificate
>> + * @param [in] offset : at which the certificate fragment must be read
>> + * @param [in] length : of the certificate fragment to read
>> + * @param [out] cert_part : buffer to hold the fragment, caller allocated
> Again, I don't think this is kerneldoc format. Please build the kernel
> documentation output and see what this results in.
>
>> + *
>> + * @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 = { 0 };
>> + int ret = -1;
> Use real error values, not random integers :)
>
>> +
>> + if (unlikely(buffer == NULL || cert_part == NULL)) {
> Only use likely/unlikely if you can measure the speed difference. For
> USB, and probe sequences, that will never be the casae.
>
>> + pr_err("invalid argument\n");
> Again, dev_err()?
>
> But how can these parameters not be set properly? You control how they
> are called, no need to always verify that you wrote the code properly :)
>
>> + return -EINVAL;
>> + }
>> +
>> + cert_req.offset = offset;
>> + cert_req.length = length;
>> +
>> + // AUTH OUT request transfer
>> + memcpy(buffer, &cert_req, sizeof(struct usb_auth_cert_req));
>> + ret = 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 = -ECOMM;
>> + goto cleanup;
>> + }
>> +
>> + // AUTH IN certificate read
>> + ret = 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 = -ECOMM;
>> + goto cleanup;
>> + }
>> +
>> + // TODO: parse received header
>> + memcpy(cert_part, buffer + 4, length);
>> +
>> + ret = 0;
>> +
>> +cleanup:
>> +
>> + return ret;
> As "cleanup:" does nothing, no need for it, just return the error value
> above when you were doing a goto line.
>
>> +}
>> +
>> +/**
>> + * 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 *const buffer,
>> + uint8_t slot, uint8_t **cert_der, size_t *cert_len)
>> +{
>> + uint16_t read_offset = 0;
>> + uint16_t read_length = 0;
>> + uint8_t chain_part[64] = { 0 };
> Again, u16 and u8 please.
>
> thanks,
>
> greg k-h
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 3/4] usb: core: Plug the usb authentication capability
2025-06-20 19:11 ` Alan Stern
@ 2025-06-30 11:20 ` Nicolas Bouchinet
2025-06-30 18:04 ` Alan Stern
0 siblings, 1 reply; 24+ messages in thread
From: Nicolas Bouchinet @ 2025-06-30 11:20 UTC (permalink / raw)
To: Alan Stern
Cc: Greg Kroah-Hartman, Kannappan R, Sabyrzhan Tasbolatov,
Krzysztof Kozlowski, Stefan Eichenberger, Thomas Gleixner,
Pawel Laszczak, Ma Ke, Jeff Johnson, Luc Bonnafoux, Luc Bonnafoux,
Nicolas Bouchinet, linux-kernel, linux-usb
Hi Alan,
Thank you very much for your review. We will take every form remarks into
account in the next patch version.
On 6/20/25 21:11, Alan Stern wrote:
> On Fri, Jun 20, 2025 at 04:27:18PM +0200, nicolas.bouchinet@oss.cyber.gouv.fr wrote:
>> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>>
>> 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 functions.
>> The usb_device also contains an authenticated field that could be used to track
>> 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 <luc.bonnafoux@ssi.gouv.fr>
>> Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
>> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>> ---
> Here are some more stylistic suggestions, similar to what Greg said.
>
>> @@ -824,7 +825,50 @@ static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
>> kref_init(&intfc->ref);
>> }
>>
>> - /* FIXME: parse the BOS descriptor */
>> + /* If device USB version is above 2.0, get BOS descriptor */
>> + /*
>> + * Requirement for bcdUSB >= 2.10 is defined in USB 3.2 §9.2.6.6
>> + * "Devices with a value of at least 0210H in the bcdUSB field of their
>> + * device descriptor shall support GetDescriptor (BOS Descriptor) requests."
>> + *
>> + * To discuss, BOS request could be also sent for bcdUSB >= 0x0201
>> + */
>> + // Set a default value for authenticated at true in order not to block devices
>> + // that do not support the authentication
> It looks really weird to see three comment blocks, in three different
> styles, right next to each other. In the kernel, we avoid C++-style //
> comments. And two adjacent /**/-style comments would normally be
> separated by a blank line or just merged into one bigger comment.
>
>> + dev->authenticated = 1;
>> +
>> + if (le16_to_cpu(dev->descriptor.bcdUSB) >= 0x0201) {
>> + pr_notice("bcdUSB >= 0x0201\n");
>> + retval = 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 = usb_authenticate_device(dev);
>> + if (retval != 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;
> Do these two cases need to be handled separately? Regardless of whether
> the function call fails, or succeeds but gives a negative result,
> shouldn't the end result be the same?
>
>> + } else {
>> + pr_notice("device has been authorized\n");
>> + }
> Be careful not to mix up the two separate notions of authentication and
> authorization. It's already difficult to keep them straight, because
> the words are so similar.
You are right indeed.
We moved the `usb_authenticate_dev()` call in `usb_new_device()` in
order to
perform the authentication only once the device configuration is
complete. Also
we think we need to split the problem of handling the authentication vs
authorization in two parts.
- which component has authority to set the two fields ?
- where/how is it enforced ?
To answer the first question :
- We think that the authenticated field can only be set by the
`usb_authenticate_dev()` function.
- it is less clear for the authorized status which is already
manipulated by
the sysfs (usbguard) and the default hcd policy.
The reconciliation between the two fields could be done at the enforcement
point. In `usb_probe_interface()` instead of simply checking the
authorized flag
it could check a more complex policy. For example:
+-------------------+----------------------------------------+----------------+
| | authorized | not
authorized |
+-------------------+----------------------------------------+----------------+
| authenticated | OK | NOK
|
+-------------------+----------------------------------------+----------------+
| not authenticated | Depends on tolerance in local security
| |
| | policy (set by cmdline or sysctl) | NOK
|
+-------------------+----------------------------------------+----------------+
This way it would also help to handle internal devices. When
`hcd->dev_policy` is
set to USB_DEVICE_AUTHORIZE_INTERNAL, only internal devices are
authorized by
default on connection. So external devices will have to be authenticated
and
then authorized via the sysfs. Internal devices will be authorized and not
authenticated.
>
>> + } else {
>> + // USB authentication unsupported
>> + // Apply security policy on failed devices
>> + pr_notice("no authentication capability\n");
>> + }
>> +#endif
> We generally prefer to avoid #if or #ifdef blocks inside function
> definitions, if at all possible. In this case, you could define a
> separate function usb_handle_bos_authent() (or whatever you want to call
> it) that does this work, all inside a #ifdef block, along with a #else
> section that defines usb_handle_bos_authent() to be an inline empty
> function.
>
>> + } else {
>> + // Older USB version, authentication not supported
>> + // Apply security policy on failed devices
>> + pr_notice("device does not have a BOS descriptor\n");
>> + }
>> + }
>>
>> /* 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 = bos->bLength;
>> total_len = le16_to_cpu(bos->wTotalLength);
>> num = bos->bNumDeviceCaps;
>> +
> Patches shouldn't make extraneous whitespace changes to existing code.
>
>> kfree(bos);
>> if (total_len < length)
>> return -EINVAL;
>> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
>> index 0e1dd6ef60a719f59a22d86caeb20f86991b5b29..753e55155ea34a7739b5f530dad429534e60842d 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 = MKDEV(USB_DEVICE_MAJOR,
>> (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
>>
>> + // 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;
> A comment that simply repeats what the code already says is not very
> useful. Such comments do exist here and there (I'm guilty of adding
> some of them myself), but in general they should be avoided.
>
>> diff --git a/include/linux/usb.h b/include/linux/usb.h
>> index b46738701f8dc46085f251379873b6a8a008d99d..e9037c8120b43556f8610f9acb3ad4129e847b98 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;
> None of the other entries here have a comment like this. Why does the
> new entry need one?
>
> Alan Stern
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 4/4] usb: core: Add Kconfig option to compile usb authorization
2025-06-21 7:22 ` Greg Kroah-Hartman
@ 2025-06-30 11:22 ` Nicolas Bouchinet
0 siblings, 0 replies; 24+ messages in thread
From: Nicolas Bouchinet @ 2025-06-30 11:22 UTC (permalink / raw)
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, linux-usb
On 6/21/25 09:22, Greg Kroah-Hartman wrote:
> On Fri, Jun 20, 2025 at 04:27:19PM +0200, nicolas.bouchinet@oss.cyber.gouv.fr wrote:
>> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>>
>> This enables the usb authentication protocol implementation.
>>
>> Co-developed-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
>> Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
>> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>> ---
>> 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..07ba67137b7fe16ecb1e993a51dbbfd4dd3ada88 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.
>>
>> If unsure, keep the default value.
>> +
>> +config USB_AUTHENTICATION
>> + bool "Enable USB authentication function"
>> + default n
> Nit, "default n" is the default, no need to ever list it.
Done, fixes will be sent in the next patch version.
>
> thanks,
>
> greg k-h
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 1/4] usb: core: Introduce netlink usb authentication policy engine
2025-06-21 9:37 ` Sabyrzhan Tasbolatov
@ 2025-06-30 11:42 ` Nicolas Bouchinet
0 siblings, 0 replies; 24+ messages in thread
From: Nicolas Bouchinet @ 2025-06-30 11:42 UTC (permalink / raw)
To: Sabyrzhan Tasbolatov
Cc: Greg Kroah-Hartman, Alan Stern, Kannappan R, Krzysztof Kozlowski,
Stefan Eichenberger, Thomas Gleixner, Pawel Laszczak, Ma Ke,
Jeff Johnson, Luc Bonnafoux, Luc Bonnafoux, Nicolas Bouchinet,
linux-kernel, linux-usb
Hi Sabyrzhan,
Thank you very much for your review.
On 6/21/25 11:37, Sabyrzhan Tasbolatov wrote:
> On Fri, Jun 20, 2025 at 7:27 PM <nicolas.bouchinet@oss.cyber.gouv.fr> wrote:
>> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>>
>> 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 <luc.bonnafoux@ssi.gouv.fr>
>> Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
>> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>> ---
>> 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..d53a220c762ffc1bd9aeb95bf90dc0dd79c43f09
>> --- /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 <luc.bonnafoux@ssi.gouv.fr>
>> + * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>> + *
>> + */
>> +
>> +#include <linux/sched.h>
>> +#include <linux/time.h>
>> +#include <linux/delay.h>
>> +#include <linux/slab.h>
>> +#include <linux/mutex.h>
>> +#include <linux/err.h>
>> +#include <linux/capability.h>
>> +
>> +#include <net/genetlink.h>
>> +
>> +#include <uapi/linux/usb/usb_auth_netlink.h>
>> +
>> +#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 protected */
>> + uint8_t done; /**< 1 if the response has been received, used as wait condition */
>> + 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_OUTSTANDING_REQS];
> Wonder, if hot-plugged low-speed hubs may exhaust the table and block every
> other device for 5 min (according to the timeouts in this patch).
>
> With the array hard-capped at 10, the 11-th concurrent device gets -EXFULL
> and stalls enumeration. The capacity must become dynamic (e.g. xarray/idr)
> or requests must be queued instead of rejected (?). IDR is deprecated,
> though is used in drivers/usb.
We want to avoid a rogue device being able to spam creation of USB
instantiation and thus DOSing the system. We think that a static size should
suffice, we might chose a better size though.
USB 2.0 specification 11.23.2.1 Hub Descriptor Table 11-13 Offset 7
description
defines a USB hubs can have up to a maximum of 255 ports.
The Linux kernel `include/uapi/linux/usb/ch11.h` however defines the
following
limit :
```C
/* This is arbitrary.
* From USB 2.0 spec Table 11-13, offset 7, a hub can
* have up to 255 ports. The most yet reported is 10.
* Upcoming hardware might raise that limit.
* Because the arrays need to add a bit for hub status data, we
* use 31, so plus one evens out to four bytes.
*/
#define USB_MAXCHILDREN 31
/* See USB 3.1 spec Table 10-5 */
#define USB_SS_MAXPORTS 15
```
We could either follow specification blindly or adapt to the Linux
kernel limits.
What's your opinion on this ?
>
>> +
>> +////////////////////////////////////////////////////////////////////////////////
>> +// 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 = -EXFULL;
>> + uint32_t i = 0;
>> +
>> + mutex_lock(&usb_auth_reqs_mutex);
>> +
>> + // Take the first available slot
>> + for (i = 0; i < USB_AUTH_MAX_OUTSTANDING_REQS; i++) {
>> + if (usb_auth_outstanding_reqs[i].used == 0) {
>> + usb_auth_outstanding_reqs[i].used = 1;
>> + usb_auth_outstanding_reqs[i].done = 0;
>> + usb_auth_outstanding_reqs[i].error = USBAUTH_OK;
>> + memset(usb_auth_outstanding_reqs[i].resp, 0,
>> + USB_AUTH_MAX_RESP_SIZE);
>> + *index = i;
>> + ret = 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 = 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 provided
>> + * in the request
>> + */
>> +static int usb_auth_register_req_doit(struct sk_buff *skb, struct genl_info *info)
>> +{
>> + int ret = 0;
>> + void *hdr = NULL;
>> + struct sk_buff *msg = 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 = info->snd_portid;
> info->snd_portid is the per-net-ns socket port ID of the sender.
> Any later caller, inside the same or a different network namespace
> simply replaces the old value. Can we reject the second registration?
We could indeed reject multiple registration attempts but it would be
problematic in case of userspace policy engine crash or restrat.
We would need it to be able to register again.
One way to fix it would be to detect if the policy_engine process is
unresponsive (because it died or is stalled) in which case we could
de-register
it and signal userspace. In this scenario, a new policy engine or the
stalled
one would have to register again.
>
> capable(CAP_SYS_ADMIN) succeeds in any user-namespace where the task
> has that bit set. A container root therefore passes the check and can
> issue USBAUTH_CMD_REGISTER, hijacking the channel.
>
>> + pol_eng_net = genl_info_net(info);
> Similarly, if that sender lives in a container’s net-namespace, pol_eng_net
> now points there.
IIUC, since the capable function checks for the capability against the
initial
user-ns, there is no way to hijack it with a root user run from an
unprivileged
user-ns. However a root user-ns attached to his own set of net-ns could
be able
to talk to the netlink socket (running a container with privileged
docker for
example). In this scenario, we could only allow root net-ns to
communicate over
the netlink socket. Which is a bit weird since usb stack does not have
much to
do with network.
We however changed the capability check with `netlink_capable()` instead. It
not only checks the current calling thread has the CAP_SYS_ADMIN in the root
user-ns but also checks the capability of the socket file opener at file
opening time.
>
>> +
>> + wake_up_all(&usb_req_wq);
>> +
>> + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
>> + if (msg == NULL) {
>> + pr_err("failed to allocate message buffer\n");
>> + return -ENOMEM;
>> + }
>> +
>> + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
>> + &usbauth_genl_fam, 0, USBAUTH_CMD_REGISTER);
>> + if (hdr == NULL) {
>> + pr_err("failed to create genetlink header\n");
>> + nlmsg_free(msg);
>> + return -EMSGSIZE;
>> + }
>> +
>> + genlmsg_end(msg, hdr);
>> +
>> + ret = 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 = 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 = 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 = USBAUTH_INVRESP;
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + usb_auth_outstanding_reqs[index].error =
>> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
> AFAIK, `usb_auth_outstanding_reqs` fields `done/error/resp[]` are not protected
> by the mutex that guards used. `usb_auth_reqs_mutex` is not held in
> either place.
> Potential race between any *_resp_doit() writer and the reader
> in the matching usb_policy_engine_*() helper.
>
> Perhaps, you can replace `error, resp[], done` by a single
> `struct completion`, plus a private response struct that the reader accesses
> only after some `complete()`.
This is indeed a critical section.
The main risk of race is during the reservation of a slot. The `used`
attribute
behave as a lock on the ownership of a request slot and as such must be
protected with a mutex.
Once a request slot has been locked (once `used` is set) for a
transaction it
is solely accessed by the `usb_policy_engine_*()` caller to setup the
transaction. When the transaction is sent to userspace, the request slot
can be
accessed by the `*_resp_doit()` callback to provide the response. The
ownership
to the slot is then given back to the caller once the `done` is set to 1.
Thus there is a sequential access to the request slot that should prevent
potential race. The main hypothesis that must be respected is that the
index of
the request slot is preserved between the functions in kernel and
userspace for
the length of the transaction.
If a userspace policy_engine does not respect the index it has been
assigned in
the request then it can use up all the requests slots. But even in this
case we
have include timeouts to prevent kernel DoS.
>
>> +
>> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
>> + pr_err("digest_resp_doit: response error\n");
>> + usb_auth_outstanding_reqs[index].done = 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 = USBAUTH_INVRESP;
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + usb_auth_outstanding_reqs[index].resp[0] =
>> + nla_get_u8(info->attrs[USBAUTH_A_KNOWN]);
>> + usb_auth_outstanding_reqs[index].resp[1] =
>> + nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]);
>> + ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0] =
>> + nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]);
>> +
>> + usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + usb_auth_outstanding_reqs[index].error =
>> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
>> +
>> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
>> + pr_err("response error\n");
>> + usb_auth_outstanding_reqs[index].done = 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 = 1;
>> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
>> + return -EINVAL;
>> + }
>> +
>> + usb_auth_outstanding_reqs[index].resp[0] =
>> + nla_get_u8(info->attrs[USBAUTH_A_VALID]);
>> + usb_auth_outstanding_reqs[index].resp[1] =
>> + nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]);
>> + ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0] =
>> + nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]);
>> +
>> + usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + usb_auth_outstanding_reqs[index].error =
>> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
>> +
>> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
>> + pr_err("response error\n");
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + if (!info->attrs[USBAUTH_A_VALID]) {
>> + pr_err("invalid response: missing attributes\n");
>> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + usb_auth_outstanding_reqs[index].resp[0] =
>> + nla_get_u8(info->attrs[USBAUTH_A_VALID]);
>> +
>> + usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + usb_auth_outstanding_reqs[index].error =
>> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
>> +
>> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
>> + pr_err("response error\n");
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + if (!info->attrs[USBAUTH_A_NONCE]) {
>> + pr_err("invalid response: missing attributes\n");
>> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + nla_memcpy(usb_auth_outstanding_reqs[index].resp, info->attrs[USBAUTH_A_NONCE], 32);
>> +
>> + usb_auth_outstanding_reqs[index].done = 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 = 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 = 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 = USBAUTH_INVRESP;
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + usb_auth_outstanding_reqs[index].error =
>> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
>> +
>> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
>> + pr_err("response error\n");
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + if (!info->attrs[USBAUTH_A_VALID]) {
>> + pr_err("invalid response: missing attributes\n");
>> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
>> + usb_auth_outstanding_reqs[index].done = 1;
>> + return -EINVAL;
>> + }
>> +
>> + usb_auth_outstanding_reqs[index].resp[0] =
>> + nla_get_u8(info->attrs[USBAUTH_A_VALID]);
>> +
>> + usb_auth_outstanding_reqs[index].done = 1;
>> +
>> + wake_up_all(&usb_req_wq);
>> +
>> + return 0;
>> +}
>> +
>> +/* Attribute validation policy */
>> +static struct nla_policy usbauth_attr_pol[USBAUTH_A_MAX + 1] = {
>> + [USBAUTH_A_REQ_ID] = {.type = NLA_U32,},
>> + [USBAUTH_A_DEV_ID] = {.type = NLA_U32,},
>> + [USBAUTH_A_DIGEST] = {.type = NLA_UNSPEC, .len = 32,},
>> + [USBAUTH_A_DIGESTS] = {.type = NLA_UNSPEC, .len = 256,},
>> + [USBAUTH_A_SLOT_MASK] = {.type = NLA_U8,},
>> + [USBAUTH_A_KNOWN] = {.type = NLA_U8,},
>> + [USBAUTH_A_BLOCKED] = {.type = NLA_U8,},
>> + [USBAUTH_A_VALID] = {.type = NLA_U8,},
>> + [USBAUTH_A_CERTIFICATE] = {.type = NLA_UNSPEC, .max = 4096,},
>> + [USBAUTH_A_CERT_LEN] = {.type = NLA_U32},
>> + [USBAUTH_A_ROUTE] = {.type = NLA_U32},
>> + [USBAUTH_A_NONCE] = {.type = NLA_BINARY, .len = 32,},
>> + [USBAUTH_A_CHALL] = {.type = NLA_UNSPEC, .len = 204,},
>> + [USBAUTH_A_DESCRIPTOR] = {.type = NLA_UNSPEC, .len = USBAUTH_MAX_DESC_SIZE},
>> + [USBAUTH_A_BOS] = {.type = NLA_UNSPEC, .len = USBAUTH_MAX_BOS_SIZE},
>> + [USBAUTH_A_ERROR_CODE] = {.type = NLA_U8},
>> +};
>> +
>> +/* Operations */
>> +static struct genl_ops usbauth_genl_ops[] = {
>> + {
>> + .cmd = USBAUTH_CMD_REGISTER,
>> + .policy = usbauth_attr_pol,
>> + .doit = usb_auth_register_req_doit,
>> + },
>> + {
>> + .cmd = USBAUTH_CMD_RESP_DIGEST,
>> + .policy = usbauth_attr_pol,
>> + .doit = usb_auth_digest_resp_doit,
>> + },
>> + {
>> + .cmd = USBAUTH_CMD_RESP_CERTIFICATE,
>> + .policy = usbauth_attr_pol,
>> + .doit = usb_auth_cert_resp_doit,
>> + },
>> + {
>> + .cmd = USBAUTH_CMD_RESP_REMOVE_DEV,
>> + .policy = usbauth_attr_pol,
>> + .doit = usb_auth_remove_dev_doit,
>> + },
>> + {
>> + .cmd = USBAUTH_CMD_RESP_GEN_NONCE,
>> + .policy = usbauth_attr_pol,
>> + .doit = usb_auth_gen_nonce_doit,
>> + },
>> + {
>> + .cmd = USBAUTH_CMD_RESP_CHECK_CHALL,
>> + .policy = usbauth_attr_pol,
>> + .doit = usb_auth_check_chall_doit,
>> + }
>> +};
>> +
>> +/* USB Authentication netlink family definition */
>> +static struct genl_family usbauth_genl_fam = {
>> + .name = USBAUTH_GENL_NAME,
>> + .version = USBAUTH_GENL_VERSION,
>> + .maxattr = USBAUTH_A_MAX,
>> + .ops = usbauth_genl_ops,
>> + .n_ops = ARRAY_SIZE(usbauth_genl_ops),
>> + .mcgrps = NULL,
>> + .n_mcgrps = 0,
>> +};
>> +
>> +int usb_auth_init_netlink(void)
>> +{
>> + int ret = 0;
>> + uint8_t i = 0;
>> +
>> + for (i = 0; i < USB_AUTH_MAX_OUTSTANDING_REQS; i++)
>> + usb_auth_outstanding_reqs[i].used = 0;
>> +
>> + init_waitqueue_head(&usb_req_wq);
>> +
>> + ret = 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 *const digests,
>> + const uint8_t mask, uint8_t *is_known, uint8_t *is_blocked, uint32_t *id)
>> +{
>> + int ret = -1;
>> + void *hdr = NULL;
>> + struct sk_buff *skb = NULL;
>> + uint32_t index = 0;
>> +
>> + if (digests == NULL) {
>> + pr_err("invalid inputs\n");
>> + return -EINVAL;
>> + }
>> +
>> + // Arbitrary 30s wait before giving up
>> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_TIMEOUT)) {
> WAIT_USERSPACE_TIMEOUT is 30 sec and WAIT_RESPONSE_TIMEOUT is 5 min.
> Can we keep a modest deadline, e.g. 5 sec for daemon presence and
> 10 sec for its reply, or something similar that makes sense?
The problem is that the userspace policy_engine can execute time consuming
actions as such as checking the validity of a certificate on a distant
server.
Hence we need pretty large timeouts. We could however make them configurable
using sysclts. We will think of better default values.
>
>> + 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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
>> + if (skb == NULL) {
>> + pr_err("failed to allocated buffer\n");
>> + return -ENOMEM;
>> + }
>> +
>> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
>> + USBAUTH_CMD_CHECK_DIGEST);
>> + if (unlikely(hdr == NULL)) {
>> + pr_err("failed to place header\n");
>> + nlmsg_free(skb);
>> + return -ENOMEM;
>> + }
>> +
>> + ret = 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 = 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 = nla_put(skb, USBAUTH_A_DIGESTS, 260, digests);
> Attribute is declared as 256 in the policy
> (usbauth_attr_pol[USBAUTH_A_DIGESTS].len = 256
> but you emit 260 here. Is it wrong or where do 4 bytes come from?
This was indeed a mistake. We will fix it in the next patch version.
>
>> + if (ret) {
>> + pr_err("failed to place digests\n");
>> + genlmsg_cancel(skb, hdr);
>> + nlmsg_free(skb);
>> + return ret;
>> + }
>> +
>> + ret = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
>> + if (ret != 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 == 1,
>> + HZ * WAIT_RESPONSE_TIMEOUT)) {
>> + pr_err("userspace response not available\n");
>> + usb_auth_release_reqs_slot(index);
> Here we have 5 min enumeration stall while holding usb_lock_device().
> Can we replace the blocking helper with a work-queue item?
> So we could have pushed to the work and continue enumeration.
>
> struct usb_auth_work {
> struct work_struct work;
> struct usb_device *udev;
> /* request-specific data */
> };
>
If we understood correctly your remark. You want to be able to perform the
authentication asynchronously while completing the device enumeration.
If it is
so, we think it is necessary to wait for the authentication decision before
exposing the usb device to userspace and we thus need to block the
enumeration
process.
>> + return -ECOMM;
>> + }
>> +
>> + pr_info("received CHECK_DIGEST response\n");
>> +
>> + // Get response
>> + if (usb_auth_outstanding_reqs[index].error == 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 = usb_auth_outstanding_reqs[index].resp[0];
>> + *is_blocked = usb_auth_outstanding_reqs[index].resp[1];
>> + *id = ((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 = -1;
>> + void *hdr = NULL;
>> + struct sk_buff *skb = NULL;
>> + uint32_t index = 0;
>> +
>> + if (chain == NULL || chain_len > 4096 || digest == NULL) {
>> + pr_err("invalid inputs\n");
>> + return -EINVAL;
>> + }
>> +
>> + // Arbitrary 30s wait before giving up
>> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
>> + if (skb == NULL) {
>> + pr_err("failed to allocated buffer\n");
>> + return -ENOMEM;
>> + }
>> +
>> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
>> + USBAUTH_CMD_CHECK_CERTIFICATE);
>> + if (unlikely(hdr == NULL)) {
>> + pr_err("failed to place header\n");
>> + nlmsg_free(skb);
>> + return -ENOMEM;
>> + }
>> +
>> + ret = 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 = 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 = 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 = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
>> + if (ret != 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 == 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 = usb_auth_outstanding_reqs[index].resp[0];
>> + *is_blocked = usb_auth_outstanding_reqs[index].resp[1];
>> + *id = ((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 = -1;
>> + void *hdr = NULL;
>> + struct sk_buff *skb = NULL;
>> + uint32_t index = 0;
>> +
>> + // Arbitrary 30s wait before giving up
>> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
>> + if (skb == NULL) {
>> + pr_err("failed to allocated buffer\n");
>> + return -ENOMEM;
>> + }
>> +
>> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
>> + USBAUTH_CMD_REMOVE_DEV);
>> + if (unlikely(hdr == NULL)) {
>> + pr_err("failed to place header\n");
>> + nlmsg_free(skb);
>> + return -ENOMEM;
>> + }
>> +
>> + ret = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
>> + if (ret != 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 == 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 = -1;
>> + void *hdr = NULL;
>> + struct sk_buff *skb = NULL;
>> + uint32_t index = 0;
>> +
>> + // Arbitrary 30s wait before giving up
>> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
>> + if (skb == NULL) {
>> + pr_err("failed to allocated buffer\n");
>> + return -ENOMEM;
>> + }
>> +
>> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
>> + USBAUTH_CMD_GEN_NONCE);
>> + if (unlikely(hdr == NULL)) {
>> + pr_err("failed to place header\n");
>> + nlmsg_free(skb);
>> + return -ENOMEM;
>> + }
>> +
>> + ret = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
>> + if (ret != 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 == 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 = -1;
>> + void *hdr = NULL;
>> + struct sk_buff *skb = NULL;
>> + uint32_t index = 0;
>> +
>> + if (unlikely(challenge == NULL || context == 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 != 0, HZ * WAIT_USERSPACE_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 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
>> + if (skb == NULL) {
>> + pr_err("failed to allocated buffer\n");
>> + return -ENOMEM;
>> + }
>> +
>> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
>> + USBAUTH_CMD_CHECK_CHALL);
>> + if (unlikely(hdr == NULL)) {
>> + pr_err("failed to place header\n");
>> + nlmsg_free(skb);
>> + return -ENOMEM;
>> + }
>> +
>> + ret = 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 = 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 = 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 = 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 = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
>> + if (ret != 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 == 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 = 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..504da32547b75b85b4328f3ea7df43b0a565dd18
>> --- /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 <luc.bonnafoux@ssi.gouv.fr>
>> + * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>> + *
>> + */
>> +
>> +#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 otherwise
>> + * @param [out] is_blocked : 1 if the device is known and banned, 0 otherwise
>> + * @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 one of the
>> + * root CA in store.
>> + * A certificate chain is blocked if one of the certificate of chain is blocked,
>> + * 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 bytes
>> + * @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 certificate is blocked
>> + * @param [out] id : if is_known and !is_blocked then contains the 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 size
>> + * - 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..e5b1e0e130a1ffb320aac4805161d579923a5b29
>> --- /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 <luc.bonnafoux@ssi.gouv.fr>
>> + * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>> + *
>> + */
>> +
>> +#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 = 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_ */
>>
>> --
>> 2.50.0
>>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/4] usb: core: Introduce usb authentication feature
2025-06-30 11:07 ` Nicolas Bouchinet
@ 2025-06-30 11:43 ` Greg Kroah-Hartman
0 siblings, 0 replies; 24+ messages in thread
From: Greg Kroah-Hartman @ 2025-06-30 11:43 UTC (permalink / raw)
To: Nicolas Bouchinet
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, linux-usb
On Mon, Jun 30, 2025 at 01:07:29PM +0200, Nicolas Bouchinet wrote:
> Hi Greg,
>
> Thank you very much for your review. We will take every style remarks into
> account in the next patch version. Other responses are inline (there is only
> one):
>
> On 6/20/25 16:54, Greg Kroah-Hartman wrote:
> > First off, thanks so much for doing this work, I've been wondering if
> > anyone would ever do it :)
> >
> > Just a few quick review comments that you might want to do for the next
> > round of your patches for some basic style things:
> >
> > On Fri, Jun 20, 2025 at 04:27:17PM +0200, nicolas.bouchinet@oss.cyber.gouv.fr wrote:
> > > +#include <linux/types.h>
> > > +#include <linux/usb.h>
> > > +#include <linux/usb/ch9.h>
> > > +#include <linux/usb/hcd.h>
> > > +#include <linux/usb/quirks.h>
> > > +#include <linux/module.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/device.h>
> > > +#include <linux/delay.h>
> > > +#include <asm/byteorder.h>
> > > +
> > > +#include "authent_netlink.h"
> > > +
> > > +#include "authent.h"
> > No need for the blank lines there.
> >
> > > +static int usb_authent_req_digest(struct usb_device *dev, uint8_t *const buffer,
> > > + uint8_t digest[256], uint8_t *mask)
> > > +{
> > > + int ret = 0;
> > > + struct usb_authent_digest_resp *digest_resp = NULL;
> > > +
> > > + if (unlikely((buffer == NULL || mask == NULL))) {
> > > + pr_err("invalid arguments\n");
> > > + return -EINVAL;
> > > + }
> > > + ret = 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 = -ECOMM;
> > > + goto exit;
> > > + }
> > > +
> > > + // Parse received digest response
> > > + digest_resp = (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);
> > Always use the dev_*() macros instead of pr_*() ones as that way you
> > know what device is making the message please.
> >
> > > +
> > > + *mask = digest_resp->slotMask;
> > > + memcpy(digest, digest_resp->digests, 256);
> > > +
> > > + ret = 0;
> > > +
> > > +exit:
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +struct usb_auth_cert_req {
> > > + uint16_t offset;
> > > + uint16_t length;
> > > +} __packed;
> > Kernel types are u16, not uint16_t. The uint*_t types are from
> > userspace C code, not kernel code. Yes, they are slowly sliding in in
> > places, but let's not do that unless really required for some specific
> > reason.
> >
> > And why packed?
>
> Since this structure is part of the usb authentication protocol, we need to
> be
> sure it is sent as is on the usb bus.
Great, then properly document it as such please. Ideally in a header
file as we have tried to document all the USB messages in ch9.h where
defined.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/4] usb: core: Introduce usb authentication feature
2025-06-21 10:21 ` Sabyrzhan Tasbolatov
@ 2025-06-30 11:56 ` Nicolas Bouchinet
0 siblings, 0 replies; 24+ messages in thread
From: Nicolas Bouchinet @ 2025-06-30 11:56 UTC (permalink / raw)
To: Sabyrzhan Tasbolatov
Cc: Greg Kroah-Hartman, Alan Stern, Kannappan R, Krzysztof Kozlowski,
Stefan Eichenberger, Thomas Gleixner, Pawel Laszczak, Ma Ke,
Jeff Johnson, Luc Bonnafoux, Luc Bonnafoux, Nicolas Bouchinet,
linux-kernel, linux-usb
On 6/21/25 12:21, Sabyrzhan Tasbolatov wrote:
> On Fri, Jun 20, 2025 at 7:27 PM <nicolas.bouchinet@oss.cyber.gouv.fr> wrote:
>> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>>
>> 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 <luc.bonnafoux@ssi.gouv.fr>
>> Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
>> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>> ---
>> 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..46f048d45a909e0fef504d71072eb7854320d271
>> --- /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 <luc.bonnafoux@ssi.gouv.fr>
>> + * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>> + *
>> + */
>> +
>> +#include <linux/types.h>
>> +#include <linux/usb.h>
>> +#include <linux/usb/ch9.h>
>> +#include <linux/usb/hcd.h>
>> +#include <linux/usb/quirks.h>
>> +#include <linux/module.h>
>> +#include <linux/slab.h>
>> +#include <linux/device.h>
>> +#include <linux/delay.h>
>> +#include <asm/byteorder.h>
>> +
>> +#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 buffer,
>> + uint8_t digest[256], uint8_t *mask)
>> +{
>> + int ret = 0;
>> + struct usb_authent_digest_resp *digest_resp = NULL;
>> +
>> + if (unlikely((buffer == NULL || mask == NULL))) {
>> + pr_err("invalid arguments\n");
>> + return -EINVAL;
>> + }
>> + ret = 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 = -ECOMM;
>> + goto exit;
>> + }
>> +
>> + // Parse received digest response
>> + digest_resp = (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 = digest_resp->slotMask;
> Wonder, if we might have a bug for BE hosts here.
> Spec says the slot bitmap is LE, for devices that set bit 7
> it will be mis-interpreted on BE.
>
> int usb_authenticate_device(struct usb_device *dev)
> {
> ...
> for (i = 0; i < 8; i++) {
> if (1 == ((slot_mask >> i) & 1)) { /* bit-test */
> /* use digest at digests[i * 32] */
> ...
> }
> }
> }
>
> That loop treats bit 0 as the LSB, so it does match the spec on both
> little- and big-endian hosts. There is no endian bug in the mask
> handling itself, AFAIU.
Indeed, we need to check the shift behavior on both architecture.
>
> What can bite us, however, is positional coupling between the mask and
> the digest array:
>
> The spec guarantees that digest k appears only if bit k is set and
> that digests are “returned in order of increasing slot number.”
>
> So if a device leaves gap(s) (e.g. slots 0 and 2 populated, slot 1
> empty) the firmware is allowed to pack the digests for 0 and 2
> contiguously. Our code assumes digest[k] is always at offset k*32,
> which fails in
> that sparse case.
From what we understand of the specification, the firmaware should not be
allowed to pack digests in the response that should always be 260 bytes.
It is indeed not specified what the value of digests for unused slots should
be. For reference :
- In Table 5-7 of USB Authentication Mapping Specification: GET_DIGESTS
Authentication IN Control Request Fields wLength is set to a fixed size
of 260.
- In Table 5-11 of USB Security Foundation Specification: DIGESTS Response
Payload digest offsets are given as 4 + (32 * (n-1))
>
>> + memcpy(digest, digest_resp->digests, 256);
>> +
>> + ret = 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 allocated
>> + * @param [in] slot : slot in which to read the certificate
>> + * @param [in] offset : at which the certificate fragment must be read
>> + * @param [in] length : of the certificate fragment to read
>> + * @param [out] cert_part : buffer to hold the fragment, caller allocated
>> + *
>> + * @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 = { 0 };
>> + int ret = -1;
>> +
>> + if (unlikely(buffer == NULL || cert_part == NULL)) {
>> + pr_err("invalid argument\n");
>> + return -EINVAL;
>> + }
>> +
>> + cert_req.offset = offset;
>> + cert_req.length = length;
>> +
>> + // AUTH OUT request transfer
>> + memcpy(buffer, &cert_req, sizeof(struct usb_auth_cert_req));
>> + ret = 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 = -ECOMM;
>> + goto cleanup;
>> + }
>> +
>> + // AUTH IN certificate read
>> + ret = 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 = -ECOMM;
>> + goto cleanup;
>> + }
>> +
>> + // TODO: parse received header
>> + memcpy(cert_part, buffer + 4, length);
>> +
>> + ret = 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 *const buffer,
>> + uint8_t slot, uint8_t **cert_der, size_t *cert_len)
>> +{
>> + uint16_t read_offset = 0;
>> + uint16_t read_length = 0;
>> + uint8_t chain_part[64] = { 0 };
>> +
>> + if (unlikely(slot >= 8 || buffer == NULL || cert_der == NULL || cert_len == 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) != 0) {
>> + pr_err("Failed to get first certificate part\n");
>> + return -ECOMM;
>> + }
>> +
>> + // Extract total length
>> + *cert_len = ((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 = 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 = USB_AUTH_CHAIN_HEADER_SIZE;
>> +
>> + while (read_offset < *cert_len) {
>> + read_length = (*cert_len - read_offset) >= 64 ? 64 : (*cert_len - read_offset);
>> +
>> + if (usb_auth_read_cert_part(dev, buffer, slot, read_offset,
>> + read_length, chain_part) != 0) {
>> + pr_err("USB AUTH: Failed to get certificate part\n");
>> + return -ECOMM;
>> + }
>> +
>> + memcpy(*cert_der + read_offset, chain_part, read_length);
>> +
>> + read_offset += 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 allocated
>> + *
>> + * 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 *buffer,
>> + const uint8_t slot, const uint8_t slot_mask, const uint8_t *const nonce,
>> + uint8_t *const chall)
>> +{
>> + int ret = -1;
>> +
>> + if (unlikely(buffer == NULL || slot >= 8 || nonce == NULL)) {
>> + pr_err("invalid arguments\n");
>> + return -EINVAL;
>> + }
>> +
>> + // AUTH OUT challenge request transfer
>> + memcpy(buffer, nonce, 32);
>> + ret = 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 = -ECOMM;
>> + goto cleanup;
>> + }
>> +
>> + // Complete the challenge with the request
>> + chall[1] = USB_SECURITY_PROTOCOL_VERSION;
>> + chall[0] = USB_AUTHENT_CHALLENGE_REQ_TYPE;
>> + chall[2] = slot;
>> + chall[3] = 0x00;
>> + memcpy(chall+4, nonce, 32);
>> +
>> + // AUTH IN challenge response transfer
>> + ret = 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 = -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 = 0;
>> +
>> +cleanup:
>> +
>> + return ret;
>> +}
>> +
>> +/**
>> + * @brief Create a device context according to USB Type-C Authentication Specification, 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 allocated
>> + * @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 = 0;
>> +
>> + if (unlikely(dev == NULL || ctx == NULL)) {
>> + pr_err("invalid inputs\n");
>> + return -EINVAL;
>> + }
>> +
>> + *ctx_size = 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 += (size_t) dev->descriptor.bLength;
>> +
>> + // Device BOS and capabilities
>> + if (unlikely(dev->bos == NULL || dev->bos->desc == NULL)) {
>> + pr_err("invalid BOS\n");
>> + return -EINVAL;
>> + }
>> +
>> + desc_size = 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 += desc_size;
>> +
>> + // Device configuration descriptor
>> + if (unlikely(dev->config == NULL)) {
>> + pr_err("invalid configuration\n");
>> + return -EINVAL;
>> + }
>> +
>> + desc_size = 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);
> What is 9 byte size for? Can not find 9 in spec "chapter 5.5 Context Hash".
> Do we want to copy the entire config descriptor(s) instead?
> If yes, then we might use dev->rawdescriptors[] which has wTotalLength.
> If no, the comment of 9 explanation will be useful here.
We have added a loop to collect all device configurations.
We used the value 9 as it is the standard size of a configuration
descriptor in
chapter 9.6.3 of the usb spec 2.0. We will however check if we can use a
more
dynamic field.
The specification is not completely clear about the content that should be
included in the context. We need to spend more time to validate the context
creation part to ensure every configuration scenarios are taken into
account.
>
>> +
>> + *ctx_size += 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 == NULL || dev == NULL ||
>> + dev->port_is_suspended == 1 ||
>> + dev->reset_in_progress == 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 = 0;
>> +
>> + uint8_t is_valid = 0;
>> + uint8_t is_known = 0;
>> + uint8_t is_blocked = 0;
>> + uint8_t chain_nb = 0;
>> + uint8_t slot_mask = 0;
>> + uint8_t slot = 0;
>> + uint8_t digests[256] = { 0 };
>> + uint8_t nonce[32] = {0};
>> + uint8_t chall[204] = {0};
>> + uint32_t dev_id = 0;
>> + size_t ctx_size = 0;
>> + int i = 0;
>> +
>> + uint8_t *cert_der = NULL;
>> + size_t cert_len = 0;
>> +
>> + if (unlikely(dev == NULL || dev->parent == NULL))
>> + return -ENODEV;
>> +
>> + struct usb_device *hub = dev->parent;
>> +
>> + // By default set authorization status at false
>> + dev->authorized = 0;
>> + dev->authenticated = 0;
>> +
>> + uint8_t *buffer = NULL;
>> + // Buffer to hold responses
>> + buffer = 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 = usb_authent_req_digest(dev, buffer, digests, &slot_mask);
>> + if (ret != 0) {
>> + pr_err("failed to get digest: %d\n", ret);
>> + goto cleanup;
>> + }
>> + pr_notice("received digest\n");
>> +
>> + usb_unlock_device(hub);
>> + ret = usb_policy_engine_check_digest(dev->route, digests, slot_mask,
>> + &is_known, &is_blocked, &dev_id);
>> + if (ret != 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 = usb_auth_try_resume(dev, hub);
>> + if (unlikely(ret != 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 = 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 = 0; i < 8; i++) {
>> + // Test if slot contains a certificate chain
>> + if (1 == ((slot_mask >> i) & 1)) {
>> + ret = usb_authent_read_certificate(dev, buffer,
>> + chain_nb,
>> + &cert_der,
>> + &cert_len);
>> + if (ret != 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 = usb_policy_engine_check_cert_chain(
>> + dev->route, digests + i * 32, cert_der,
>> + cert_len, &is_valid, &is_blocked,
>> + &dev_id);
>> + if (ret != 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 = usb_auth_try_resume(dev, hub);
>> + if (unlikely(ret != 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 = i;
>> + break;
>> + } else if (is_valid && is_blocked) {
>> + // Found a valid and unauthorized certificate,
>> + // reject device
>> + ret = 0;
>> + goto cleanup;
>> + }
>> + }
>> + }
>> + } else {
>> + // Pick a slot among the valid ones, take first one
>> + for (i = 0; i < 8; i++) {
>> + if (1 == ((is_known >> i) & 1)) {
>> + slot = i;
>> + break;
>> + }
>> + }
>> + }
>> +
>> + /*
>> + * Authenticate the device with a challenge request
>> + */
>> + // Obtain a nonce for the challenge
>> + usb_unlock_device(hub);
>> + ret = usb_policy_engine_generate_challenge(dev_id, nonce);
>> + if (ret != 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 = usb_auth_try_resume(dev, hub);
>> + if (unlikely(ret != 0)) {
>> + pr_err("failed to resume: %d\n", ret);
>> + goto cleanup;
>> + }
>> +
>> + pr_info("resuming\n");
>> +
>> + // Send a challenge request
>> + ret = usb_authent_challenge_dev(dev, buffer, slot, slot_mask, nonce,
>> + chall);
>> + if (ret != 0) {
>> + pr_err("failed to challenge device: %d\n", ret);
>> + goto cleanup;
>> + }
>> + pr_notice("validated challenge\n");
>> +
>> + // Create device context
>> + ret = usb_auth_create_dev_ctx(dev, buffer, 512, &ctx_size);
>> + if (ret != 0) {
>> + pr_err("failed to create context: %d\n", ret);
>> + goto cleanup;
>> + }
>> +
>> + // Validate the challenge
>> + usb_unlock_device(hub);
>> + ret = usb_policy_engine_check_challenge(dev_id, chall, buffer, ctx_size,
>> + &is_valid);
>> + if (ret != 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 = usb_auth_try_resume(dev, hub);
>> + if (unlikely(ret != 0)) {
>> + pr_err("failed to resume: %d\n", ret);
>> + goto cleanup;
>> + }
>> +
>> + pr_info("resuming\n");
>> +
>> + // Apply authorization decision
>> + if (is_valid) {
>> + dev->authorized = 1;
>> + dev->authenticated = 1;
>> + }
>> +
>> + ret = 0;
>> +
>> +cleanup:
>> + kfree(buffer);
>> + kfree(cert_der);
>> +
>> + return 0;
> In every early-error path `ret` is set but thrown away here.
> Enumeration thus always continues and hub believes the device is authenticated.
> Please return `ret`.
It will be fixed in the next version of the patch.
>
>> +}
>> diff --git a/drivers/usb/core/authent.h b/drivers/usb/core/authent.h
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..c3852636dbcea9150ed1663769e2a7b6348f528c
>> --- /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 <luc.bonnafoux@ssi.gouv.fr>
>> + * Author: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>> + *
>> + */
>> +
>> +#ifndef __USB_CORE_AUTHENT_H_
>> +#define __USB_CORE_AUTHENT_H_
>> +
>> +#include <linux/types.h>
>> +#include <linux/usb.h>
>> +#include <linux/usb/ch11.h>
>> +#include <linux/usb/hcd.h>
>> +
>> +/* 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_VERSION */
>> + __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 */
> nit: shall we follow camelcase as in spec or keep Linux underscore style?
We used the same convention as in `ch9.h` but we will adapt it to the
maintainers preferences.
>
>> + __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 endian*/
>> + __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 digests */
>> + __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_ */
>>
>> --
>> 2.50.0
>>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 3/4] usb: core: Plug the usb authentication capability
2025-06-21 11:09 ` Sabyrzhan Tasbolatov
@ 2025-06-30 12:25 ` Nicolas Bouchinet
0 siblings, 0 replies; 24+ messages in thread
From: Nicolas Bouchinet @ 2025-06-30 12:25 UTC (permalink / raw)
To: Sabyrzhan Tasbolatov
Cc: Greg Kroah-Hartman, Alan Stern, Kannappan R, Krzysztof Kozlowski,
Stefan Eichenberger, Thomas Gleixner, Pawel Laszczak, Ma Ke,
Jeff Johnson, Luc Bonnafoux, Luc Bonnafoux, Nicolas Bouchinet,
linux-kernel, linux-usb
On 6/21/25 13:09, Sabyrzhan Tasbolatov wrote:
> On Fri, Jun 20, 2025 at 7:27 PM <nicolas.bouchinet@oss.cyber.gouv.fr> wrote:
>> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>>
>> 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 functions.
>> The usb_device also contains an authenticated field that could be used to track
>> 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 <luc.bonnafoux@ssi.gouv.fr>
>> Signed-off-by: Luc Bonnafoux <luc.bonnafoux@ssi.gouv.fr>
>> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>> ---
>> 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..45ee8e93e263c41f1bf4271be4e69ccfcac3f650 100644
>> --- a/drivers/usb/core/config.c
>> +++ b/drivers/usb/core/config.c
>> @@ -14,6 +14,7 @@
>> #include <asm/byteorder.h>
>> #include "usb.h"
>>
>> +#include "authent.h"
>>
>> #define USB_MAXALTSETTING 128 /* Hard limit */
>>
>> @@ -824,7 +825,50 @@ static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
>> kref_init(&intfc->ref);
>> }
>>
>> - /* FIXME: parse the BOS descriptor */
>> + /* If device USB version is above 2.0, get BOS descriptor */
>> + /*
>> + * Requirement for bcdUSB >= 2.10 is defined in USB 3.2 §9.2.6.6
>> + * "Devices with a value of at least 0210H in the bcdUSB field of their
>> + * device descriptor shall support GetDescriptor (BOS Descriptor) requests."
>> + *
>> + * To discuss, BOS request could be also sent for bcdUSB >= 0x0201
>> + */
>> + // Set a default value for authenticated at true in order not to block devices
>> + // that do not support the authentication
>> + dev->authenticated = 1;
>> +
>> + if (le16_to_cpu(dev->descriptor.bcdUSB) >= 0x0201) {
>> + pr_notice("bcdUSB >= 0x0201\n");
>> + retval = 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 = usb_authenticate_device(dev);
>> + if (retval != 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;
> Returning 0 after rejecting a device leaves udev half-initialised and still
> linked in usb_bus_type (the hub driver only aborts after this call).
> The next hot-plug may oops on dangling pointers.
> Please consider returning -EPERM instead of 0.
We have changed the way we handle errors and
authentication/authorization. It
will appear in the next version of the patch.
We have commented and explained how we want to proceed in reply to Alan [1].
[1]:
https://lore.kernel.org/linux-usb/8cc10112-23a7-41af-b81f-7fc0c097d34d@oss.cyber.gouv.fr/
>
>> + } 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");
>> + }
>> + }
>>
>> /* 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 = bos->bLength;
>> total_len = le16_to_cpu(bos->wTotalLength);
>> num = 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 =
>> (struct usb_ptm_cap_descriptor *)buffer;
>> break;
>> + case USB_AUTHENT_CAP_TYPE:
>> + dev->bos->authent_cap =
>> + (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..753e55155ea34a7739b5f530dad429534e60842d 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 = MKDEV(USB_DEVICE_MAJOR,
>> (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
>>
>> + // 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);
>>
>> diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
>> index 0b4685aad2d50337f3cacb2198c95a68ae8eff86..76847c01d3493e2527992a3bb927422519d9a974 100644
>> --- a/drivers/usb/core/usb.c
>> +++ b/drivers/usb/core/usb.c
>> @@ -46,6 +46,7 @@
>> #include <linux/dma-mapping.h>
>>
>> #include "hub.h"
>> +#include "authent_netlink.h"
>>
>> const char *usbcore_name = "usbcore";
>>
>> @@ -1080,6 +1081,10 @@ static int __init usb_init(void)
>> usb_debugfs_init();
>>
>> usb_acpi_register();
>> +
>> + // TODO : check error case
>> + usb_auth_init_netlink();
>> +
>> retval = 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..e9037c8120b43556f8610f9acb3ad4129e847b98 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;
>> };
>>
>> int __usb_get_extra_descriptor(char *buffer, unsigned size,
>>
>> --
>> 2.50.0
>>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 3/4] usb: core: Plug the usb authentication capability
2025-06-23 18:15 ` Oliver Neukum
@ 2025-06-30 12:34 ` Nicolas Bouchinet
2025-06-30 13:07 ` Oliver Neukum
0 siblings, 1 reply; 24+ messages in thread
From: Nicolas Bouchinet @ 2025-06-30 12:34 UTC (permalink / raw)
To: Oliver Neukum, 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, linux-usb
Hi Olivier,
Thank you for your review.
Indeed our current implementation of the usb authentication is still a bit
crude.
Currently, most, if not all of usb devices can't handle authentication.
If we
want to have an integration that doesn't break on current hosts, we need to
have a fail safe. We are still working on the best way to handle the
combination of authentication and authorization.
See the reply to Alan [1].
[1]:
https://lore.kernel.org/linux-usb/8cc10112-23a7-41af-b81f-7fc0c097d34d@oss.cyber.gouv.fr/
On 6/23/25 20:15, Oliver Neukum wrote:
> Hi,
>
> I am afraid someone has to address this.
>
> On 20.06.25 16:27, nicolas.bouchinet@oss.cyber.gouv.fr wrote:
>
>> + // Set a default value for authenticated at true in order not to
>> block devices
>> + // that do not support the authentication
>> + dev->authenticated = 1;
>
> So the default is authenticated. OK.
>
>> + if (le16_to_cpu(dev->descriptor.bcdUSB) >= 0x0201) {
>> + pr_notice("bcdUSB >= 0x0201\n");
>> + retval = usb_get_bos_descriptor(dev);
>> + if (!retval) {
>> + pr_notice("found BOS\n");
>> +#ifdef CONFIG_USB_AUTHENTICATION
>> + if (dev->bos->authent_cap) {
>
> If the device claims not to support authentication ...
>
>> + /* If authentication cap is present, start device
>> authent */
>> + pr_notice("found Authent BOS\n");
>> + retval = usb_authenticate_device(dev);
>> + if (retval != 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");
>
> ... we do nothing about it. We enumerate.
>
> The purpose of authentication is guarding against unknown or malicious
> devices,
> isn't it? This behavior seems to be kind of incompatible with the goal.
>
> Regards
> Oliver
>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/4] usb: core: Introduce usb authentication feature
2025-06-25 9:59 ` Oliver Neukum
@ 2025-06-30 12:38 ` Nicolas Bouchinet
0 siblings, 0 replies; 24+ messages in thread
From: Nicolas Bouchinet @ 2025-06-30 12:38 UTC (permalink / raw)
To: Oliver Neukum, 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, linux-usb
Hi Olivier,
Thanks for the remarks on the style. We will take them into account in
the next
patch version.
We started to include endianess conversion function but tests are still
ongoing
with a physical device to ensure everything works as it should.
On 6/25/25 11:59, Oliver Neukum wrote:
>
>
> On 20.06.25 16:27, nicolas.bouchinet@oss.cyber.gouv.fr wrote:
>
>> +/**
>> + * 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 buffer,
>
> How can buffer be const if it is used for output?
Here, const operates on the left value which is the pointer, hence, the
address
is constant, not the value pointed by the pointer.
>
> [..]
>> +struct usb_auth_cert_req {
>> + uint16_t offset;
>> + uint16_t length;
>> +} __packed;
>
> Endianness?
>
>
>> +/**
>> + * 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 *const buffer,
>> + uint8_t slot, uint8_t **cert_der, size_t *cert_len)
>> +{
>> + uint16_t read_offset = 0;
>> + uint16_t read_length = 0;
>> + uint8_t chain_part[64] = { 0 };
>> +
>> + if (unlikely(slot >= 8 || buffer == NULL || cert_der == NULL ||
>> cert_len == 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) != 0) {
>> + pr_err("Failed to get first certificate part\n");
>> + return -ECOMM;
>> + }
>> +
>> + // Extract total length
>> + *cert_len = ((uint16_t *)chain_part)[0];
>
> Endianness
>
>
>> +
>> +/**
>> + * 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 allocated
>> + *
>> + * 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
>> *buffer,
>> + const uint8_t slot, const uint8_t slot_mask, const uint8_t
>> *const nonce,
>> + uint8_t *const chall)
>> +{
>> + int ret = -1;
>> +
>> + if (unlikely(buffer == NULL || slot >= 8 || nonce == NULL)) {
>> + pr_err("invalid arguments\n");
>> + return -EINVAL;
>> + }
>> +
>> + // AUTH OUT challenge request transfer
>> + memcpy(buffer, nonce, 32);
>> + ret = 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 = -ECOMM;
>> + goto cleanup;
>> + }
>> +
>> + // Complete the challenge with the request
>> + chall[1] = USB_SECURITY_PROTOCOL_VERSION;
>> + chall[0] = USB_AUTHENT_CHALLENGE_REQ_TYPE;
>> + chall[2] = slot;
>> + chall[3] = 0x00;
>> + memcpy(chall+4, nonce, 32);
>
> This may be worth a definition.
>> +
>> + // AUTH IN challenge response transfer
>> + ret = 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 = -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);
>
> The 168 comes whence?
>
>> +
>> + ret = 0;
>> +
>> +cleanup:
>> +
>> + return ret;
>> +}
>
>
>> +/**
>> + * @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 == NULL || dev == NULL ||
>> + dev->port_is_suspended == 1 ||
>> + dev->reset_in_progress == 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 = 0;
>> +
>> + uint8_t is_valid = 0;
>> + uint8_t is_known = 0;
>> + uint8_t is_blocked = 0;
>> + uint8_t chain_nb = 0;
>> + uint8_t slot_mask = 0;
>> + uint8_t slot = 0;
>> + uint8_t digests[256] = { 0 };
>> + uint8_t nonce[32] = {0};
>> + uint8_t chall[204] = {0};
>> + uint32_t dev_id = 0;
>> + size_t ctx_size = 0;
>> + int i = 0;
>> +
>> + uint8_t *cert_der = NULL;
>> + size_t cert_len = 0;
>> +
>> + if (unlikely(dev == NULL || dev->parent == NULL))
>> + return -ENODEV;
>> +
>> + struct usb_device *hub = dev->parent;
>> +
>> + // By default set authorization status at false
>> + dev->authorized = 0;
>> + dev->authenticated = 0;
>> +
>> + uint8_t *buffer = NULL;
>> + // Buffer to hold responses
>> + buffer = kzalloc(512, GFP_KERNEL);
>
> Should this not be cached for comparison after resume?
>
> Regards
> Oliver
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 3/4] usb: core: Plug the usb authentication capability
2025-06-30 12:34 ` Nicolas Bouchinet
@ 2025-06-30 13:07 ` Oliver Neukum
0 siblings, 0 replies; 24+ messages in thread
From: Oliver Neukum @ 2025-06-30 13:07 UTC (permalink / raw)
To: Nicolas Bouchinet, Oliver Neukum, 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, linux-usb
Hi,
On 30.06.25 14:34, Nicolas Bouchinet wrote:
> Hi Olivier,
>
> Thank you for your review.
>
> Indeed our current implementation of the usb authentication is still a bit
> crude.
that is understood, but the question here is not just whether they
are crude, but whether they are sensible. You are putting the use of
the authentication code in usb_new_device(). This means that the admin
cannot change the settings of currently connected devices. It would seem
to me that the check should go into usb_claim_interface().
Regards
Oliver
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 3/4] usb: core: Plug the usb authentication capability
2025-06-30 11:20 ` Nicolas Bouchinet
@ 2025-06-30 18:04 ` Alan Stern
0 siblings, 0 replies; 24+ messages in thread
From: Alan Stern @ 2025-06-30 18:04 UTC (permalink / raw)
To: Nicolas Bouchinet
Cc: Greg Kroah-Hartman, Kannappan R, Sabyrzhan Tasbolatov,
Krzysztof Kozlowski, Stefan Eichenberger, Thomas Gleixner,
Pawel Laszczak, Ma Ke, Jeff Johnson, Luc Bonnafoux, Luc Bonnafoux,
Nicolas Bouchinet, linux-kernel, linux-usb
On Mon, Jun 30, 2025 at 01:20:27PM +0200, Nicolas Bouchinet wrote:
> We moved the `usb_authenticate_dev()` call in `usb_new_device()` in order to
> perform the authentication only once the device configuration is complete.
usb_new_device() does device initialization, not device configuration.
The default configuration is selected by usb_choose_configuration(), but
the config can be changed at any time by the user (via sysfs or usbfs).
> Also
> we think we need to split the problem of handling the authentication vs
> authorization in two parts.
>
> - which component has authority to set the two fields ?
> - where/how is it enforced ?
>
> To answer the first question :
>
> - We think that the authenticated field can only be set by the
> `usb_authenticate_dev()` function.
>
> - it is less clear for the authorized status which is already manipulated by
> the sysfs (usbguard) and the default hcd policy.
>
> The reconciliation between the two fields could be done at the enforcement
> point. In `usb_probe_interface()` instead of simply checking the authorized
> flag
> it could check a more complex policy. For example:
>
> +-------------------+----------------------------------------+----------------+
>
> | | authorized | not
> authorized |
> +-------------------+----------------------------------------+----------------+
>
> | authenticated | OK | NOK
> |
> +-------------------+----------------------------------------+----------------+
>
> | not authenticated | Depends on tolerance in local security
> | |
> | | policy (set by cmdline or sysctl) | NOK
> |
> +-------------------+----------------------------------------+----------------+
>
>
> This way it would also help to handle internal devices. When
> `hcd->dev_policy` is
> set to USB_DEVICE_AUTHORIZE_INTERNAL, only internal devices are authorized
> by
> default on connection. So external devices will have to be authenticated and
> then authorized via the sysfs. Internal devices will be authorized and not
> authenticated.
Okay, that seems like a reasonable approach.
Alan Stern
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2025-06-30 18:04 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-20 14:27 [RFC PATCH 0/4] Support for usb authentication nicolas.bouchinet
2025-06-20 14:27 ` [RFC PATCH 1/4] usb: core: Introduce netlink usb authentication policy engine nicolas.bouchinet
2025-06-21 9:37 ` Sabyrzhan Tasbolatov
2025-06-30 11:42 ` Nicolas Bouchinet
2025-06-20 14:27 ` [RFC PATCH 2/4] usb: core: Introduce usb authentication feature nicolas.bouchinet
2025-06-20 14:54 ` Greg Kroah-Hartman
2025-06-30 11:07 ` Nicolas Bouchinet
2025-06-30 11:43 ` Greg Kroah-Hartman
2025-06-21 10:21 ` Sabyrzhan Tasbolatov
2025-06-30 11:56 ` Nicolas Bouchinet
2025-06-25 9:59 ` Oliver Neukum
2025-06-30 12:38 ` Nicolas Bouchinet
2025-06-20 14:27 ` [RFC PATCH 3/4] usb: core: Plug the usb authentication capability nicolas.bouchinet
2025-06-20 19:11 ` Alan Stern
2025-06-30 11:20 ` Nicolas Bouchinet
2025-06-30 18:04 ` Alan Stern
2025-06-21 11:09 ` Sabyrzhan Tasbolatov
2025-06-30 12:25 ` Nicolas Bouchinet
2025-06-23 18:15 ` Oliver Neukum
2025-06-30 12:34 ` Nicolas Bouchinet
2025-06-30 13:07 ` Oliver Neukum
2025-06-20 14:27 ` [RFC PATCH 4/4] usb: core: Add Kconfig option to compile usb authorization nicolas.bouchinet
2025-06-21 7:22 ` Greg Kroah-Hartman
2025-06-30 11:22 ` Nicolas Bouchinet
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).