Skip to content

Commit 559d9be

Browse files
authored
Merge pull request #898 from TheManticoreProject/kerberos-native
Native Kerberos v5: iana constants, RFC 8009 crypto, and message layer (phases 0–2)
2 parents ed8de68 + 37d529f commit 559d9be

58 files changed

Lines changed: 22158 additions & 377 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

go.mod

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,13 @@ require (
77
github.com/TheManticoreProject/winacl v1.3.1
88
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667
99
github.com/go-ldap/ldap/v3 v3.4.12
10-
github.com/jcmturner/gokrb5/v8 v8.4.4
1110
golang.org/x/crypto v0.43.0
1211
golang.org/x/term v0.36.0
1312
)
1413

1514
require (
1615
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
17-
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e // indirect
1816
github.com/google/uuid v1.6.0 // indirect
19-
github.com/hashicorp/go-uuid v1.0.3 // indirect
20-
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
21-
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
22-
github.com/jcmturner/gofork v1.7.6 // indirect
23-
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
24-
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
2517
golang.org/x/net v0.46.0 // indirect
2618
golang.org/x/sys v0.37.0 // indirect
2719
)

go.sum

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ github.com/TheManticoreProject/winacl v1.3.1 h1:LR8e914481tiGx/bvchieCq3rYQccoiB
66
github.com/TheManticoreProject/winacl v1.3.1/go.mod h1:Xji/qLzY8AkdBNXRgOS7g+UBEuqMQBBZllr+rl7Hzko=
77
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
88
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
9-
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
109
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1110
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1211
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
@@ -15,11 +14,6 @@ github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s
1514
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
1615
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
1716
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
18-
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
19-
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
20-
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
21-
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
22-
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
2317
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
2418
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
2519
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
@@ -36,54 +30,15 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ
3630
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
3731
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3832
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
39-
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
40-
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
41-
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
42-
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
43-
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
44-
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
4533
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
4634
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
47-
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
48-
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
49-
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
50-
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
5135
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
5236
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
53-
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
54-
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
55-
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
56-
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
57-
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
58-
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
59-
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
6037
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
6138
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
62-
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
63-
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
64-
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
65-
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
66-
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
67-
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
68-
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
69-
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7039
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
7140
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
72-
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
73-
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
74-
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
7541
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
7642
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
77-
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
78-
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
79-
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
80-
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
81-
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
82-
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
83-
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
84-
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
85-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
86-
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
87-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
8843
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
8944
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Package attacks provides the offensive-tooling surface built on the native
2+
// Kerberos primitives: hashcat-compatible hash formatting for AS-REP roasting
3+
// and Kerberoasting. The network operations live on the client
4+
// (kerberos.ASREPRoast, KerberosClient.Kerberoast); this package turns their
5+
// encrypted output into crackable hash strings.
6+
package attacks
7+
8+
import (
9+
"encoding/hex"
10+
"fmt"
11+
12+
"github.com/TheManticoreProject/Manticore/network/kerberos/v5/iana"
13+
)
14+
15+
// aesMacLen returns the truncated-HMAC length appended by an AES enctype's
16+
// encrypt-then/over-MAC construction, which is where the crackable checksum sits
17+
// in the ciphertext for AES Kerberoast/AS-REP hashes.
18+
func aesMacLen(etype int) int {
19+
switch etype {
20+
case iana.ETypeAES128CTSHMACSHA196, iana.ETypeAES256CTSHMACSHA196:
21+
return 12
22+
case iana.ETypeAES128CTSHMACSHA256:
23+
return 16
24+
case iana.ETypeAES256CTSHMACSHA384:
25+
return 24
26+
default:
27+
return 0
28+
}
29+
}
30+
31+
// splitCipher separates a KDC-REP encrypted part into the checksum and the
32+
// remaining encrypted data, as the hashcat formats expect.
33+
//
34+
// - RC4-HMAC (23): the ciphertext is checksum(16) || edata, so the checksum is
35+
// the first 16 bytes.
36+
// - AES: the ciphertext is edata || HMAC, so the checksum is the trailing
37+
// 12/16/24 bytes depending on the enctype.
38+
func splitCipher(etype int, cipher []byte) (cksum, edata []byte, err error) {
39+
switch etype {
40+
case iana.ETypeRC4HMAC:
41+
if len(cipher) < 16 {
42+
return nil, nil, fmt.Errorf("attacks: RC4 ciphertext too short (%d bytes)", len(cipher))
43+
}
44+
return cipher[:16], cipher[16:], nil
45+
case iana.ETypeAES128CTSHMACSHA196, iana.ETypeAES256CTSHMACSHA196,
46+
iana.ETypeAES128CTSHMACSHA256, iana.ETypeAES256CTSHMACSHA384:
47+
m := aesMacLen(etype)
48+
if len(cipher) < m {
49+
return nil, nil, fmt.Errorf("attacks: AES ciphertext too short (%d bytes)", len(cipher))
50+
}
51+
return cipher[len(cipher)-m:], cipher[:len(cipher)-m], nil
52+
default:
53+
return nil, nil, fmt.Errorf("attacks: unsupported etype %d for hash formatting", etype)
54+
}
55+
}
56+
57+
// FormatASREPHash formats an AS-REP encrypted part as a hashcat AS-REP-roast
58+
// hash. RC4 (etype 23) yields the mode-18200 form
59+
// "$krb5asrep$23$user@realm:<checksum>$<edata>"; AES enctypes use the analogous
60+
// "$krb5asrep$<etype>$user@realm:<checksum>$<edata>".
61+
func FormatASREPHash(username, realm string, etype int, cipher []byte) (string, error) {
62+
cksum, edata, err := splitCipher(etype, cipher)
63+
if err != nil {
64+
return "", err
65+
}
66+
return fmt.Sprintf("$krb5asrep$%d$%s@%s:%s$%s",
67+
etype, username, realm, hex.EncodeToString(cksum), hex.EncodeToString(edata)), nil
68+
}
69+
70+
// FormatTGSHash formats a service ticket's encrypted part as a hashcat
71+
// Kerberoast hash. RC4 (etype 23) yields the mode-13100 form
72+
// "$krb5tgs$23$*account$realm$spn*$<checksum>$<edata>"; AES enctypes yield the
73+
// mode-19600/19700 form "$krb5tgs$<etype>$account$realm$<checksum>$<edata>"
74+
// (which carries no SPN, matching hashcat's format).
75+
func FormatTGSHash(account, realm, spn string, etype int, cipher []byte) (string, error) {
76+
cksum, edata, err := splitCipher(etype, cipher)
77+
if err != nil {
78+
return "", err
79+
}
80+
if etype == iana.ETypeRC4HMAC {
81+
return fmt.Sprintf("$krb5tgs$23$*%s$%s$%s*$%s$%s",
82+
account, realm, spn, hex.EncodeToString(cksum), hex.EncodeToString(edata)), nil
83+
}
84+
return fmt.Sprintf("$krb5tgs$%d$%s$%s$%s$%s",
85+
etype, account, realm, hex.EncodeToString(cksum), hex.EncodeToString(edata)), nil
86+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package attacks
2+
3+
import (
4+
"encoding/hex"
5+
"strings"
6+
"testing"
7+
8+
"github.com/TheManticoreProject/Manticore/network/kerberos/v5/iana"
9+
)
10+
11+
// Exact example hashes from the hashcat wiki (example_hashes), used as vectors.
12+
const (
13+
exASREP23 = "$krb5asrep$23$user@domain.com:3e156ada591263b8aab0965f5aebd837$007497cb51b6c8116d6407a782ea0e1c5402b17db7afa6b05a6d30ed164a9933c754d720e279c6c573679bd27128fe77e5fea1f72334c1193c8ff0b370fadc6368bf2d49bbfdba4c5dccab95e8c8ebfdc75f438a0797dbfb2f8a1a5f4c423f9bfc1fea483342a11bd56a216f4d5158ccc4b224b52894fadfba3957dfe4b6b8f5f9f9fe422811a314768673e0c924340b8ccb84775ce9defaa3baa0910b676ad0036d13032b0dd94e3b13903cc738a7b6d00b0b3c210d1f972a6c7cae9bd3c959acf7565be528fc179118f28c679f6deeee1456f0781eb8154e18e49cb27b64bf74cd7112a0ebae2102ac"
14+
exTGS23 = "$krb5tgs$23$*user$realm$test/spn*$63386d22d359fe42230300d56852c9eb$891ad31d09ab89c6b3b8c5e5de6c06a7f49fd559d7a9a3c32576c8fedf705376cea582ab5938f7fc8bc741acf05c5990741b36ef4311fe3562a41b70a4ec6ecba849905f2385bb3799d92499909658c7287c49160276bca0006c350b0db4fd387adc27c01e9e9ad0c20ed53a7e6356dee2452e35eca2a6a1d1432796fc5c19d068978df74d3d0baf35c77de12456bf1144b6a750d11f55805f5a16ece2975246e2d026dce997fba34ac8757312e9e4e6272de35e20d52fb668c5ed"
15+
exTGS17 = "$krb5tgs$17$user$realm$ae8434177efd09be5bc2eff8$90b4ce5b266821adc26c64f71958a475cf9348fce65096190be04f8430c4e0d554c86dd7ad29c275f9e8f15d2dab4565a3d6e21e449dc2f88e52ea0402c7170ba74f4af037c5d7f8db6d53018a564ab590fc23aa1134788bcc4a55f69ec13c0a083291a96b41bffb978f5a160b7edc828382d11aacd89b5a1bfa710b0e591b190bff9062eace4d26187777db358e70efd26df9c9312dbeef20b1ee0d823d4e71b8f1d00d91ea017459c27c32dc20e451ea6278be63cdd512ce656357c942b95438228e"
16+
exTGS18 = "$krb5tgs$18$user$realm$8efd91bb01cc69dd07e46009$7352410d6aafd72c64972a66058b02aa1c28ac580ba41137d5a170467f06f17faf5dfb3f95ecf4fad74821fdc7e63a3195573f45f962f86942cb24255e544ad8d05178d560f683a3f59ce94e82c8e724a3af0160be549b472dd83e6b80733ad349973885e9082617294c6cbbea92349671883eaf068d7f5dcfc0405d97fda27435082b82b24f3be27f06c19354bf32066933312c770424eb6143674756243c1bde78ee3294792dcc49008a1b54f32ec5d5695f899946d42a67ce2fb1c227cb1d2004c0"
17+
)
18+
19+
func mustHex(t *testing.T, s string) []byte {
20+
t.Helper()
21+
b, err := hex.DecodeString(s)
22+
if err != nil {
23+
t.Fatalf("bad hex: %v", err)
24+
}
25+
return b
26+
}
27+
28+
// rebuildRC4 reconstructs the KDC-REP ciphertext for an RC4 hash: checksum(16) || edata.
29+
func rebuildRC4(t *testing.T, cksumHex, edataHex string) []byte {
30+
return append(mustHex(t, cksumHex), mustHex(t, edataHex)...)
31+
}
32+
33+
// rebuildAES reconstructs the ciphertext for an AES hash: edata || checksum.
34+
func rebuildAES(t *testing.T, cksumHex, edataHex string) []byte {
35+
return append(mustHex(t, edataHex), mustHex(t, cksumHex)...)
36+
}
37+
38+
func TestFormatASREPHashRC4(t *testing.T) {
39+
// $krb5asrep$23$user@domain.com:<cksum>$<edata>
40+
rest := exASREP23[strings.Index(exASREP23, ":")+1:]
41+
cksumHex, edataHex, _ := strings.Cut(rest, "$")
42+
cipher := rebuildRC4(t, cksumHex, edataHex)
43+
44+
got, err := FormatASREPHash("user", "domain.com", iana.ETypeRC4HMAC, cipher)
45+
if err != nil {
46+
t.Fatal(err)
47+
}
48+
if got != exASREP23 {
49+
t.Errorf("AS-REP RC4 hash mismatch:\n got %s\n want %s", got, exASREP23)
50+
}
51+
}
52+
53+
func lastTwoFields(s string) (secondLast, last string) {
54+
f := strings.Split(s, "$")
55+
return f[len(f)-2], f[len(f)-1]
56+
}
57+
58+
func TestFormatTGSHashRC4(t *testing.T) {
59+
cksumHex, edataHex := lastTwoFields(exTGS23)
60+
cipher := rebuildRC4(t, cksumHex, edataHex)
61+
62+
got, err := FormatTGSHash("user", "realm", "test/spn", iana.ETypeRC4HMAC, cipher)
63+
if err != nil {
64+
t.Fatal(err)
65+
}
66+
if got != exTGS23 {
67+
t.Errorf("TGS RC4 hash mismatch:\n got %s\n want %s", got, exTGS23)
68+
}
69+
}
70+
71+
func TestFormatTGSHashAES(t *testing.T) {
72+
for _, tc := range []struct {
73+
name string
74+
etype int
75+
ex string
76+
}{
77+
{"aes128", iana.ETypeAES128CTSHMACSHA196, exTGS17},
78+
{"aes256", iana.ETypeAES256CTSHMACSHA196, exTGS18},
79+
} {
80+
t.Run(tc.name, func(t *testing.T) {
81+
cksumHex, edataHex := lastTwoFields(tc.ex)
82+
cipher := rebuildAES(t, cksumHex, edataHex)
83+
got, err := FormatTGSHash("user", "realm", "ignored/spn", tc.etype, cipher)
84+
if err != nil {
85+
t.Fatal(err)
86+
}
87+
if got != tc.ex {
88+
t.Errorf("TGS %s hash mismatch:\n got %s\n want %s", tc.name, got, tc.ex)
89+
}
90+
})
91+
}
92+
}
93+
94+
func TestFormatRejectsShortCipher(t *testing.T) {
95+
if _, err := FormatASREPHash("u", "R", iana.ETypeRC4HMAC, []byte{1, 2, 3}); err == nil {
96+
t.Error("expected error for short RC4 cipher")
97+
}
98+
if _, err := FormatTGSHash("u", "R", "s", iana.ETypeAES256CTSHMACSHA196, []byte{1, 2, 3}); err == nil {
99+
t.Error("expected error for short AES cipher")
100+
}
101+
if _, err := FormatTGSHash("u", "R", "s", 999, make([]byte, 32)); err == nil {
102+
t.Error("expected error for unsupported etype")
103+
}
104+
}

0 commit comments

Comments
 (0)