nex-go/kerberos.go
2024-11-20 15:04:05 -05:00

205 lines
5.7 KiB
Go

package nex
import (
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/rc4"
"errors"
"fmt"
"github.com/PretendoNetwork/nex-go/v2/types"
)
// KerberosEncryption is a struct representing a Kerberos encryption utility
type KerberosEncryption struct {
key []byte
}
// Validate checks the integrity of the given buffer by verifying the HMAC checksum
func (ke *KerberosEncryption) Validate(buffer []byte) bool {
data := buffer[:len(buffer)-0x10]
checksum := buffer[len(buffer)-0x10:]
mac := hmac.New(md5.New, ke.key)
mac.Write(data)
return hmac.Equal(checksum, mac.Sum(nil))
}
// Decrypt decrypts the provided buffer if it passes the integrity check
func (ke *KerberosEncryption) Decrypt(buffer []byte) ([]byte, error) {
if !ke.Validate(buffer) {
return nil, errors.New("Invalid Kerberos checksum (incorrect password)")
}
cipher, err := rc4.NewCipher(ke.key)
if err != nil {
return nil, err
}
decrypted := make([]byte, len(buffer)-0x10)
cipher.XORKeyStream(decrypted, buffer[:len(buffer)-0x10])
return decrypted, nil
}
// Encrypt encrypts the given buffer and appends an HMAC checksum for integrity
func (ke *KerberosEncryption) Encrypt(buffer []byte) []byte {
cipher, _ := rc4.NewCipher(ke.key)
encrypted := make([]byte, len(buffer))
cipher.XORKeyStream(encrypted, buffer)
mac := hmac.New(md5.New, ke.key)
mac.Write(encrypted)
checksum := mac.Sum(nil)
return append(encrypted, checksum...)
}
// NewKerberosEncryption creates a new KerberosEncryption instance with the given key.
func NewKerberosEncryption(key []byte) *KerberosEncryption {
return &KerberosEncryption{key: key}
}
// KerberosTicket represents a ticket granting a user access to a secure server
type KerberosTicket struct {
SessionKey []byte
TargetPID types.PID
InternalData types.Buffer
}
// Encrypt writes the ticket data to the provided stream and returns the encrypted byte slice
func (kt *KerberosTicket) Encrypt(key []byte, stream *ByteStreamOut) ([]byte, error) {
encryption := NewKerberosEncryption(key)
stream.Grow(int64(len(kt.SessionKey)))
stream.WriteBytesNext(kt.SessionKey)
kt.TargetPID.WriteTo(stream)
kt.InternalData.WriteTo(stream)
return encryption.Encrypt(stream.Bytes()), nil
}
// NewKerberosTicket returns a new Ticket instance
func NewKerberosTicket() *KerberosTicket {
return &KerberosTicket{}
}
// KerberosTicketInternalData holds the internal data for a kerberos ticket to be processed by the server
type KerberosTicketInternalData struct {
Server *PRUDPServer // TODO - Remove this dependency and make a settings struct
Issued types.DateTime
SourcePID types.PID
SessionKey []byte
}
// Encrypt writes the ticket data to the provided stream and returns the encrypted byte slice
func (ti *KerberosTicketInternalData) Encrypt(key []byte, stream *ByteStreamOut) ([]byte, error) {
ti.Issued.WriteTo(stream)
ti.SourcePID.WriteTo(stream)
stream.Grow(int64(len(ti.SessionKey)))
stream.WriteBytesNext(ti.SessionKey)
data := stream.Bytes()
if ti.Server.KerberosTicketVersion == 1 {
ticketKey := make([]byte, 16)
_, err := rand.Read(ticketKey)
if err != nil {
return nil, fmt.Errorf("Failed to generate ticket key. %s", err.Error())
}
hash := md5.Sum(append(key, ticketKey...))
finalKey := hash[:]
encryption := NewKerberosEncryption(finalKey)
encrypted := encryption.Encrypt(data)
finalStream := NewByteStreamOut(stream.LibraryVersions, stream.Settings)
ticketBuffer := types.NewBuffer(ticketKey)
encryptedBuffer := types.NewBuffer(encrypted)
ticketBuffer.WriteTo(finalStream)
encryptedBuffer.WriteTo(finalStream)
return finalStream.Bytes(), nil
}
encryption := NewKerberosEncryption([]byte(key))
return encryption.Encrypt(data), nil
}
// Decrypt decrypts the given data and populates the struct
func (ti *KerberosTicketInternalData) Decrypt(stream *ByteStreamIn, key []byte) error {
if ti.Server.KerberosTicketVersion == 1 {
ticketKey := types.NewBuffer(nil)
if err := ticketKey.ExtractFrom(stream); err != nil {
return fmt.Errorf("Failed to read Kerberos ticket internal data key. %s", err.Error())
}
data := types.NewBuffer(nil)
if err := ticketKey.ExtractFrom(stream); err != nil {
return fmt.Errorf("Failed to read Kerberos ticket internal data. %s", err.Error())
}
hash := md5.Sum(append(key, ticketKey...))
key = hash[:]
stream = NewByteStreamIn(data, stream.LibraryVersions, stream.Settings)
}
encryption := NewKerberosEncryption(key)
decrypted, err := encryption.Decrypt(stream.Bytes())
if err != nil {
return fmt.Errorf("Failed to decrypt Kerberos ticket internal data. %s", err.Error())
}
stream = NewByteStreamIn(decrypted, stream.LibraryVersions, stream.Settings)
timestamp := types.NewDateTime(0)
if err := timestamp.ExtractFrom(stream); err != nil {
return fmt.Errorf("Failed to read Kerberos ticket internal data timestamp %s", err.Error())
}
userPID := types.NewPID(0)
if err := userPID.ExtractFrom(stream); err != nil {
return fmt.Errorf("Failed to read Kerberos ticket internal data user PID %s", err.Error())
}
ti.Issued = timestamp
ti.SourcePID = userPID
ti.SessionKey = stream.ReadBytesNext(int64(ti.Server.SessionKeyLength))
return nil
}
// NewKerberosTicketInternalData returns a new KerberosTicketInternalData instance
func NewKerberosTicketInternalData(server *PRUDPServer) *KerberosTicketInternalData {
return &KerberosTicketInternalData{Server: server}
}
// DeriveKerberosKey derives a users kerberos encryption key based on their PID and password
func DeriveKerberosKey(pid types.PID, password []byte) []byte {
iterationCount := int(65000 + pid%1024)
key := password
hash := make([]byte, md5.Size)
for i := 0; i < iterationCount; i++ {
sum := md5.Sum(key)
copy(hash, sum[:])
key = hash
}
return key
}