Skip to content

Commit df6496c

Browse files
Remove keytab because KVNO may not match
1 parent eb7dfe0 commit df6496c

File tree

4 files changed

+47
-260
lines changed

4 files changed

+47
-260
lines changed

compat/compat.go

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ package compat
55
import (
66
"github.com/jcmturner/gokrb5/v8/config"
77
"github.com/jcmturner/gokrb5/v8/credentials"
8-
"github.com/jcmturner/gokrb5/v8/keytab"
98
gokrb5ForkConfig "github.com/oiweiwei/gokrb5.fork/v9/config"
109
gokrb5ForkCredentials "github.com/oiweiwei/gokrb5.fork/v9/credentials"
11-
gokrb5ForkKeytab "github.com/oiweiwei/gokrb5.fork/v9/keytab"
1210
gokrb5ForkTypes "github.com/oiweiwei/gokrb5.fork/v9/types"
1311

1412
"github.com/jcmturner/gokrb5/v8/types"
@@ -75,27 +73,3 @@ func Gokrb5ForkV9Principal(realm string, principalName types.PrincipalName) gokr
7573
PrincipalName: gokrb5ForkTypes.PrincipalName(principalName),
7674
}
7775
}
78-
79-
func Gokrb5ForkV9Keytab(keytab *keytab.Keytab) *gokrb5ForkKeytab.Keytab {
80-
entries := make([]gokrb5ForkKeytab.Entry, 0, len(keytab.Entries))
81-
82-
for _, entry := range keytab.Entries {
83-
entries = append(entries, gokrb5ForkKeytab.Entry{
84-
Principal: gokrb5ForkKeytab.Principal{
85-
NumComponents: entry.Principal.NumComponents,
86-
Realm: entry.Principal.Realm,
87-
Components: entry.Principal.Components,
88-
NameType: entry.Principal.NameType,
89-
},
90-
Timestamp: entry.Timestamp,
91-
KVNO8: entry.KVNO8,
92-
Key: gokrb5ForkTypes.EncryptionKey(entry.Key),
93-
KVNO: entry.KVNO,
94-
})
95-
}
96-
97-
return &gokrb5ForkKeytab.Keytab{
98-
Version: 2,
99-
Entries: entries,
100-
}
101-
}

credentials.go

Lines changed: 0 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"crypto/rsa"
66
"crypto/x509"
7-
"encoding/hex"
87
"fmt"
98
"net"
109
"os"
@@ -13,9 +12,7 @@ import (
1312

1413
"github.com/RedTeamPentesting/adauth/othername"
1514
"github.com/jcmturner/gokrb5/v8/config"
16-
"github.com/jcmturner/gokrb5/v8/credentials"
1715
"github.com/jcmturner/gokrb5/v8/iana/etypeID"
18-
"github.com/jcmturner/gokrb5/v8/keytab"
1916
"software.sslmate.com/src/go-pkcs12"
2017
)
2118

@@ -156,30 +153,6 @@ func (c *Credential) mustUseKerberos() bool {
156153
return c.Password == "" && c.NTHash == "" && (c.CCache != "" || c.AESKey != "")
157154
}
158155

