Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
14d356b
nla: add CredSSP/NTLM authentication using auth_respond
Copilot Apr 4, 2026
3e6d1bd
nla: fix nil keyspec guard and strdup error check
Copilot Apr 4, 2026
326e4ff
nla: rename functions, use c->keyspec, suppress credentials for NLA, …
Copilot Apr 4, 2026
d8369fa
nla: remove redundant nil check on c->keyspec
Copilot Apr 4, 2026
373eddf
nla: guard auth.h and nlahandshake with NLATESTONLY to fix plan9port …
Copilot Apr 4, 2026
d2dda4a
nla: move nlahandshake to rpc.c, remove ifdef guards
Copilot Apr 4, 2026
fc49a22
rpc: simplify strdup assignment in nlahandshake
Copilot Apr 4, 2026
aeeca0b
address review: rename ntlm→nt, use gbtag/gblen, simplify signatures,…
Copilot Apr 5, 2026
8433166
nla: add named constants for CredSSP context-specific tags
Copilot Apr 5, 2026
6b45d4a
nla: fix build failures: NTLM in constants/comments, restore TESTHFIL…
Copilot Apr 5, 2026
8425559
nla: replace magic DER tag bytes with named constants in writetsreq/r…
Copilot Apr 5, 2026
33cf896
nla: rename TagOStr→TagOctetString, name BER magic numbers
Copilot Apr 5, 2026
9be022e
nla: add len comment, use putder for int length, consolidate p+= in m…
Copilot Apr 5, 2026
44d30c0
nla: unfold mkntauth field groups to one statement per line
Copilot Apr 5, 2026
8cfbbea
rpc: fix phase comments to say NTLM Negotiate/Challenge/Authenticate
Copilot Apr 5, 2026
db0e50f
README: document NLA in Options, High-level architecture, Repository …
Copilot Apr 5, 2026
4cd0c93
nla: add NTLMv1 Extended Session Security (ESS) support
Copilot Apr 5, 2026
ebceb92
nla: role=client in keyspec template; inline NTLMFlags
Copilot Apr 5, 2026
6bbba4b
nla: add hex dump of NTLM Challenge packet for debugging
Copilot Apr 6, 2026
cc190f9
nla: extract TargetName from NTLM Challenge and use as DomainName in …
Copilot Apr 6, 2026
daecabc
nla: fix NtChallengeResponse – use NTresp[24] from factotum MSchaprep…
Copilot Apr 6, 2026
0ce1b28
nla: implement CredSSP credential delegation (Phases D and E)
Copilot Apr 6, 2026
265865a
nla: fix build failures – restore missing /*, setupRC4state
Copilot Apr 6, 2026
f64bdb5
nla: add debug prints to trace hang in CredSSP Phases C–E
Copilot Apr 6, 2026
416a631
nla: fix debug print ordering – move prints before blocking calls
Copilot Apr 6, 2026
593fb44
nla: compute NT response directly from password; add ntrespfrompasswd…
Copilot Apr 6, 2026
9e58cc4
nla: fix CI failure — des_ecb_encrypt → desECBencrypt (Plan 9 libsec …
Copilot Apr 6, 2026
575f4f0
nla: downgrade to CredSSP v2 (no clientNonce), v2-compatible Phase E …
Copilot Apr 6, 2026
dac5014
nla: fix testmkntnego/testmkntauth flags; fix NfESS incorrectly set w…
Copilot Apr 6, 2026
2cf18f2
nla: fix testmktsreqhdr expected version byte (CredSSPVer=2 not 5)
Copilot Apr 6, 2026
10b7e03
nla: implement Early User Authorization Result PDU (PROTOCOL_HYBRID_EX)
Copilot Apr 6, 2026
26c3a91
nla: detect EUARP at Phase B in PROTOCOL_HYBRID_EX mode
Copilot Apr 6, 2026
acd19af
nla: upgrade to CredSSP v5 (clientNonce + HMAC-SHA256 pubKeyAuth); fi…
Copilot Apr 6, 2026
7e3a541
nla: remove NfSign|NfSeal from NTLM Negotiate/Authenticate flags
Copilot Apr 6, 2026
8b4de13
nla: implement NTLMv2 authentication (required by Windows 10+)
Copilot Apr 7, 2026
ea1b5ff
nla: address code review feedback: zero sesskey, add NfESS comment
Copilot Apr 7, 2026
9e8b761
nla: add MIC to NTLM Authenticate for CredSSP Phase D
Copilot Apr 7, 2026
6ed1fc7
nla: add debug prints for ExportedSessionKey, MIC, ntnego, and ntauth…
Copilot Apr 7, 2026
db21737
nla: fix NTLM flags, add NfKeyExch+EncryptedRandomSessionKey for Cred…
Copilot Apr 7, 2026
85aafcf
nla: remove unnecessary n=0 resets in debug print loops
Copilot Apr 7, 2026
dbb102f
rpc.c: restore missing x224hangup function signature (syntax error at…
Copilot Apr 7, 2026
947d9ee
nla: fix NTLM flag bit positions (NfAlwaysSign, NfESS, Nf128, NfKeyEx…
Copilot Apr 7, 2026
2e04f30
nla: add EPA channel bindings (MsvAvChannelBindings + MsvAvTargetName…
Copilot Apr 7, 2026
483e26e
nla: fix NTLMv2 blob length (bloblen = 28 + mtilen, not 32)
Copilot Apr 7, 2026
e0468ef
fix build: move NTv2RespMax and related constants to fns.h
Copilot Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Rd follows the conventional Plan 9 build workflow:
## Usage

```
rd [-0A] [-T title] [-a depth] [-c wdir] [-d dom] [-k keyspec] [-s shell] [net!]server[!port]
rd [-0AN] [-T title] [-a depth] [-c wdir] [-d dom] [-k keyspec] [-s shell] [net!]server[!port]
```

Rd takes exactly **one non-optional argument**: a **server connection string**.
Expand All @@ -82,13 +82,16 @@ Examples:
### Options

- `-k keyspec`
Adds extra attributes to the **factotum** key query used to obtain credentials (the attributes are appended to the query passed to `auth_getuserpasswd`).
Adds extra attributes to the **factotum** key query used to obtain credentials. With plain TLS logon the attributes are appended to the `proto=pass service=rdp` query passed to `auth_getuserpasswd`. With `-N` (NLA) they are appended to the `proto=mschap service=rdp` query passed to `auth_respond`.

- `-d dom`
Specifies a **Windows domain name** to authenticate against (for domain logons).

- `-N`
Enables **Network Level Authentication (NLA)** using CredSSP/NTLM. After TLS is established, Rd performs an NTLM handshake encapsulated in ASN.1 DER `TSRequest` messages (MS-CSSP protocol). The NT response is computed by **factotum** via `auth_respond` with `proto=mschap`, so the plaintext password never leaves factotum. NLA is required by some Windows configurations and provides pre-session authentication.

- `-A`
Disables factotum-based “auto logon”. With `-A`, Rd requests the server to present an interactive **logon screen** instead, and credentials are entered and validated inside the remote GUI session. At that stage, credential submission occurs over an **encrypted channel**.
Disables factotum-based “auto logon”. With `-A`, Rd requests the server to present an interactive **logon screen** instead, and credentials are entered and validated inside the remote GUI session. At that stage, credential submission occurs over an **encrypted channel**. (Not applicable with `-N`: NLA always authenticates before the session starts.)

- `-T title`
Customizes the local window label. By default it is:
Expand Down Expand Up @@ -122,12 +125,14 @@ A quick overview of the runtime flow and the major modules.

Connection setup follows a staged pipeline:

1. Parse command-line options (title, depth, domain, shell override, working directory, console attach).
2. Optionally obtain credentials from factotum.
1. Parse command-line options (title, depth, domain, shell override, working directory, console attach, NLA flag).
2. Optionally obtain credentials from factotum (skipped when `-N` NLA is used, as factotum is consulted during the NLA handshake instead).
3. `dial()` the server (default TCP port 3389).
4. Perform **X.224** transport/session handshake.
5. Initialize the local screen/window.
6. Perform the main **RDP handshake** (negotiation/capabilities/licensing/activation).
4. Perform **X.224** transport/session handshake (negotiating TLS or CredSSP as the security protocol).
5. Upgrade the connection to **TLS** (`starttls`).
6. If `-N` was given, perform the **NLA (CredSSP/NTLM) handshake** (`nlahandshake`): a three-message NTLM exchange (Negotiate → Challenge → Authenticate) wrapped in ASN.1 DER `TSRequest` structures, with the NT response computed by factotum.
7. Initialize the local screen/window.
8. Perform the main **RDP handshake** (negotiation/capabilities/licensing/activation).

### 3) Concurrency model: network loop + local input helpers

Expand Down Expand Up @@ -183,6 +188,7 @@ Rd includes a virtual-channel abstraction (`Vchan`) that:
**Security:**

- `tls.c` / `tls9f.c` — TLS support and certificate/thumbprint verification glue (build selects the appropriate file)
- `nla.c` — Network Level Authentication (NLA/CredSSP): NTLM message serialisers/parsers (`mkntnego`, `mkntauth`, `getntchal`) and `TSRequest` ASN.1 DER framing (`writetsreq`, `readtsreq`, `gettsreq`)

**Rendering and graphics updates:**

Expand All @@ -201,7 +207,7 @@ Rd includes a virtual-channel abstraction (`Vchan`) that:
**Virtual channels and extensions:**

- `vchan.c` — virtual channel table setup, fragmentation/defragmentation, and send/dispatch support
- `rpc.c` — higher-level request/response helpers used by some channel-style interactions
- `rpc.c` — higher-level request/response helpers; also contains `nlahandshake()`, the CredSSP orchestration that drives the three-message NTLM exchange via factotum
- `audio.c` — audio virtual channel handling and client-side playback hooks
- `efs.c` — extension/virtual channel support for device-redirection-style messages (see `Efsmsg` in `dat.h`)

Expand All @@ -212,6 +218,7 @@ Rd includes a virtual-channel abstraction (`Vchan`) that:
- `aud_test.c` — audio-related tests
- `efs_test.c` — tests for EFS/extension encoding/decoding
- `msg_test.c` — message parsing/encoding tests
- `nla_test.c` — NLA serialiser/parser tests (`writetsreq`/`gettsreq`, `mkntnego`, `getntchal`, `mkntauth`)

**Headers:**

Expand Down
5 changes: 5 additions & 0 deletions dat.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ struct Rdp
char *passwd; /* password for auto logon (sic) */
char *shell; /* remote shell override */
char *rwd; /* remote working directory */
char *keyspec; /* factotum key spec */
int nla; /* use NLA (CredSSP/NTLM) authentication */
char *server; /* server hostname (for NTLM SPN: "TERMSRV/<server>") */
uchar *tlscert; /* TLS server certificate DER (for CredSSP pubKeyAuth) */
int tlscertlen;
int xsz; /* rfb dimensions */
int ysz; /* rfb dimensions */
int depth; /* rfb color depth */
Expand Down
2 changes: 2 additions & 0 deletions efs_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ int audiotests(void);
int egditests(void);
int msgtests(void);
int mppctests(void);
int nlatests(void);
int rletests(void);
int utf16tests(void);

