bolkedebruin / rdpgw

Remote Desktop Gateway in Go for deploying on Linux/BSD/Kubernetes
Apache License 2.0
757 stars 119 forks source link

No success with tokeninfo #106

Open beatrubi opened 5 months ago

beatrubi commented 5 months ago

I manged to pass a user token from rdpgw through xrdp with enable_token_login=true and pam-jwt back to rdpgw.

The generated rdp file contains the username with concatenated token:

gatewayhostname:s:rdpgw.lab.0x1b.ch:8443
full address:s:ubuntu03.lab.0x1b.ch:3389
username:s:beat^_eyJhbGciOiJkaXIiLCJjdHkiOiJKV1QiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiemlwIjoiREVGIn0..kyekj2rYsFoUl781IkGG0w.x8vf2eBx_szYtYGfuYjtoTD33DBUENzvhPRjAWaho7zZw1BVgXjyPnmqkqiZdAbuBTMKuwN6YIGDsiN1gvIzLQ.b82or-ul-bCkDmtvUVMmbg
gatewaycredentialssource:i:5
gatewayprofileusagemethod:i:1
gatewayusagemethod:i:1
...

xrdp-sesman successfully calls the PAM module:

Apr 11 20:28:05 ubuntu03 xrdp[8765]: [INFO ] sending login info to session manager, please wait...
Apr 11 20:28:05 ubuntu03 pam-jwt[8613]: token url set to https://rdpgw.lab.0x1b.ch:8443/tokeninfo?access_token

According to rdpgw's log I get the identical token back to validate - sadly no success:

Apr 11 20:28:05 rdpgw.lab.0x1b.ch docker[9665]: rdpgw  | 2024/04/11 18:28:05 Identity SessionId: b74ac558-9a75-40ab-a611-18a73ad09964, UserName: : Authenticated: false: Path: /tokeninfo?access_token=eyJhbGciOiJkaXIiLCJjdHkiOiJKV1QiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiemlwIjoiREVGIn0..kyekj2rYsFoUl781IkGG0w.x8vf2eBx_szYtYGfuYjtoTD33DBUENzvhPRjAWaho7zZw1BVgXjyPnmqkqiZdAbuBTMKuwN6YIGDsiN1gvIzLQ.b82or-ul-bCkDmtvUVMmbg
Apr 11 20:28:05 rdpgw.lab.0x1b.ch docker[9665]: rdpgw  | 2024/04/11 18:28:05 Cannot decrypt token go-jose/go-jose: missing payload in JWS message

Now I'm lost, the encoder and decoder for the user token goes beyond my Go skills 😬 I'm not sure if I screwed something up or if the code to encode and decode the limited JWT do not match.

For completeness my rdpgw.conf:

Server:
 CertFile: /opt/rdpgw/server.pem
 KeyFile: /opt/rdpgw/key.pem
 GatewayAddress: rdpgw.lab.0x1b.ch:8443
 Port: 8443
 Hosts:
  - ubuntu01.lab.0x1b.ch:3389
  - ubuntu02.lab.0x1b.ch:3389
  - ubuntu03.lab.0x1b.ch:3389
 HostSelection: unsigned
 SessionKey: d505d0d5ce23977b9d544e224692f77d
 SessionEncryptionKey: f088998ac5b017a2593f211a8ce39ca9
OpenId:
 ProviderUrl: https://rdpgw.lab.0x1b.ch/auth/realms/demo
 ClientId: rdpgw
 ClientSecret: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Client:
 UsernameTemplate: "{{ username }}\x1f{{ token }}"
 NetworkAutoDetect: 0
 BandwidthAutoDetect: 1
 ConnectionType: 6
Security:
  PAATokenSigningKey: fc4c2095bc3409375461bc9ef36f7b89
  PAATokenEncryptionKey: 8188742ac3cf0efe67805a9797d2a349
  UserTokenEncryptionKey: d3ecd7e565e56e37e2f2e95b584d8c0c
  UserTokenSigningKey: 5aa3a1568fe8421cd7e127d5ace28d2d
  VerifyClientIp: true
  EnableUserToken: true
Caps:
 TokenAuth: true
bolkedebruin commented 5 months ago

I fixed this is master now (I think). Issue was that the UserTokenSigningKey wasn't used as it makes the token bigger and we have very limited space.