159-
// Keytab returns the Kerberos keytab containing the AES key and/or NT hash if
160-
// they were supplied. If a password is supplied, the keys/hashes are not
161-
// derived and the keytab will be empty. For compatibility with other Kerberos
162-
// libraries, see the `compat` package.
163-
func (c *Credential) Keytab() (*keytab.Keytab, error) {
164-
kt := newKeytab()
165-
166-
if c.AESKey != "" {
167-
err := addKeyToKeytab(kt, c.Username, c.Domain, c.AESKey, true, 1)
168-
if err != nil {
169-
return nil, fmt.Errorf("add AES key: %w", err)
170-
}
171-
}
172-
173-
if c.NTHash != "" {
174-
err := addKeyToKeytab(kt, c.Username, c.Domain, c.NTHash, false, 1)
175-
if err != nil {
176-
return nil, fmt.Errorf("add RC4 key: %w", err)
177-
}
178-
}
179-
180-
return kt, nil
181-
}
182-
183156
// KerberosConfig returns the Kerberos configuration for the credential's
184157
// domain. For compatibility with other Kerberos libraries, see the `compat`
185158
// package.
@@ -272,80 +245,3 @@ func splitUserIntoDomainAndUsername(user string) (domain string, username string
272245
return "", user
273246
}
274247
}
275-
276-
func newKeytab() *keytab.Keytab {
277-
kt := &keytab.Keytab{}
278-
279-
err := kt.Unmarshal([]byte{
280-
// header.
281-
0x05, // first-byte
282-
0x02, // version
283-
0x00, 0x00, 0x00, 0x00, // entry-length
284-
})
285-
if err != nil {
286-
panic(err.Error())
287-
}
288-
289-
return kt
290-
}
291-
292-
func addKeyToKeytab(kt *keytab.Keytab, username string, domain string, key string, aes bool, kvno uint32) error {
293-
keyBytes, err := hex.DecodeString(key)
294-
if err != nil {
295-
return fmt.Errorf("decode AES key: %w", err)
296-
}
297-
298-
var keyType int32
299-
300-
switch len(keyBytes) {
301-
case 32:
302-
keyType = etypeID.AES256_CTS_HMAC_SHA1_96
303-
case 16:
304-
if aes {
305-
keyType = etypeID.AES128_CTS_HMAC_SHA1_96
306-
} else {
307-
keyType = etypeID.RC4_HMAC
308-
}
309-
default:
310-
return fmt.Errorf("invalid AES128/AES256 key")
311-
}
312-
313-
tmp := &keytab.Keytab{}
314-
315-
err = tmp.Unmarshal([]byte{
316-
// header
317-
0x05, // first-byte
318-
0x02, // version
319-
0x00, 0x00, 0x00, 0x11, // entry-length
320-
// principal
321-
0x00, 0x00, // num components
322-
0x00, 0x00, // realm length
323-
0x00, 0x00, 0x00, 0x00, // name type
324-
// key
325-
0x00, 0x00, 0x00, 0x00, // timestamp
326-
0x00, // kvno8
327-
0x00, 0x00, // key type
328-
0x00, 0x00, // key length
329-
})
330-
if err != nil {
331-
return fmt.Errorf("invalid dummy data: %w", err)
332-
}
333-
334-
e := tmp.Entries[0]
335-
336-
krbCreds := credentials.New(username, domain)
337-
e.Principal.NumComponents = int16(len(krbCreds.CName().NameString))
338-
e.Principal.Components = krbCreds.CName().NameString
339-
e.Principal.Realm = strings.ToUpper(krbCreds.Realm())
340-
e.Principal.NameType = krbCreds.CName().NameType
341-
342-
e.Timestamp = time.Now()
343-
e.KVNO8 = 0
344-
e.Key.KeyType = keyType
345-
e.Key.KeyValue = keyBytes
346-
e.KVNO = kvno
347-
348-
kt.Entries = append(kt.Entries, e)
349-
350-
return nil
351-
}

credentials_test.go

Lines changed: 0 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -2,136 +2,12 @@ package adauth_test
22

33
import (
44
"context"
5-
"encoding/hex"
65
"net"
7-
"strconv"
8-
"strings"
96
"testing"
107

118
"github.com/RedTeamPentesting/adauth"
12-
"github.com/jcmturner/gokrb5/v8/iana/etypeID"
13-
"github.com/jcmturner/gokrb5/v8/iana/nametype"
14-
"github.com/jcmturner/gokrb5/v8/types"
159
)
1610