Expand Down Expand Up @@ -132,6 +133,7 @@ main(int, char**)
egditests();
msgtests();
mppctests();
nlatests();
rletests();
utf16tests();
print("ok\n");
Expand Down
26 changes: 26 additions & 0 deletions fns.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,32 @@ int istpkt(uchar*,uchar*);
int tptype(uchar*,uchar*);
uchar* tpdat(uchar*,uchar*);

/* nla.c buffer sizes (also used by rpc.c) */
enum
{
MaxNTLMTargetInfo = 1024, /* maximum TargetInfo AvPairs length from challenge */
MaxNTLMClientAvExtra = 8 + (4+16) + (4+512) + 4, /* MsvAvFlags+MsvAvChannelBindings+MsvAvTargetName+EOL */
NTv2RespMax = 16 + 28 + MaxNTLMTargetInfo + MaxNTLMClientAvExtra, /* max NTLMv2 NtChallengeResponse */
};

/* nla.c */
int mkntnego(uchar*, int);
int getntchal(uchar[8], uchar*, int);
uchar* getntargetinfo(uchar*, int, int*);
int ntv2frompasswd(char*, char*, char*, uchar*, uchar*, uchar*, int, uchar*, int, uchar*, uchar*, uchar*, int, char*);
int mkntauth(uchar*, int, char*, char*, uchar*, int, uchar*, uchar*);
void ntrespfrompasswd(char*, uchar[8], uchar[24]);
int writetsreq(int, uchar*, int);
int writetsreqnonce(int, uchar*, int, uchar*, int);
int writetsreqdone(int, uchar*, int, uchar*, int);
int nlafinish(int, uchar*, int, uchar*, char*, char*, char*, uchar*);
int readtsreq(int, uchar*, int);
int readtsreq_oreuarp(int, uchar*, int, ulong*);
uchar* gettsreq(uchar*, int, int*);

