12 KiB
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:
- Logs into vCenter over HTTPS 443 (SOAP /
urn:vim25). - Asks vCenter for a one-time NFC ticket.
- 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-13098on hosthost-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://<vcenter>: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→ namespaceurn:nfc, version7.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:
<NfcGetVmFiles xmlns="urn:vim25">
<_this type="NfcService">nfcService</_this>
<vm type="VirtualMachine">vm-13098</vm>
</NfcGetVmFiles>
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):
<NfcRandomAccessOpenDisk xmlns="urn:vim25">
<_this type="NfcService">nfcService</_this>
<vm type="VirtualMachine">vm-13098</vm>
<diskDeviceKey>2000</diskDeviceKey>
<hostForAccess type="HostSystem">host-13001</hostForAccess>
</NfcRandomAccessOpenDisk>
A disk-scoped read ticket also works and returns the same
HostServiceTicket type:
<NfcRandomAccessOpenReadonly xmlns="urn:nfc">
<_this type="NfcService">nfcService</_this>
<vm type="VirtualMachine">vm-13098</vm>
<diskDeviceKey>2000</diskDeviceKey>
<hostForAccess type="HostSystem">host-13001</hostForAccess>
</NfcRandomAccessOpenReadonly>
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 <sessionId>\r\n
C -> BANNER \r\n
S -> 220 VMware Authentication Daemon Version 1.10: ...\r\n
C -> THUMBPRINT_SHA2 PlainText\r\n
S -> 200 <SHA-256 thumbprint with colons>\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:
SESSIONdoes not get a reply of its own. Waiting for a line afterSESSIONlooks like a hang.BANNERis the 7-byte commandBANNERplus a trailing space. That space is part of the token; authd strips spaces when matching some commands, soTHUMBPRINT_SHA2 <colon-thumbprint>is parsed as one token and returns501 Invalid arguments.PlainTexthas no extra spaces/colons and is the argument VDDK sends.PROXYusesticket.service(vpxa-nfcvia vCenter) for NBD. NBDSSL appendsssl(vpxa-nfcssl). The success line names the host-side endpoint (ha-nfcorha-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 <sessionId> |
530 Login incorrect |
USER <sessionId> / PASS <sessionId> |
530 Login incorrect |
SESSIONID <sessionId> |
530 Please login with USER and PASS |
CONNECT_VPXA <sessionId> (after TLS) |
530 Please login with USER and PASS |
SESSION <sessionId> 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:
.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).