crypto.rs wraps the crypto bindings (AES-128, AES-CMAC) for the KDF this
driver needs; hdcp.rs holds the HDCP 2.2 message-ID constants and wire
structs (reusing <drm/display/drm_hdcp.h> where it overlaps); ake.rs
builds/parses the AKE_Init/AKE_Send_Cert/AKE_No_Stored_km/
AKE_Send_H_prime/AKE_Send_Pairing_Info, LC_Init/LC_Send_L_prime and
SKE_Send_Eks messages plus the RepeaterAuth exchange, all verified
byte-exact against the reference daemon's wire traffic; golden.rs holds
a small captured plaintext capability-announce skeleton the handshake
fills with live session values.

This is a clean-room implementation of the standard HDCP 2.2 AKE from
the published spec and the driver's own captured wire traffic -- no
daemon code or binary content is reused.

Signed-off-by: Mike Lothian <[email protected]>
Assisted-by: Claude:claude-sonnet-5 [Claude-Code]
---
 drivers/gpu/drm/vino/ake.rs    | 173 +++++++++++++++++++++++++++++++++
 drivers/gpu/drm/vino/crypto.rs |  48 +++++++++
 drivers/gpu/drm/vino/golden.rs |  69 +++++++++++++
 drivers/gpu/drm/vino/hdcp.rs   | 167 +++++++++++++++++++++++++++++++
 4 files changed, 457 insertions(+)
 create mode 100644 drivers/gpu/drm/vino/ake.rs
 create mode 100644 drivers/gpu/drm/vino/crypto.rs
 create mode 100644 drivers/gpu/drm/vino/golden.rs
 create mode 100644 drivers/gpu/drm/vino/hdcp.rs