17-
func TestKeytab(t *testing.T) {
18-
expectedNTHash := hex.EncodeToString(make([]byte, 16))
19-
expectedAES256Key := hex.EncodeToString(make([]byte, 32))
20-
expectedAES128Key := hex.EncodeToString(make([]byte, 16))
21-
principal := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, testUser)
22-
23-
testCases := []struct {
24-
Cred adauth.Credential
25-
ShouldHaveRC4Key bool
26-
ShouldHaveAES128Key bool
27-
ShouldHaveAES256Key bool
28-
}{
29-
{
30-
Cred: adauth.Credential{},
31-
ShouldHaveRC4Key: false,
32-
ShouldHaveAES128Key: false,
33-
ShouldHaveAES256Key: false,
34-
},
35-
{
36-
Cred: adauth.Credential{
37-
NTHash: expectedNTHash,
38-
},
39-
ShouldHaveRC4Key: true,
40-
ShouldHaveAES128Key: false,
41-
ShouldHaveAES256Key: false,
42-
},
43-
{
44-
Cred: adauth.Credential{
45-
NTHash: expectedNTHash,
46-
AESKey: expectedAES128Key,
47-
},
48-
ShouldHaveRC4Key: true,
49-
ShouldHaveAES128Key: true,
50-
ShouldHaveAES256Key: false,
51-
},
52-
{
53-
Cred: adauth.Credential{
54-
NTHash: expectedNTHash,
55-
AESKey: expectedAES256Key,
56-
},
57-
ShouldHaveRC4Key: true,
58-
ShouldHaveAES128Key: false,
59-
ShouldHaveAES256Key: true,
60-
},
61-
{
62-
Cred: adauth.Credential{
63-
AESKey: expectedAES128Key,
64-
},
65-
ShouldHaveRC4Key: false,
66-
ShouldHaveAES128Key: true,
67-
ShouldHaveAES256Key: false,
68-
},
69-
{
70-
Cred: adauth.Credential{
71-
AESKey: expectedAES256Key,
72-
},
73-
ShouldHaveRC4Key: false,
74-
ShouldHaveAES128Key: false,
75-
ShouldHaveAES256Key: true,
76-
},
77-
}
78-
79-
for i, testCase := range testCases {
80-
testCase := testCase
81-
82-
t.Run(strconv.Itoa(i), func(t *testing.T) {
83-
testCase.Cred.Username = testUser
84-
testCase.Cred.Domain = testDomain
85-
86-
keyTab, err := testCase.Cred.Keytab()
87-
if err != nil {
88-
t.Fatalf("create keytab: %v", err)
89-
}
90-
91-
rc4Key, _, rc4Err := keyTab.GetEncryptionKey(principal, strings.ToUpper(testDomain), 0, etypeID.RC4_HMAC)
92-
93-
switch {
94-
case testCase.ShouldHaveRC4Key && rc4Err != nil:
95-
t.Errorf("expected RC4 key but got error: %v", rc4Err)
96-
case testCase.ShouldHaveRC4Key && len(rc4Key.KeyValue) != 16:
97-
t.Errorf("RC4 key has %d bytes instead of %d", len(rc4Key.KeyValue), 16)
98-
case testCase.ShouldHaveRC4Key && rc4Key.KeyType != etypeID.RC4_HMAC:
99-
t.Errorf("RC4 key type is %d instead of %d", rc4Key.KeyType, etypeID.RC4_HMAC)
100-
case !testCase.ShouldHaveRC4Key && (rc4Err == nil || len(rc4Key.KeyValue) > 0):
101-
t.Errorf("RC4 key should not exist")
102-
}
103-
104-
aes128Key, _, aes128Err := keyTab.GetEncryptionKey(
105-
principal, strings.ToUpper(testDomain), 0, etypeID.AES128_CTS_HMAC_SHA1_96)
106-
107-
switch {
108-
case testCase.ShouldHaveAES128Key && aes128Err != nil:
109-
t.Errorf("expected AES128 key but got error: %v:\n%#v\n", aes128Err, keyTab.Entries)
110-
case testCase.ShouldHaveAES128Key && len(aes128Key.KeyValue) != 16:
111-
t.Errorf("AES128 key has %d bytes instead of %d", len(aes128Key.KeyValue), 16)
112-
case testCase.ShouldHaveAES128Key && aes128Key.KeyType != etypeID.AES128_CTS_HMAC_SHA1_96:
113-
t.Errorf("AES128 key type is %d instead of %d", aes128Key.KeyType, etypeID.AES128_CTS_HMAC_SHA1_96)
114-
case !testCase.ShouldHaveAES128Key && (aes128Err == nil || len(aes128Key.KeyValue) > 0):
115-
t.Errorf("AES128 key should not exist")
116-
}
117-
118-
aes256Key, _, aes256Err := keyTab.GetEncryptionKey(
119-
principal, strings.ToUpper(testDomain), 0, etypeID.AES256_CTS_HMAC_SHA1_96)
120-
121-
switch {
122-
case testCase.ShouldHaveAES256Key && aes256Err != nil:
123-
t.Errorf("expected AES256 key but got error: %v", aes256Err)
124-
case testCase.ShouldHaveAES256Key && len(aes256Key.KeyValue) != 32:
125-
t.Errorf("AES256 key has %d bytes instead of %d", len(aes256Key.KeyValue), 32)
126-
case testCase.ShouldHaveAES256Key && aes256Key.KeyType != etypeID.AES256_CTS_HMAC_SHA1_96:
127-
t.Errorf("AES256 key type is %d instead of %d", aes256Key.KeyType, etypeID.AES256_CTS_HMAC_SHA1_96)
128-
case !testCase.ShouldHaveAES256Key && (aes256Err == nil || len(aes256Key.KeyValue) > 0):
129-
t.Errorf("AES256 key should not exist")
130-
}
131-
})
132-
}
133-
}
134-
13511
func TestSetDC(t *testing.T) {
13612
creds := adauth.Credential{
13713
Username: testUser,

ldapauth/ldap.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"crypto/tls"
1212
"crypto/x509"
1313
"encoding/binary"
14+
"encoding/hex"
1415
"encoding/pem"
1516
"fmt"
1617
"net"
@@ -26,7 +27,9 @@ import (
2627

2728
"github.com/go-ldap/ldap/v3"
2829
"github.com/oiweiwei/gokrb5.fork/v9/client"
30+
"github.com/oiweiwei/gokrb5.fork/v9/iana/etypeID"
2931
"github.com/oiweiwei/gokrb5.fork/v9/iana/flags"
32+
"github.com/oiweiwei/gokrb5.fork/v9/types"
3033
"github.com/spf13/pflag"
3134
)
3235

@@ -353,19 +356,57 @@ func kerberosClient(
353356
}
354357

355358
authClient.BindCertificate = cert
356-
case creds.NTHash != "" || creds.AESKey != "":
357-
opts.Debug("authenticating using GSSAPI bind (key)")
359+
case creds.NTHash != "":
360+
opts.Debug("authenticating using GSSAPI bind (NT hash)")
358361

359-
keyTab, err := creds.Keytab()
362+
ntHash, err := hex.DecodeString(creds.NTHash)
360363
if err != nil {
361-
return nil, fmt.Errorf("create keytab: %w", err)
364+
return nil, fmt.Errorf("decode NT hash: %w", err)
362365
}
363366

364367
authClient = &gssapiClient{
365-
Client: client.NewWithKeytab(
368+
Client: client.NewWithEncryptionKey(
366369
creds.Username,
367370
strings.ToUpper(creds.Domain),
368-
compat.Gokrb5ForkV9Keytab(keyTab),
371+
types.EncryptionKey{
372+
KeyType: etypeID.RC4_HMAC,
373+
KeyValue: ntHash,
374+
},
375+
compat.Gokrb5ForkV9KerberosConfig(krbConf),
376+
client.DisablePAFXFAST(true),
377+
client.Dialer(opts.KerberosDialer),
378+
),
379+
BindCertificate: cert,
380+
}
381+
382+
authClient.BindCertificate = cert
383+
case creds.AESKey != "":
384+
opts.Debug("authenticating using GSSAPI bind (AES key)")
385+
386+
aesKey, err := hex.DecodeString(creds.AESKey)
387+
if err != nil {
388+
return nil, fmt.Errorf("decode AES key: %w", err)
389+
}
390+
391+
var keyType int32
392+
393+
switch len(aesKey) {
394+
case 32:
395+
keyType = etypeID.AES256_CTS_HMAC_SHA1_96
396+
case 16:
397+
keyType = etypeID.AES128_CTS_HMAC_SHA1_96
398+
default:
399+
return nil, fmt.Errorf("invalid AES128/AES256 key: key size is %d bytes", len(aesKey))
400+
}
401+
402+
authClient = &gssapiClient{
403+
Client: client.NewWithEncryptionKey(
404+
creds.Username,
405+
strings.ToUpper(creds.Domain),
406+
types.EncryptionKey{
407+
KeyType: keyType,
408+
KeyValue: aesKey,
409+
},
369410
compat.Gokrb5ForkV9KerberosConfig(krbConf),
370411
client.DisablePAFXFAST(true),
371412
client.Dialer(opts.KerberosDialer),

0 commit comments

Comments
 (0)