
CloudsStormsSunsetsSunrises / Karsun Designs
Recently, I had to setup a SPNEGO example to demonstrate a Kerberos identity integration. The details aren’t important, but I spent a bit of time figuring out how to use curl’s SPENGO support. So, we have the next blog post topic.
If you search for this topic on Google (or DuckDuckGo as I tend to do), the first useful link on this topic is from Niklas Ottosson’s Tech Blog. However, I want to make it work without the dependency on a JDK/JRE — just out-of-the-box Microsoft Windows installation.
Note, none of the hostnames, users, or SPNs referenced in this post exist. I use my iyasec.io domain name in the examples as I always do.
In this example, I was using an EC2 instance running Windows Server 2025 as a KDC. From the base installation, I installed and configured Active Directory Domain Services — see the setup instructions. We’ll call the forest iyasec.io (DC=iyasec, DC=com).
If you are running Linux on your device like I am, the following will get you an RDP session:
$ xfreerdp /u:"Administrator" /v:"blah.compute.amazonaws.com:3389" /drive:myshare,/local/path/to/share
Administrator is the user, blah.compute.amazonaws.com is the hostname, myshare is the name of the local drive mapping on the Windows Server instance, and the /local/path/to/share is the path to share on your device.
The basic setup of Windows/AD configuration objects can be found in the first half of this tutorial.
In order for this to work, there must a service / server that has a DNS entry in a local DNZ zone file. So, let’s create a DNS zone and define app1.iyasec.io in our DNS setup on the Windows Server — this can point right back to the Windows Server, if you don’t have anything better. The KDC is presumably the Windows Server instance that was just promoted to a Domain Controller.
A regular AD user was created using the AD User & Computers management screen in Server Manager. See the tutorial mentioned above. We’ll assume the user was called user1 for this tutorial. The DN would be CN=user1,OU=Users, DC=IyaSec,DC=io. Ensure that you’ve enabled all the check boxes under the Account tab for this user under Tools->Active Directory Users and Computers” that are described in the tutorial. Most notably,
- User cannot change password.
- Password never expires.
- This account supports Kerberos AES 128 bit encryption.
- This account support Kerberos AES 256 bit encryption.
- Do not require Kerberos Preauthentication.
Run the following command from a Command window (run as administrator) to create an Service Principal Name (SPN):
C:\Users\Administrator> setspn -a HTTP/app1.iyasec.io@iyasec.io user1
Verify that the new SPN is attached to the AD User (again, from the Windows Command prompt):
C:\Users\Administrator> setspn -L user1
Registered ServicePrincipalNames for CN=user1,CN=Users,DC=iyasec,DC=io:
HTTP/app1.iyasec.io
Go back into the Server Manager. Go to the “Tools->Active Directory Users and Computers” section. Find the app1 user. Go to the Account tab of the user’s properties. If the User login name doesn’t match the format HTTP/user.domain.com@domain.com (in our example above, that is HTTP/app1.iyasec.io@iyasec.io), update this field. The original instructions claimed this field would be automatically populated, it wasn’t for me in Windows Server 2025.
We need to locally authenticate the app1 user on the domain controller. If you have the Administrator user password or are an administrator yourself, you can just go the next section and skip this step. If you do not have this level of access, then you can attempt the following. You can try this tutorial to grant local logon access and then run:
C:\Users\Administrator> runas /user:IYASEC\user1 "klist get HTTP/app1.iyasec.io@iyasec.io"
If you are on a non-domain-controller Windows Server or workstation, a slightly different set of permissions is needed.
Run the following command (again, from the Windows Command prompt):
C:\Users\Administrator> klist get HTTP/app1.iyasec.io@iyasec.io
Current LogonId is 0:0x8b1a7
A ticket to HTTP/app1.iyasec.io@iyasec.io has been retrieved successfully.
Cached Tickets: (2)
#0> Client: user1 @ IYASEC.IO
Server: krbtgt/IYASEC.IO @ IYASEC.IO
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Ticket Flags 0x40c10000 -> forwardable renewable initial name_canonicalize
Start Time: 11/18/2025 13:13:56 (local)
End Time: 11/18/2025 23:13:56 (local)
Renew Time: 11/25/2025 13:13:56 (local)
Session Key Type: AES-256-CTS-HMAC-SHA1-96
Cache Flags: 0x1 -> PRIMARY
Kdc Called: WINSERVER1
#1> Client: user1 @ IYASEC.IO
Server: HTTP/app1.iyasec.io @ IYASEC.IO
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Ticket Flags 0x40850000 -> forwardable renewable ok_as_delegate name_canonicalize
Start Time: 11/18/2025 13:13:56 (local)
End Time: 11/18/2025 23:13:56 (local)
Renew Time: 11/25/2025 13:13:56 (local)
Session Key Type: AES-256-CTS-HMAC-SHA1-96
Cache Flags: 0
Kdc Called: WINSERVER1
This will retrieve (or update) the local Kerberos ticket cache that are needed to access the app1 server / service1 with curl. See the Microsoft documentation for more information.
To run the curl command with SPNEGO support on Windows, run the following:
C:\Users\Administrator> curl -X --negotiate -u : https://app1.iyasec.io -v
Note: Using embedded CA bundle (230814 bytes)
Note: Using embedded CA bundle, for proxies (230814 bytes)
* Trying x.x.x.x:443...
* ALPN: curl offers h2,http/1.1
...
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: CN=api1.iyasec.io
...
* SSL certificate verification failed, continuing anyway!
* Established connection to app1.iyasec.io (x.x.x.x port 443) from x.x.x.x port 63195
* using HTTP/1.x
* Server auth using Negotiate with user ''
> GET / HTTP/1.1
> Host: app1.iyasec.io
> Authorization: Negotiate YIIG2QYGKwYBBQUCoIIGzTCCBsmgMDAuBgkqhkiC9xIBAgIGCSqGSI...mWt6jIXH/OOhxY=
> User-Agent: curl/8.17.0
> Accept: */*
> Content-Type: text/html
> Content-Length: 2349
>
* upload completely sent off: 526 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 200 Success
< pragma: no-cache
< cache-control: private, max-age=0, no-cache, no-store
< content-type: text/html
...
You can see the AP-REQ embedded in the SPNEGO negotiate request in the Authorization request header above. An analysis of what the base64-encoded blob following the “Negotiate: “ string means is explored in “Kerberos Wireshark Captures: A SPNEGO Example”. The structure of the kerberos AP-REQ message is explored in “Kerberos and Windows Security: Kerberos V5 Protocol”. We’re not going to go through all that again here.
Notes:
- AI / GenAI / ChatGPT / etc were not used to generate this article.
- None of the hostname or users listed actually exist.
- Feel free to post any comments or suggestions below.
- Image: CloudsStormsSunsetsSunrises / Karsun Designs