diff --git a/drivers/gpu/drm/vino/ake.rs b/drivers/gpu/drm/vino/ake.rs
new file mode 100644
index 000000000000..38919624cb2f
--- /dev/null
+++ b/drivers/gpu/drm/vino/ake.rs
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! HDCP 2.2 AKE wire layer (sec 5.1 OUT framing, sec 5.2 IN parsing) -- the 
byte-exact
+//! message builders the AKE state machine drives, mirroring the verified 
userspace
+//! oracle (`vino-driver::hdcp_msgs`). DLM hardcodes per-message `sub_size` /
+//! `sub_len_dw` values the dock validates, so they are reproduced verbatim 
rather
+//! than derived.
+//!
+//! OUT body layout (sec 5.1), after the 16-byte sec 3 transport header:
+//! ```text
+//!   body[0..2]   u16 sub_size      (DLM-fixed per message)
+//!   body[2..4]   u16 = 0x0010
+//!   body[4..8]   u32 hdcp_seq      increments 1..7 across the AKE OUT 
messages
+//!   body[8..22]  14 zero bytes
+//!   body[22..26] u32 = 0x00000030  marker
+//!   body[26]     u8  = 0x00        flag
+//!   body[27]     u8  = msg_id
+//!   body[28..]   HDCP payload (zero-padded to the fixed body length)
+//! ```
+#![allow(dead_code)] // AKE message builders; response handlers run only after 
CP engagement
+
+use super::*;
+
+/// HDCP 2.2 message IDs (sec 5.3). `pub(crate)` so the AKE state machine
+/// ([`super::VinoDriver::run_ake`]) can match on the response IDs too.
+pub(crate) mod id {
+    use kernel::bindings;
+
+    // Standard HDCP 2.2 message IDs: reuse the canonical values from
+    // `<drm/display/drm_hdcp.h>` rather than redefining them, so vino stays in
+    // lockstep with the kernel's HDCP definitions. Only the transport framing
+    // around these (the DisplayLink type/sub/ctr header) is vino-specific.
+    pub(crate) const AKE_INIT: u8 = bindings::HDCP_2_2_AKE_INIT as u8;
+    pub(crate) const AKE_SEND_CERT: u8 = bindings::HDCP_2_2_AKE_SEND_CERT as 
u8;
+    pub(crate) const AKE_NO_STORED_KM: u8 = 
bindings::HDCP_2_2_AKE_NO_STORED_KM as u8;
+    pub(crate) const AKE_SEND_H_PRIME: u8 = bindings::HDCP_2_2_AKE_SEND_HPRIME 
as u8;
+    pub(crate) const AKE_SEND_PAIRING_INFO: u8 = 
bindings::HDCP_2_2_AKE_SEND_PAIRING_INFO as u8;
+    pub(crate) const LC_INIT: u8 = bindings::HDCP_2_2_LC_INIT as u8;
+    pub(crate) const LC_SEND_L_PRIME: u8 = bindings::HDCP_2_2_LC_SEND_LPRIME 
as u8;
+    pub(crate) const SKE_SEND_EKS: u8 = bindings::HDCP_2_2_SKE_SEND_EKS as u8;
+    pub(crate) const REPEATERAUTH_SEND_RECEIVERID_LIST: u8 =
+        bindings::HDCP_2_2_REP_SEND_RECVID_LIST as u8;
+    pub(crate) const REPEATERAUTH_SEND_ACK: u8 = 
bindings::HDCP_2_2_REP_SEND_ACK as u8;
+    pub(crate) const REPEATERAUTH_STREAM_MANAGE: u8 = 
bindings::HDCP_2_2_REP_STREAM_MANAGE as u8;
+    pub(crate) const REPEATERAUTH_STREAM_READY: u8 = 
bindings::HDCP_2_2_REP_STREAM_READY as u8;
+
+    // DisplayLink-specific message IDs with no `<drm/display/drm_hdcp.h>` 
equivalent
+    // (the AKE_Send_rrx split and the transmitter/receiver-info + auth-status 
messages
+    // the DL3 dock uses), kept as literals.
+    pub(crate) const AKE_SEND_RRX: u8 = 0x06;
+    pub(crate) const RECEIVER_AUTH_STATUS: u8 = 0x12;
+    pub(crate) const AKE_TRANSMITTER_INFO: u8 = 0x13;
+    pub(crate) const AKE_RECEIVER_INFO: u8 = 0x14;
+}
+
+/// transport `sub_id` for HDCP OUT messages (type=4 sub=0x04, sec 5.1).
+const SUB_HDCP: u16 = 0x04;
+
+/// Allocate a `body_len`-byte zeroed body with the sec 5.1 header filled in
+/// (`sub_size`, the `0x0010` marker, `hdcp_seq`, the `0x30` marker and 
`msg_id`).
+/// The caller writes the payload into `body[28..]`.
+fn body(body_len: usize, sub_size: u16, hdcp_seq: u32, msg_id: u8) -> 
Result<KVec<u8>> {
+    let mut b = KVec::from_elem(0u8, body_len, GFP_KERNEL)?;
+    b[0..2].copy_from_slice(&sub_size.to_le_bytes());
+    b[2..4].copy_from_slice(&0x0010u16.to_le_bytes());
+    b[4..8].copy_from_slice(&hdcp_seq.to_le_bytes());
+    b[22..26].copy_from_slice(&0x0000_0030u32.to_le_bytes());
+    b[27] = msg_id;
+    Ok(b)
+}
+
+/// Wrap a finished HDCP body in the sec 3 transport header (type=4 sub=0x04) 
with
+/// the DLM-fixed `sub_len_dw` and the transport `seq`.
+fn wrap(sub_len_dw: u16, seq: u32, body: &[u8]) -> Result<KVec<u8>> {
+    let mut frame = KVec::with_capacity(16 + body.len(), GFP_KERNEL)?;
+    proto::push_frame_with(&mut frame, 0x04, SUB_HDCP, sub_len_dw, seq, body)?;
+    Ok(frame)
+}
+
+/// `AKE_Init` (msg_id 0x02): `rtx[8] || TxCaps[3]`, padded to a 48-byte body
+/// (`sub_size=0x22`, `sub_len_dw=0x0c` -- guide sec 5.4 table).
+pub(super) fn ake_init(
+    hdcp_seq: u32,
+    seq: u32,
+    rtx: &[u8; 8],
+    tx_caps: &[u8; 3],
+) -> Result<KVec<u8>> {
+    let mut b = body(48, 0x0022, hdcp_seq, id::AKE_INIT)?;
+    b[28..36].copy_from_slice(rtx);
+    b[36..39].copy_from_slice(tx_caps);
+    wrap(0x000c, seq, &b)
+}
+
+/// `AKE_Transmitter_Info` (msg_id 0x13): byte-exact DLM framing
+/// (`sub_size=0x1f`, `sub_len_dw=0x0f`), payload `00 06 02 00 02`.
+pub(super) fn ake_transmitter_info(hdcp_seq: u32, seq: u32) -> 
Result<KVec<u8>> {
+    let mut b = body(48, 0x001f, hdcp_seq, id::AKE_TRANSMITTER_INFO)?;
+    b[28..33].copy_from_slice(&[0x00, 0x06, 0x02, 0x00, 0x02]);
+    wrap(0x000f, seq, &b)
+}
+
+/// `AKE_No_Stored_km` (msg_id 0x04): the 128-byte RSA-OAEP-SHA256 `Ekpub(km)`
+/// in a 160-byte body (`sub_size=0x9a`, `sub_len_dw=0x04` -- guide sec 5.4 
table).
+pub(super) fn ake_no_stored_km(
+    hdcp_seq: u32,
+    seq: u32,
+    ekpub_km: &[u8; 128],
+) -> Result<KVec<u8>> {
+    let mut b = body(160, 0x009a, hdcp_seq, id::AKE_NO_STORED_KM)?;
+    b[28..156].copy_from_slice(ekpub_km);
+    wrap(0x0004, seq, &b)
+}
+
+/// `LC_Init` (msg_id 0x09): `rn[8]` in a 48-byte body
+/// (`sub_size=0x22`, `sub_len_dw=0x0c`).
+pub(super) fn lc_init(hdcp_seq: u32, seq: u32, rn: &[u8; 8]) -> 
Result<KVec<u8>> {
+    let mut b = body(48, 0x0022, hdcp_seq, id::LC_INIT)?;
+    b[28..36].copy_from_slice(rn);
+    wrap(0x000c, seq, &b)
+}
+
+/// `SKE_Send_Eks` (msg_id 0x0b): `Edkey(ks)[16] || riv[8]` in a 64-byte body
+/// (`sub_size=0x32`, `sub_len_dw=0x0c`).
+pub(super) fn ske_send_eks(
+    hdcp_seq: u32,
+    seq: u32,
+    edkey_ks: &[u8; 16],
+    riv: &[u8; 8],
+) -> Result<KVec<u8>> {
+    let mut b = body(64, 0x0032, hdcp_seq, id::SKE_SEND_EKS)?;
+    b[28..44].copy_from_slice(edkey_ks);
+    b[44..52].copy_from_slice(riv);
+    wrap(0x000c, seq, &b)
+}
+
+/// `RepeaterAuth_Send_ACK` (msg_id 0x0f): the full `V[16]` in a 48-byte body
+/// (`sub_size=0x2a`, `sub_len_dw=0x04`).
+pub(super) fn repeater_auth_send_ack(
+    hdcp_seq: u32,
+    seq: u32,
+    v: &[u8; 16],
+) -> Result<KVec<u8>> {
+    let mut b = body(48, 0x002a, hdcp_seq, id::REPEATERAUTH_SEND_ACK)?;
+    b[28..44].copy_from_slice(v);
+    wrap(0x0004, seq, &b)
+}
+
+/// `RepeaterAuth_Stream_Manage` SM2 (msg_id 0x10): byte-exact DLM replica sent
+/// after Send_ACK -- `k=2` (LE), `StreamID_Type[0]=4` (LE), `body[43]=0x05`
+/// (`sub_size=0x2d`, `sub_len_dw=0x01`). See guide sec 5.4 and sec 8.2.
+///
+/// `type0` (CP_STREAM_TYPE0 experiment, 2026-06-23): zero the two DL3 
stream-type bytes
+/// (0x04/0x05 -> 0x00) so the dock is asked for a single Type-0 
(unrestricted) stream
+/// instead of the replicated DLM stream set. Default DLM-exact (`type0 == 
false`); the M
+/// computation in `wait_cap_complete` zeroes the matching `m_data` bytes 
under the same flag.
+pub(super) fn repeater_auth_stream_manage(hdcp_seq: u32, seq: u32, type0: 
bool) -> Result<KVec<u8>> {
+    let mut b = body(48, 0x002d, hdcp_seq, id::REPEATERAUTH_STREAM_MANAGE)?;
+    b[32..36].copy_from_slice(&[0x02, 0, 0, 0]); // k = 2 (LE)
+    let (s0, s1) = if type0 { (0x00, 0x00) } else { (0x04, 0x05) };
+    b[36..40].copy_from_slice(&[s0, 0, 0, 0]); // StreamID_Type[0]
+    b[43] = s1; // StreamID_Type[1]
+    wrap(0x0001, seq, &b)
+}
+
+/// Parse an IN HDCP message body (sec 5.2): `body[8]` marker, `body[9]` 
msg_id,
+/// `body[10..]` payload (for `AKE_Send_Cert`, `body[10]` is a version flag).
+/// Returns `(msg_id, payload)`.
+pub(super) fn parse_in(body: &[u8]) -> Option<(u8, &[u8])> {
+    if body.len() < 10 {
+        return None;
+    }
+    Some((body[9], &body[10..]))
+}
diff --git a/drivers/gpu/drm/vino/crypto.rs b/drivers/gpu/drm/vino/crypto.rs
new file mode 100644
index 000000000000..83e988ded726
--- /dev/null
+++ b/drivers/gpu/drm/vino/crypto.rs
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Thin adapters onto the shared [`kernel::crypto`] library-crypto bindings, 
so the
+//! protocol code keeps its `crypto::aes128_ecb` / `crypto::hmac_sha256` call 
sites.
+#![allow(dead_code)] // exercised by the AES-CTR seal + HDCP AKE
+
+use super::*;
+
+/// An AES-128 key prepared once for single-block encryption; re-exported so 
the
+/// AES-CTR keystream loops in [`cp`](super::cp) can expand the key once and 
reuse
+/// it across every block (rather than re-expanding per block).
+pub(super) use kernel::crypto::Aes128;
+
+/// `AES_ECB(key, block)` -- one 16-byte AES-128 block. Convenience one-shot 
for
+/// callers that encrypt a single block (e.g. HDCP dKey derivation); the 
AES-CTR
+/// paths build an [`Aes128`] once and call [`Aes128::encrypt_block`] in a 
loop.
+pub(super) fn aes128_ecb(key: &[u8; 16], block: &[u8; 16]) -> Result<[u8; 16]> 
{
+    Ok(Aes128::new(key)?.encrypt_block(block))
+}
+
+/// `HMAC-SHA256(key, data)`.
+pub(super) fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
+    kernel::crypto::hmac_sha256(key, data)
+}
+
+/// `AES-CMAC-128(key, data)` (RFC 4493) via the in-tree AES-CMAC library.
+/// This is DisplayLink's "Dl3Cmac" core -- the CP per-message integrity tag is
+/// `AES_CMAC(ks, nonce8 || BE64(counter) || content)` (see `cp::dl3cmac_tag`);
+/// verified byte-exact against live DLM data (canonical guide sec 8.6.7).
+pub(super) fn aes_cmac(key: &[u8; 16], data: &[u8]) -> [u8; 16] {
+    kernel::crypto::aes_cmac(key, data)
+}
+
+/// `SHA256(data)`.
+pub(super) fn sha256(data: &[u8]) -> [u8; 32] {
+    kernel::crypto::sha256(data)
+}
+
+/// Raw RSA public-key op `out = input^exponent mod modulus`, big-endian,
+/// `out` written fixed-width (caller applies OAEP padding to `input`).
+pub(super) fn rsa_pubkey_encrypt(
+    modulus: &[u8],
+    exponent: &[u8],
+    input: &[u8],
+    out: &mut [u8],
+) -> Result {
+    kernel::crypto::rsa_pubkey_encrypt(modulus, exponent, input, out)
+}
diff --git a/drivers/gpu/drm/vino/golden.rs b/drivers/gpu/drm/vino/golden.rs
new file mode 100644
index 000000000000..e379e888c9c8
--- /dev/null
+++ b/drivers/gpu/drm/vino/golden.rs
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Captured DisplayLink control-plane protocol templates.
+//!
+//! These are NOT replay dumps of an encrypted session. They are the
+//! session-invariant *plaintext skeletons* of two control-plane bursts 
captured
+//! from the proprietary DisplayLinkManager (DLM). The driver overwrites the
+//! session-specific fields with THIS session's live values and then seals the
+//! result under the live `ks`, so the bytes that reach the wire are this
+//! session's own, never the capture's. They remain inline here because the
+//! field-by-field live builders that would replace them are not yet written --
+//! see the "help wanted" note at the top of the file.
+
+/// Plaintext capability-announce skeleton: the seven `sub=0x10`, ctr 1..7
+/// frames that restate the AKE OUT messages. `build_cap_announce` walks this
+/// and overwrites each frame's payload with this session's live AKE value
+/// (rtx / Ekpub / rn / Edkey+riv / V). 590 bytes.
+pub(super) const CAP_PLAIN_1080P: &[u8] = &[
+    0x40, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00,
+    0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x10, 0x00, 0x01, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1f, 0xe7,
+    0x18, 0x56, 0x6e, 0x1f, 0xc0, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3c, 0x00,
+    0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x1f, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
+    0x00, 0x00, 0x00, 0x13, 0x00, 0x06, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0xb0, 0x00, 0x00, 0x00, 0xac, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00,
+    0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x10, 0x00, 0x03, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0e, 0xd9,
+    0x2f, 0x05, 0xee, 0x3e, 0xca, 0x40, 0x7e, 0x14, 0x9f, 0x9d, 0x12, 0x6c,
+    0xca, 0x1a, 0x70, 0x27, 0x55, 0x02, 0x22, 0x0c, 0xde, 0x7d, 0x79, 0x6b,
+    0x13, 0x14, 0x32, 0x62, 0xef, 0x62, 0xc0, 0xf2, 0xb6, 0x3d, 0x41, 0x21,
+    0xcf, 0xbd, 0x2a, 0x40, 0xf9, 0xe8, 0x42, 0xc7, 0xbb, 0xa7, 0xcd, 0x8c,
+    0x53, 0xab, 0x56, 0x4e, 0x5b, 0xf8, 0x55, 0x0a, 0x05, 0x96, 0x09, 0x28,
+    0xbb, 0xf9, 0xbe, 0xc9, 0xe8, 0x81, 0x32, 0xaa, 0xc8, 0x49, 0x27, 0x3c,
+    0x80, 0x5c, 0x7c, 0xb8, 0x23, 0x54, 0xb6, 0xe0, 0x38, 0x71, 0x3c, 0xdd,
+    0xa6, 0x77, 0x91, 0x16, 0x3f, 0xd4, 0xec, 0xfd, 0xdd, 0x56, 0xf7, 0x01,
+    0xe1, 0x6c, 0x03, 0x50, 0xdf, 0x80, 0xd5, 0x93, 0x66, 0x55, 0xe1, 0xd7,
+    0x3b, 0x55, 0x7e, 0x9c, 0xb7, 0x71, 0xfe, 0x0b, 0x7d, 0x1c, 0x0d, 0x6b,
+    0x18, 0xda, 0xdb, 0xbe, 0x79, 0x75, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+    0x00, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x09, 0xf4, 0xc4, 0x61, 0x0d,
+    0xe0, 0x75, 0x99, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x04, 0x00,
+    0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00,
+    0x10, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+    0x00, 0x0b, 0xb2, 0xd9, 0xbd, 0x87, 0x94, 0x1b, 0xf0, 0xec, 0x59, 0x40,
+    0xf2, 0xba, 0xd5, 0x6d, 0x24, 0xab, 0x56, 0xfe, 0x0c, 0xff, 0xbc, 0x3a,
+    0x9d, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x00, 0x00,
+    0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, 0x00,
+    0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0f,
+    0x38, 0x08, 0x3b, 0x1f, 0x39, 0x61, 0xb4, 0x9b, 0x3a, 0x2e, 0x9a, 0x1c,
+    0xbd, 0x64, 0x78, 0x85, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+    0x3c, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x2d, 0x00, 0x10, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x30, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
+    0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
+    0x00, 0x00,
+];
diff --git a/drivers/gpu/drm/vino/hdcp.rs b/drivers/gpu/drm/vino/hdcp.rs
new file mode 100644
index 000000000000..c22d58b624ab
--- /dev/null
+++ b/drivers/gpu/drm/vino/hdcp.rs
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! HDCP 2.2 key derivation and verifier computation (sec 5.6), built on 
[`crypto`].
+//! Lets the driver run a clean-room AKE without DisplayLink's binary; the 
byte-exact
+//! formulas are verified against the live dock in the guide.
+#![allow(dead_code)] // some HDCP builders/handlers are reached only after CP 
engagement
+
+use super::*;
+
+/// `dkey_n = AES_ECB(km with low-8-bytes XOR rn, rtx || (rrx with byte15 XOR 
n))`
+/// (HDCP 2.2 IIA sec 2.7, sec 5.6). The counter `n` XORs into byte 15 (LSB of 
the rrx
+/// half) of the IV; `rn` XORs into the low 8 bytes (km[8..16]) of the key -- 
zero
+/// for the `kd` derivation, the SKE nonce for `dkey_2`.
+fn derive_dkey(
+    km: &[u8; 16],
+    rn: &[u8; 8],
+    rtx: &[u8; 8],
+    rrx: &[u8; 8],
+    n: u8,
+) -> Result<[u8; 16]> {
+    let mut iv = [0u8; 16];
+    iv[..8].copy_from_slice(rtx);
+    iv[8..].copy_from_slice(rrx);
+    iv[15] ^= n;
+    let mut key = *km;
+    for i in 0..8 {
+        key[8 + i] ^= rn[i];
+    }
+    crypto::aes128_ecb(&key, &iv)
+}
+
+/// `kd = dkey_0 || dkey_1` with `rn = 0` (sec 5.6) -- the 256-bit derived key.
+pub(super) fn derive_kd(km: &[u8; 16], rtx: &[u8; 8], rrx: &[u8; 8]) -> 
Result<[u8; 32]> {
+    let rn = [0u8; 8];
+    let dkey0 = derive_dkey(km, &rn, rtx, rrx, 0)?;
+    let dkey1 = derive_dkey(km, &rn, rtx, rrx, 1)?;
+    let mut kd = [0u8; 32];
+    kd[..16].copy_from_slice(&dkey0);
+    kd[16..].copy_from_slice(&dkey1);
+    Ok(kd)
+}
+
+/// `H' = HMAC-SHA256(kd, rtx with byte7 ^= repeater)` (sec 5.6).
+pub(super) fn compute_h(kd: &[u8; 32], rtx: &[u8; 8], repeater: bool) -> [u8; 
32] {
+    let mut msg = *rtx;
+    msg[7] ^= repeater as u8;
+    crypto::hmac_sha256(kd, &msg)
+}
+
+/// `L' = HMAC-SHA256(kd with low-8-bytes XOR rrx, rn)` (sec 5.6).
+///
+/// "low-8-bytes" is the *least-significant* 64 bits of the 256-bit `kd`, i.e.
+/// `kd[24..32]` -- verified byte-exact against the live dock by the userspace
+/// oracle (`vino-hdcp::kdf::compute_l`). XOR-ing into `kd[0..8]` does not 
verify.
+pub(super) fn compute_l(kd: &[u8; 32], rrx: &[u8; 8], rn: &[u8; 8]) -> [u8; 
32] {
+    let mut key = *kd;
+    for i in 0..8 {
+        key[24 + i] ^= rrx[i];
+    }
+    crypto::hmac_sha256(&key, rn)
+}
+
+/// Full `V = HMAC-SHA256(kd, list_header)` (256 bits) for RepeaterAuth (sec 
2.3).
+/// The **MSB-128** (`[..16]`) is `V'` -- verified against the repeater's
+/// `RepeaterAuth_Send_ReceiverID_List` trailer. The **LSB-128** (`[16..]`) is 
the
+/// value the transmitter returns in `RepeaterAuth_Send_Ack`. vino had been 
sending
+/// the MSB (i.e. echoing the dock's own `V'`) as the Ack -- so the dock 
rejected the
+/// repeater authentication, never acknowledged Stream_Manage, and never 
engaged CP
+/// (proven 2026-06-11: vino's ctr6 == the dock's `id=0x21` list trailer; 
DLM's ctr6
+/// is a computed value present in no dock push). H'/L'/V' still pass because 
V'
+/// verification uses the MSB.
+pub(super) fn compute_v_full(kd: &[u8; 32], list_header: &[u8]) -> [u8; 32] {
+    crypto::hmac_sha256(kd, list_header)
+}
+
+/// MGF1 mask generation (RFC 8017 sec B.2.1) with SHA-256: returns `mask_len`
+/// bytes of `T = SHA256(seed || I2OSP(0,4)) || SHA256(seed || I2OSP(1,4)) || 
...`.
+fn mgf1_sha256(seed: &[u8], mask_len: usize) -> Result<KVec<u8>> {
+    let mut mask = KVec::with_capacity(mask_len, GFP_KERNEL)?;
+    let mut counter: u32 = 0;
+    let mut block = KVec::with_capacity(seed.len() + 4, GFP_KERNEL)?;
+    while mask.len() < mask_len {
+        block.clear();
+        block.extend_from_slice(seed, GFP_KERNEL)?;
+        block.extend_from_slice(&counter.to_be_bytes(), GFP_KERNEL)?;
+        let digest = crypto::sha256(&block);
+        let take = core::cmp::min(digest.len(), mask_len - mask.len());
+        mask.extend_from_slice(&digest[..take], GFP_KERNEL)?;
+        counter += 1;
+    }
+    Ok(mask)
+}
+
+/// EME-OAEP encode (RFC 8017 sec 7.1.1) with SHA-256 and an empty label, for a
+/// `k`-byte modulus. Returns the `k`-byte encoded message `EM` ready for the
+/// raw RSA op. `seed` is `hLen` (32) random bytes. HDCP 2.2 uses SHA-256 here
+/// (SHA-1 makes the dock stop responding -- guide sec 5.4).
+fn eme_oaep_encode(k: usize, msg: &[u8], seed: &[u8; 32]) -> Result<KVec<u8>> {
+    const HLEN: usize = 32;
+    // DB = lHash || PS(zeros) || 0x01 || M, length k - hLen - 1.
+    let l_hash = crypto::sha256(&[]);
+    let db_len = k - HLEN - 1;
+    let mut db = KVec::with_capacity(db_len, GFP_KERNEL)?;
+    db.extend_from_slice(&l_hash, GFP_KERNEL)?;
+    let ps_len = db_len - HLEN - 1 - msg.len(); // k - mLen - 2*hLen - 2
+    for _ in 0..ps_len {
+        db.push(0, GFP_KERNEL)?;
+    }
+    db.push(0x01, GFP_KERNEL)?;
+    db.extend_from_slice(msg, GFP_KERNEL)?;
+    // maskedDB = DB ^ MGF1(seed, db_len).
+    let db_mask = mgf1_sha256(seed, db_len)?;
+    for i in 0..db_len {
+        db[i] ^= db_mask[i];
+    }
+    // maskedSeed = seed ^ MGF1(maskedDB, hLen).
+    let seed_mask = mgf1_sha256(&db, HLEN)?;
+    let mut masked_seed = [0u8; HLEN];
+    for i in 0..HLEN {
+        masked_seed[i] = seed[i] ^ seed_mask[i];
+    }
+    // EM = 0x00 || maskedSeed || maskedDB.
+    let mut em = KVec::with_capacity(k, GFP_KERNEL)?;
+    em.push(0x00, GFP_KERNEL)?;
+    em.extend_from_slice(&masked_seed, GFP_KERNEL)?;
+    em.extend_from_slice(&db, GFP_KERNEL)?;
+    Ok(em)
+}
+
+/// RSA-OAEP-SHA256 encrypt the 16-byte master key `km` under the dock's
+/// RSA-1024 public key (`modulus[128]`, `exponent`), giving the 128-byte
+/// `Ekpub(km)` for `AKE_No_Stored_km` (sec 5.4). Generates a fresh OAEP seed.
+pub(super) fn oaep_encrypt_km(
+    modulus: &[u8; 128],
+    exponent: &[u8],
+    km: &[u8; 16],
+) -> Result<[u8; 128]> {
+    let mut seed = [0u8; 32];
+    super::rng::fill(&mut seed);
+    let em = eme_oaep_encode(128, km, &seed)?;
+    let mut out = [0u8; 128];
+    crypto::rsa_pubkey_encrypt(modulus, exponent, &em, &mut out)?;
+    Ok(out)
+}
+
+/// SKE: `Edkey(ks) = ks XOR (dkey_2 with low-8-bytes XOR rrx)` (sec 5.6).
+///
+/// `dkey_2` is derived with the SKE nonce `rn` mixed into the key; `rrx` then
+/// XORs into the low 8 bytes (`dkey_2[8..16]`) of the mask. The result is the
+/// 16-byte `Edkey_ks` carried by `SKE_Send_Eks` (msg_id 0x0b).
+pub(super) fn compute_eks(
+    km: &[u8; 16],
+    rtx: &[u8; 8],
+    rrx: &[u8; 8],
+    rn: &[u8; 8],
+    ks: &[u8; 16],
+) -> Result<[u8; 16]> {
+    let mut mask = derive_dkey(km, rn, rtx, rrx, 2)?;
+    for i in 0..8 {
+        mask[8 + i] ^= rrx[i];
+    }
+    let mut edkey_ks = [0u8; 16];
+    for i in 0..16 {
+        edkey_ks[i] = ks[i] ^ mask[i];
+    }
+    Ok(edkey_ks)
+}
-- 
2.55.0

Reply via email to