Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 13 additions & 3 deletions v8/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package crypto
import (
"encoding/hex"
"fmt"
"strings"

"github.com/jcmturner/gokrb5/v8/crypto/etype"
"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
Expand Down Expand Up @@ -67,15 +68,19 @@ func GetChksumEtype(id int32) (etype.EType, error) {
}
}

// GetKeyFromPassword generates an encryption key from the principal's password.
func GetKeyFromPassword(passwd string, cname types.PrincipalName, realm string, etypeID int32, pas types.PADataSequence) (types.EncryptionKey, etype.EType, error) {
// GetActiveDirectoryKeyFromPassword generates an Active Directory compatible encryption key from the principal's password.
func GetActiveDirectoryKeyFromPassword(passwd string, cname types.PrincipalName, realm, samAccountName string, etypeID int32, pas types.PADataSequence) (types.EncryptionKey, etype.EType, error) {
salt := realm + "host" + samAccountName + "." + strings.ToLower(realm)
return GetKeyFromPasswordWithSalt(passwd, cname, realm, salt, etypeID, pas)
}

func GetKeyFromPasswordWithSalt(passwd string, cname types.PrincipalName, realm, salt string, etypeID int32, pas types.PADataSequence) (types.EncryptionKey, etype.EType, error) {
var key types.EncryptionKey
et, err := GetEtype(etypeID)
if err != nil {
return key, et, fmt.Errorf("error getting encryption type: %v", err)
}
sk2p := et.GetDefaultStringToKeyParams()
var salt string
var paID int32
for _, pa := range pas {
switch pa.PADataType {
Expand Down Expand Up @@ -135,6 +140,11 @@ func GetKeyFromPassword(passwd string, cname types.PrincipalName, realm string,
return key, et, nil
}

// GetKeyFromPassword generates an encryption key from the principal's password.
func GetKeyFromPassword(passwd string, cname types.PrincipalName, realm string, etypeID int32, pas types.PADataSequence) (types.EncryptionKey, etype.EType, error) {
return GetKeyFromPasswordWithSalt(passwd, cname, realm, "", etypeID, pas)
}

// GetEncryptedData encrypts the data provided and returns and EncryptedData type.
// Pass a usage value of zero to use the key provided directly rather than deriving one.
func GetEncryptedData(plainBytes []byte, key types.EncryptionKey, usage uint32, kvno int) (types.EncryptedData, error) {
Expand Down
21 changes: 19 additions & 2 deletions v8/keytab/keytab.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ func (kt Keytab) String() string {
return s
}

// AddActiveDirectoryComputerAccountEntry adds an entry to the keytab for a Microsoft Active Directory Computer Object. The password should be provided in plain text and it will be converted using the defined enctype to be stored.
func (kt *Keytab) AddActiveDirectoryComputerAccountEntry(principalName, realm, password, samAccountName string, ts time.Time, KVNO uint8, encType int32) error {
// Generate a key from the password
princ, _ := types.ParseSPNString(principalName)
key, _, err := crypto.GetActiveDirectoryKeyFromPassword(password, princ, realm, samAccountName, encType, types.PADataSequence{})
if err != nil {
return err
}

kt.populateEntry(princ, realm, ts, KVNO, key)
return nil
}

// AddEntry adds an entry to the keytab. The password should be provided in plain text and it will be converted using the defined enctype to be stored.
func (kt *Keytab) AddEntry(principalName, realm, password string, ts time.Time, KVNO uint8, encType int32) error {
// Generate a key from the password
Expand All @@ -133,11 +146,16 @@ func (kt *Keytab) AddEntry(principalName, realm, password string, ts time.Time,
return err
}

kt.populateEntry(princ, realm, ts, KVNO, key)
return nil
}

func (kt *Keytab) populateEntry(princ types.PrincipalName, realm string, ts time.Time, KVNO uint8, key types.EncryptionKey) {
// Populate the keytab entry principal
ktep := newPrincipal()
ktep.NumComponents = int16(len(princ.NameString))
if kt.version == 1 {
ktep.NumComponents += 1
ktep.NumComponents++
}

ktep.Realm = realm
Expand All @@ -153,7 +171,6 @@ func (kt *Keytab) AddEntry(principalName, realm, password string, ts time.Time,
e.Key = key

kt.Entries = append(kt.Entries, e)
return nil
}

// Create a new principal.
Expand Down
80 changes: 80 additions & 0 deletions v8/keytab/keytab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,86 @@ func TestKeytabEntriesService(t *testing.T) {
assert.Equal(t, generated, ktutilbytes, "Service keytab doesn't match ktutil keytab")
}

func TestKeytabEntriesComputer(t *testing.T) {
// Load known-good keytab generated with ktutil
ktutilb64 := string(`BQIAAABAAAEAC0NZTE9OLkxPQ0FMAA5sb3NwZWNuaW5mcmEzJAAAAAFjB6XsBQAXABARDQxR4UTT
b7fk+eAS+7iIAAAABQAAAEAAAQALQ1lMT04uTE9DQUwADmxvc3BlY25pbmZyYTMkAAAAAWMHpewF
ABEAEDgTr5D44EdeyfbxcTNpiO4AAAAFAAAAUAABAAtDWUxPTi5MT0NBTAAObG9zcGVjbmluZnJh
MyQAAAABYwel7AUAEgAg9LXbCPYwoqorwz7TJIe3TIMRyiR7u5J4trLwtXgAzBMAAAAFAAAAQAAB
AAtDWUxPTi5MT0NBTAAOTE9TUEVDTklORlJBMyQAAAABYwel7AUAFwAQEQ0MUeFE02+35PngEvu4
iAAAAAUAAABAAAEAC0NZTE9OLkxPQ0FMAA5MT1NQRUNOSU5GUkEzJAAAAAFjB6XsBQARABA4E6+Q
+OBHXsn28XEzaYjuAAAABQAAAFAAAQALQ1lMT04uTE9DQUwADkxPU1BFQ05JTkZSQTMkAAAAAWMH
pewFABIAIPS12wj2MKKqK8M+0ySHt0yDEcoke7uSeLay8LV4AMwTAAAABQAAAFUAAgALQ1lMT04u
TE9DQUwABGhvc3QAHWxvc3BlY25pbmZyYTMuZWNuLmN5bG9uLmxvY2FsAAAAAWMHpewFABcAEBEN
DFHhRNNvt+T54BL7uIgAAAAFAAAAVQACAAtDWUxPTi5MT0NBTAAEaG9zdAAdbG9zcGVjbmluZnJh
My5lY24uY3lsb24ubG9jYWwAAAABYwel7AUAEQAQOBOvkPjgR17J9vFxM2mI7gAAAAUAAABlAAIA
C0NZTE9OLkxPQ0FMAARob3N0AB1sb3NwZWNuaW5mcmEzLmVjbi5jeWxvbi5sb2NhbAAAAAFjB6Xs
BQASACD0tdsI9jCiqivDPtMkh7dMgxHKJHu7kni2svC1eADMEwAAAAUAAABFAAIAC0NZTE9OLkxP
Q0FMAARob3N0AA1sb3NwZWNuaW5mcmEzAAAAAWMHpewFABcAEBENDFHhRNNvt+T54BL7uIgAAAAF
AAAARQACAAtDWUxPTi5MT0NBTAAEaG9zdAANbG9zcGVjbmluZnJhMwAAAAFjB6XsBQARABA4E6+Q
+OBHXsn28XEzaYjuAAAABQAAAFUAAgALQ1lMT04uTE9DQUwABGhvc3QADWxvc3BlY25pbmZyYTMA
AAABYwel7AUAEgAg9LXbCPYwoqorwz7TJIe3TIMRyiR7u5J4trLwtXgAzBMAAAAFAAAAYgACAAtD
WUxPTi5MT0NBTAARUmVzdHJpY3RlZEtyYkhvc3QAHWxvc3BlY25pbmZyYTMuZWNuLmN5bG9uLmxv
Y2FsAAAAAWMHpewFABcAEBENDFHhRNNvt+T54BL7uIgAAAAFAAAAYgACAAtDWUxPTi5MT0NBTAAR
UmVzdHJpY3RlZEtyYkhvc3QAHWxvc3BlY25pbmZyYTMuZWNuLmN5bG9uLmxvY2FsAAAAAWMHpewF
ABEAEDgTr5D44EdeyfbxcTNpiO4AAAAFAAAAcgACAAtDWUxPTi5MT0NBTAARUmVzdHJpY3RlZEty
Ykhvc3QAHWxvc3BlY25pbmZyYTMuZWNuLmN5bG9uLmxvY2FsAAAAAWMHpewFABIAIPS12wj2MKKq
K8M+0ySHt0yDEcoke7uSeLay8LV4AMwTAAAABQAAAFIAAgALQ1lMT04uTE9DQUwAEVJlc3RyaWN0
ZWRLcmJIb3N0AA1MT1NQRUNOSU5GUkEzAAAAAWMHpewFABcAEBENDFHhRNNvt+T54BL7uIgAAAAF
AAAAUgACAAtDWUxPTi5MT0NBTAARUmVzdHJpY3RlZEtyYkhvc3QADUxPU1BFQ05JTkZSQTMAAAAB
Ywel7AUAEQAQOBOvkPjgR17J9vFxM2mI7gAAAAUAAABiAAIAC0NZTE9OLkxPQ0FMABFSZXN0cmlj
dGVkS3JiSG9zdAANTE9TUEVDTklORlJBMwAAAAFjB6XsBQASACD0tdsI9jCiqivDPtMkh7dMgxHK
JHu7kni2svC1eADMEwAAAAUAAABFAAIAC0NZTE9OLkxPQ0FMAARob3N0AA1MT1NQRUNOSU5GUkEz
AAAAAWMHpewFABcAEBENDFHhRNNvt+T54BL7uIgAAAAFAAAARQACAAtDWUxPTi5MT0NBTAAEaG9z
dAANTE9TUEVDTklORlJBMwAAAAFjB6XsBQARABA4E6+Q+OBHXsn28XEzaYjuAAAABQAAAFUAAgAL
Q1lMT04uTE9DQUwABGhvc3QADUxPU1BFQ05JTkZSQTMAAAABYwel7AUAEgAg9LXbCPYwoqorwz7T
JIe3TIMRyiR7u5J4trLwtXgAzBMAAAAF`)
ktutilbytes, err := base64.StdEncoding.DecodeString(ktutilb64)
if err != nil {
t.Errorf("Could not parse b64 ktutil keytab: %s", err)
}
ktutil := new(Keytab)
err = ktutil.Unmarshal(ktutilbytes)
if err != nil {
t.Fatalf("Could not load ktutil-generated keytab: %s", err)
}

// Generate the same keytab with gokrb5
ts := ktutil.Entries[0].Timestamp
var encTypes = []int32{
etypeID.RC4_HMAC,
etypeID.AES128_CTS_HMAC_SHA1_96,
etypeID.AES256_CTS_HMAC_SHA1_96,
}

principals := []string{
"lospecninfra3$",
"LOSPECNINFRA3$",
"host/lospecninfra3.ecn.cylon.local",
"host/lospecninfra3",
"RestrictedKrbHost/lospecninfra3.ecn.cylon.local",
"RestrictedKrbHost/LOSPECNINFRA3",
"host/LOSPECNINFRA3",
}

kt := New()
for _, princ := range principals {
for _, et := range encTypes {
err = kt.AddActiveDirectoryComputerAccountEntry(princ, "CYLON.LOCAL", "hello123", "lospecninfra3", ts, uint8(5), et)
if err != nil {
t.Errorf("Error adding entry to keytab: %s", err)
}
}
}
generated, err := kt.Marshal()
if err != nil {
t.Errorf("Error marshalling generated keytab: %s", err)
}

// Compare content
assert.Equal(t, generated, ktutilbytes, "Computer keytab doesn't match ktutil keytab")
}

func TestKeytab_GetEncryptionKey(t *testing.T) {
princ := "HTTP/princ.test.gokrb5"
realm := "TEST.GOKRB5"
Expand Down