So for your version can can remove UserTokenSigningKey and I think it will work. Or switch to the latest image (no release yet) and then signing will be done (with the caveat above). Also the new version supports not signing and just encryption when UserTokenSigningKey is omitted.

beatrubi commented 5 months ago

Thanks @bolkedebruin for the quick fix! I had to add the possibility of providing an empty UserTokenSigningKey, now the token based login works nicely.

BuJo commented 4 months ago

Just in case someone else wonders about the limits like me and bashes their head against this...

As far as I can see, the reason why the limit is 255 bytes, is that the X.224 length field is a single byte.

This is a view into the TCP packet for the initial RDP connection with the .rdp file:

Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.1.1
Transmission Control Protocol, Src Port: 35334, Dst Port: 3389, Seq: 1, Ack: 1, Len: 263
TPKT, Version: 3, Length: 263
    Version: 3
    Reserved: 0
    Length: 263
ISO 8073/X.224 COTP Connection-Oriented Transport Protocol
    Length: 2
    PDU Type: CR Connect Request (0x0e)
    Destination reference: 0x0000
    Source reference: 0x0000
    0000 .... = Class: 0
    .... ..0. = Extended formats: False
    .... ...0 = No explicit flow control: False
Remote Desktop Protocol
    Routing Token/Cookie [truncated]: Cookie: mstshash=LOCALHOST\USERNAME\x1FeyJhbGciOiJkaXIiLCJjdHkiOiJKV1QiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiemlwIjoiREVGIn0.......
    Type: RDP Negotiation Request (0x01)
    Flags: 0x00
    Length: 8
...

xrdp will note something like:

[ERROR] Invalid length indicator in [ITU-T X.224] CR-TPDU (Connection Request). expected 258, received 2

In case the username field is too long, the single byte length field is simply being rolled over, the Cookie: mstshash= value being too long.

It's not that the JWT token has to be less than 511 (or 255) bytes, all of it together hast to fit in the length field.

The cookie contains a hostname, the username, the byte \x1f and the JWT token. The longer each one is, the less likely it is to work.

Signing the Token using UserTokenSigningKey increases the size of the JWT from 213 to 298 bytes for me, making it impossible to fit, hence no signing key is a good start.

So 255-7-28-1-1+2 = 218 bytes left for host/user/token, or just 3 bytes for host/user, depending on the length of the username because it's also encoded in the token. I'm not sure if I'm missing anything or if the host can be skipped (must be a client thing, cursory look into xfreerdp was unsuccessful, xrdp simply ignores it)...

Hopefully not embarrassing myself on the internet.... :roll_eyes:

bolkedebruin commented 4 months ago

Thanks @BuJo - you did not embarrass yourself. I think you've summarized it pretty well.

beatrubi commented 4 months ago

Thanks @BuJo for the explanation - it's good to understand what happens.

So basically to make use of a token based SSO, we have to skip the signing part. It's probably not the best from a security standpoint, at least it works on my machine. I had to edit the code to do so, in the current state an empty UserTokenSigningKey is filled with a random value and I had to disable this step.

From my understanding we have to disable UserTokenSigningKey randomization or - if a skip of signing the token is too dangerous - skip auto login as a whole 🤔 Any opinions from your side, @bolkedebruin?

BuJo commented 4 months ago

Not a cryptographer nor security personell, but here is my take:

As there is only a single entity for which the token is encrypted to, there is as far as I can see no need for a signature, as there is only one who can encrypt and decrypt it. Only in distributed models, where the multiple entities can encrypt the same thing (for example in public-key cryptography) there is a need for signatures to build trust.

It think it's safe to not use the signature.

The out of band connection between rdpgw and pam-jwt seems to me the linchpin.

The flow of the JWT token:

flowchart LR
  rdpgw --https--> user
  user --> rdp-client
  rdp-client --> xrdp
  xrdp --> xrdp-sesman
  xrdp-sesman --> pam-jwt
  pam-jwt --https--> rdpgw

As long as the library checks that the returned JWT actually uses the encryption key, encryption key did not leak, TLS v1.3 is used for perfect forward secrecy, the https terminates in rdpgw somewhat directly, the https certificates have not been leaked, or rdpgw/pam-jwt sharing the same Host.. should be safe.