# VDDK NFC authentication This document records how VMware VDDK authenticates for NBD/NFC disk access, and how the Python replacement in `openvixdisklib/nfc_auth.py` reproduces that path. Findings come from VDDK 8.0.2 libraries (`libvixDiskLib`, `libvddkVimAccess`, `libvim-types`), live SOAP calls against vCenter 8.0.1, and a TLS intercept of `VixDiskLib_ConnectEx` / `VixDiskLib_Open`. The steps used to obtain those findings are in `docs/reverse_engineering_procedure.md`. The goal of this stage is authentication only: a logged-in VIM session plus an authd TLS socket that has completed `200 Connect`. Opening a VMDK and reading sectors is `docs/nfc_open.md`. ## Mapping from VDDK The VDDK wrapper in `tests/integration/vixdisklib.py` calls `VixDiskLib_ConnectEx` with UID credentials and `VixDiskLib_Open` on a datastore path. VDDK does **not** send the vCenter username and password to ESXi port 902. It: 1. Logs into vCenter over HTTPS 443 (SOAP / `urn:vim25`). 2. Asks vCenter for a one-time NFC ticket. 3. Connects to the ESXi **authd** daemon on TCP 902, upgrades to TLS, and presents that ticket. | VDDK call | What actually happens | | ------------------------------------ | ---------------------------------------------------------- | | `VixDiskLib_InitEx` | Load plugins, SSL, logging | | `VixDiskLib_ConnectEx` | SOAP `SessionManager.Login` to vCenter | | `VixDiskLib_Open` (read-only) | `NfcGetVmFiles` ticket, then authd handshake, then NFC I/O | | `VixDiskLib_Open` (read-write) | `NfcRandomAccessOpenDisk` ticket (disk key + host) | | `transport_modes="nbdssl"` (default) | NBDSSL (`vpxa-nfcssl://...@esxi:902`, second TLS wrap) | | `transport_modes="nbd"` | NBD over NFC (`vpxa-nfc://...@esxi:902`) | | `vmxSpec=moref=vm-13098` | VM managed object used as the ticket target | | `snapshot_ref` | Not consumed by the ticket call itself | | `VIXDISKLIB_CRED_UID` | Username/password for VIM only | Lab topology used for capture: - vCenter: `10.8.1.199` (VirtualCenter 8.0.1) - VM: `vm-13098` on host `host-13001` (`10.8.1.250`) - NFC service moref on vCenter: `nfcService` - Authd: `10.8.1.250:902` ## Stage 1: VIM login This is a public pyVmomi operation. Reuse `pyVim.connect.SmartConnect` rather than crafting SOAP. - Endpoint: `https://:443/sdk` - Cookie: `vmware_soap_session` - SOAPAction: `"urn:vim25/8.0.1.0"` (negotiated) VDDK logs this as `Connected to VIM Server` / `Authenticating user` / `Logged in!`. The Python replacement keeps that `ServiceInstance` and its stub for the ticket call. Direct ESXi login is the same SOAP login against hostd, but the NFC moref and service name differ (`ha-nfc` instead of `nfcService` / `vpxa-nfc`). The lab path is vCenter-mediated. ## Stage 2: NFC ticket ### Why this is not public pyVmomi `vim.NfcService` is omitted from the public vim25 WSDL that pyVmomi ships. vCenter still implements it: - Version document: `GET /sdk/nfcServiceVersions.xml` → namespace `urn:nfc`, version `7.0.3.2` - Methods also accept `urn:vim25` (that is what VDDK uses) - Well-known moref on this vCenter: `nfcService` `ServiceManager.QueryServiceList` does **not** list NFC. The moref is hardcoded in VDDK as `nfcService` (vCenter) or `ha-nfc` (ESXi). `openvixdisklib/nfc_auth.py` registers the missing type with `pyVmomi.VmomiSupport.CreateManagedType` and invokes it on the existing SmartConnect stub, so serialization, cookies, and `HostServiceTicket` stay in pyVmomi. ### Methods VDDK actually calls Intercepted SOAP for a **read-only** `VixDiskLib_Open` of a datastore path: ```xml <_this type="NfcService">nfcService vm-13098 ``` No disk path, snapshot, or host is in this request. The path (`[datastore0] ...-000007.vmdk`) is used later on the NFC channel. A `GetVmFiles` ticket is **not** writable. Opening the same path with NFC flags `0x1a` returns AIO error `0x0b` (`VIX_E_FILE_READ_ONLY`). Writable `ConnectEx(readOnly=FALSE)` uses a disk-scoped ticket instead. `libvim-types.so` maps vmodl `randomAccessOpen` to WSDL `NfcRandomAccessOpenDisk` (same arguments as the read-only sibling): ```xml <_this type="NfcService">nfcService vm-13098 2000 host-13001 ``` A disk-scoped **read** ticket also works and returns the same `HostServiceTicket` type: ```xml <_this type="NfcService">nfcService vm-13098 2000 host-13001 ``` `diskDeviceKey` is `VirtualDisk.key` from `vm.config.hardware.device` (2000 for Hard disk 1). The replacement resolves it from the datastore path when `open` is given a VMDK rather than a key. ### Return value: `vim.HostServiceTicket` Public pyVmomi type. Example from this lab: | Field | Example | Role | | ---------------- | -------------------------------------- | ----------------------------------------- | | `host` | `10.8.1.250` | ESXi management / NFC address | | `port` | `902` | authd TCP port | | `sslThumbprint` | `BE:22:58:...:76:29` | SHA-1 of the ESXi TLS cert | | `service` | `vpxa-nfc` | authd `PROXY` argument | | `serviceVersion` | `1.1` | NFC hosted by hostd (ESX 3.0+ convention) | | `sessionId` | `52cdebc5-b7ee-359a-1dec-76f0bc105ac5` | One-time authd `SESSION` token | Tickets are single-use. Calling `GetVmFiles` twice issues two tickets; only the one presented to authd is consumed. ### Other NfcService methods seen in VDDK WSDL names are prefixed with `Nfc`. The vmodl names (from `libvim-types.so`) include: | WSDL name | Parameters (observed / from C++) | Notes | | ----------------------------- | -------------------------------------- | ------------------------------ | | `NfcGetVmFiles` | `vm` | VDDK read-only Open path | | `NfcRandomAccessOpenReadonly` | `vm`, `diskDeviceKey`, `hostForAccess` | Disk-scoped read ticket | | `NfcRandomAccessOpenDisk` | `vm`, `diskDeviceKey`, `hostForAccess` | Disk-scoped read-write ticket | | `NfcGetServerNfcLibVersion` | `hostForAccess` | Lab returned `11` | | `NfcFileManagement` | requires `ds` (datastore) | File copy, not NBD | | `NfcSystemManagement` | host moref | Not used for disk open | `NfcGetServerNfcLibVersion` without `hostForAccess` fails with `A specified parameter was not correct: hostForAccess`. Using moref `ha-nfc` on vCenter fails with `ManagedObjectNotFound`; `nfcService` is the correct vCenter object. ## Stage 3: authd handshake (TCP 902) authd is the VMware Authentication Daemon. Plaintext banner from ESXi 8: ``` 220 VMware Authentication Daemon Version 1.10: SSL Required, ServerDaemonProtocol:SOAP, MKSDisplayProtocol:VNC , VMXARGS supported, NFCSSL supported/t, SHA256 supported ``` SSL is required. Sending commands before `wrap_socket` closes the connection. After TLS there is **no** `USER` / `PASS` when the client holds a vCenter NFC ticket. ### Sequence captured from VDDK VDDK log line immediately before the socket: ``` Using proxy/session authentication, sessionId=..., useSSL=0 Plain-text connection is deprecated; use SSL to connect to NFC server ``` `useSSL=0` does **not** mean skip TLS on 902. It means skip a second NFCSSL wrap after authd TLS. The management channel is still TLS. `useSSL=1` (nbdssl) is the same authd commands with `PROXY vpxa-nfcssl` and a second TLS handshake after `200 Connect`. Intercepted writes/reads after the TLS handshake: ``` C -> SESSION \r\n C -> BANNER \r\n S -> 220 VMware Authentication Daemon Version 1.10: ...\r\n C -> THUMBPRINT_SHA2 PlainText\r\n S -> 200 \r\n C -> PROXY vpxa-nfc\r\n S -> 200 Connect ha-nfc\r\n ``` NBDSSL uses the same `SESSION` / `BANNER` / `THUMBPRINT_SHA2 PlainText` sequence. The ticket still has `service=vpxa-nfc`; the client rewrites the PROXY argument: ``` C -> PROXY vpxa-nfcssl\r\n S -> 200 Connect ha-nfcssl\r\n ``` After that reply, authd TLS is finished and `ha-nfcssl` expects a **new** TLS ClientHello on the same TCP connection (`useSSL=1`). NFC frames then travel as TLS application data of that second session. NBD (`useSSL=0`) skips the second wrap and sends NFC as raw TCP instead. Notes: - `SESSION` does not get a reply of its own. Waiting for a line after `SESSION` looks like a hang. - `BANNER` is the 7-byte command `BANNER` plus a trailing space. That space is part of the token; authd strips spaces when matching some commands, so `THUMBPRINT_SHA2 ` is parsed as one token and returns `501 Invalid arguments`. `PlainText` has no extra spaces/colons and is the argument VDDK sends. - `PROXY` uses `ticket.service` (`vpxa-nfc` via vCenter) for NBD. NBDSSL appends `ssl` (`vpxa-nfcssl`). The success line names the host-side endpoint (`ha-nfc` or `ha-nfcssl`). - After `200 Connect`, NBD speaks binary NFC on the raw fd. NBDSSL starts a second TLS handshake, then the same NFC protocol. ### Commands that are not used for this ticket type authd also implements FTP-style `USER` / `PASS` (and `XPAS`). Those are for local ESXi credentials. With a vCenter ticket: | Attempt | Result | | -------------------------------------------- | --------------------------------------- | | `USER` / `PASS` (vCenter account) | `530 Login incorrect` | | `USER *` / `PASS ` | `530 Login incorrect` | | `USER ` / `PASS ` | `530 Login incorrect` | | `SESSIONID ` | `530 Please login with USER and PASS` | | `CONNECT_VPXA ` (after TLS) | `530 Please login with USER and PASS` | | `SESSION ` then wait for a reply | No line until `BANNER` / `PROXY` follow | `THUMBPRINT` / `THUMBPRINT_SHA2` with the SHA-1 ticket thumbprint as argument is not what VDDK sends. The SHA-1 value is for verifying the TLS certificate, not for the `THUMBPRINT_SHA2` command. ## Python replacement | Piece | Module | Reuses pyVmomi? | | -------------------- | ---------------------------------------- | ---------------------------------- | | VIM login | `openvixdisklib.nfc_auth.connect_vim` | Yes — `SmartConnect` | | VM / host lookup | `vim.VirtualMachine` | Yes | | `HostServiceTicket` | return type of ticket call | Yes — public data object | | NFC ticket | `openvixdisklib.nfc_auth.get_nfc_ticket` | Same stub; type registered locally | | authd TLS + commands | `openvixdisklib.nfc_auth.connect_authd` | No public API | | End-to-end | `openvixdisklib.nfc_auth.authenticate` | `NfcAuthSession` | Management SHA-1 thumbprints are read with `openvixdisklib.nfc_auth.get_ssl_cert_thumbprint` (stdlib `ssl` and `hashlib`; no pyOpenSSL). Integration tests call that instead of hard-coding the lab certificate. Run: ```bash .venv/bin/pytest tests/integration/test_nfc_auth.py ``` The test completes VIM login and the authd handshake (`200 Connect`) and asserts an established TLS socket on `ticket.host:ticket.port`. ## What comes after authentication Authentication stops at `200 Connect ha-nfc` (NBD) or `200 Connect ha-nfcssl` (NBDSSL). Opening the VMDK and reading or writing sectors is documented in `docs/nfc_open.md` and implemented in `openvixdisklib/nfc_open.py`. The datastore path is consumed there (and, for writes, as `diskDeviceKey` on the ticket).