/* rpc.c */
int nlahandshake(Rdp*);

/* rd.c */
void atexitkiller(void);
void atexitkill(int pid);
Expand Down
4 changes: 3 additions & 1 deletion mkfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ OFILES=\
mpas.$O\
mppc.$O\
msg.$O\
nla.$O\
rd.$O\
rpc.$O\
utf16.$O\
Expand All @@ -31,12 +32,13 @@ OFILES=\

THREADOFILES=${OFILES:rd.$O=rd-thread.$O}
CLEANFILES=$O.thread $O.test
TESTHFILES=audio.c mppc.c rle.c egdi.c
TESTHFILES=audio.c mppc.c rle.c egdi.c nla.c
TESTOFILES=\
efs_test.$O errs.$O efs.$O utf16.$O \
aud_test.$O \
msg_test.$O x224.$O mcs.$O ele.$O mpas.$O alloc.$O cap.$O egdi_test.$O \
mppc_test.$O \
nla_test.$O \
rle_test.$O \
utf16_test.$O \

Expand Down
44 changes: 44 additions & 0 deletions msg_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,28 @@ testputmsgxconnect(void){

}

static int
testputmsgxconnectnla(void)
{
/* Xconnect advertising HYBRID+HYBRID_EX (ProtoCSSP|ProtoUAUTH) for NLA */
int n;
char *s, *want;
uchar buf[1042];
Msg m;

m.type = Xconnect;
m.negproto = ProtoCSSP | ProtoUAUTH;
n = putmsg(buf, sizeof buf, &m);
if(n < 0)
sysfatal("testputmsgxconnectnla: unexpected error: %r\n");
s = smprint("%.*H", n, buf);
want = "0300002722E00000000000436F6F6B69653A206D737473686173683D780D0A010008000A000000";
if(strcmp(s, want) != 0)
sysfatal("testputmsgxconnectnla: want %s, got %s", want, s);
free(s);
return 0;
}

static int
testputmsgxhangup(void){
int n;
Expand Down Expand Up @@ -572,6 +594,26 @@ testgetmsgxconntls(void)
return 0;
}

static int
testgetmsgxconnhybridex(void)
{
/* X.224 connection confirm with HYBRID_EX (ProtoUAUTH) selected by server */
int n, nb;
uchar buf[32];
Msg m = {0};
char *hex = "0300001306D000000000000200080008000000";

nb = dec16(buf, sizeof buf, hex, strlen(hex));
n = getmsg(&m, buf, nb);
if(n <= 0)
sysfatal("testgetmsgxconnhybridex: unexpected error: %r");
if(m.type != Xconnected)
sysfatal("testgetmsgxconnhybridex: type: want %d, got %d", Xconnected, m.type);
if(m.negproto != ProtoUAUTH)
sysfatal("testgetmsgxconnhybridex: negproto: want %d (ProtoUAUTH), got %d", ProtoUAUTH, m.negproto);
return 0;
}

static int
testgetmsgxconnnonego(void)
{
Expand Down Expand Up @@ -1306,6 +1348,7 @@ msgtests(void)
{
fmtinstall('H', encodefmt);
testputmsgxconnect();
testputmsgxconnectnla();
testputmsgxhangup();
testputmsgmattach();
testputmsgmjoin();
Expand All @@ -1326,6 +1369,7 @@ msgtests(void)
testgetmsgfp2();
testgetmsgfp3();
testgetmsgxconntls();
testgetmsgxconnhybridex();
testgetmsgxconnnonego();
testgetmsgmattacheduid();
testgetmsgmattachednouid();
Expand Down
Loading
Loading