433 lines
14 KiB
Go
433 lines
14 KiB
Go
|
package messages
|
||
|
|
||
|
// Reference: https://www.ietf.org/rfc/rfc4120.txt
|
||
|
// Section: 5.4.1
|
||
|
|
||
|
import (
|
||
|
"crypto/rand"
|
||
|
"fmt"
|
||
|
"math"
|
||
|
"math/big"
|
||
|
"time"
|
||
|
|
||
|
"github.com/jcmturner/gofork/encoding/asn1"
|
||
|
"github.com/jcmturner/gokrb5/v8/asn1tools"
|
||
|
"github.com/jcmturner/gokrb5/v8/config"
|
||
|
"github.com/jcmturner/gokrb5/v8/crypto"
|
||
|
"github.com/jcmturner/gokrb5/v8/iana"
|
||
|
"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
|
||
|
"github.com/jcmturner/gokrb5/v8/iana/flags"
|
||
|
"github.com/jcmturner/gokrb5/v8/iana/keyusage"
|
||
|
"github.com/jcmturner/gokrb5/v8/iana/msgtype"
|
||
|
"github.com/jcmturner/gokrb5/v8/iana/nametype"
|
||
|
"github.com/jcmturner/gokrb5/v8/iana/patype"
|
||
|
"github.com/jcmturner/gokrb5/v8/krberror"
|
||
|
"github.com/jcmturner/gokrb5/v8/types"
|
||
|
)
|
||
|
|
||
|
type marshalKDCReq struct {
|
||
|
PVNO int `asn1:"explicit,tag:1"`
|
||
|
MsgType int `asn1:"explicit,tag:2"`
|
||
|
PAData types.PADataSequence `asn1:"explicit,optional,tag:3"`
|
||
|
ReqBody asn1.RawValue `asn1:"explicit,tag:4"`
|
||
|
}
|
||
|
|
||
|
// KDCReqFields represents the KRB_KDC_REQ fields.
|
||
|
type KDCReqFields struct {
|
||
|
PVNO int
|
||
|
MsgType int
|
||
|
PAData types.PADataSequence
|
||
|
ReqBody KDCReqBody
|
||
|
Renewal bool
|
||
|
}
|
||
|
|
||
|
// ASReq implements RFC 4120 KRB_AS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1.
|
||
|
type ASReq struct {
|
||
|
KDCReqFields
|
||
|
}
|
||
|
|
||
|
// TGSReq implements RFC 4120 KRB_TGS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1.
|
||
|
type TGSReq struct {
|
||
|
KDCReqFields
|
||
|
}
|
||
|
|
||
|
type marshalKDCReqBody struct {
|
||
|
KDCOptions asn1.BitString `asn1:"explicit,tag:0"`
|
||
|
CName types.PrincipalName `asn1:"explicit,optional,tag:1"`
|
||
|
Realm string `asn1:"generalstring,explicit,tag:2"`
|
||
|
SName types.PrincipalName `asn1:"explicit,optional,tag:3"`
|
||
|
From time.Time `asn1:"generalized,explicit,optional,tag:4"`
|
||
|
Till time.Time `asn1:"generalized,explicit,tag:5"`
|
||
|
RTime time.Time `asn1:"generalized,explicit,optional,tag:6"`
|
||
|
Nonce int `asn1:"explicit,tag:7"`
|
||
|
EType []int32 `asn1:"explicit,tag:8"`
|
||
|
Addresses []types.HostAddress `asn1:"explicit,optional,tag:9"`
|
||
|
EncAuthData types.EncryptedData `asn1:"explicit,optional,tag:10"`
|
||
|
// Ticket needs to be a raw value as it is wrapped in an APPLICATION tag
|
||
|
AdditionalTickets asn1.RawValue `asn1:"explicit,optional,tag:11"`
|
||
|
}
|
||
|
|
||
|
// KDCReqBody implements the KRB_KDC_REQ request body.
|
||
|
type KDCReqBody struct {
|
||
|
KDCOptions asn1.BitString `asn1:"explicit,tag:0"`
|
||
|
CName types.PrincipalName `asn1:"explicit,optional,tag:1"`
|
||
|
Realm string `asn1:"generalstring,explicit,tag:2"`
|
||
|
SName types.PrincipalName `asn1:"explicit,optional,tag:3"`
|
||
|
From time.Time `asn1:"generalized,explicit,optional,tag:4"`
|
||
|
Till time.Time `asn1:"generalized,explicit,tag:5"`
|
||
|
RTime time.Time `asn1:"generalized,explicit,optional,tag:6"`
|
||
|
Nonce int `asn1:"explicit,tag:7"`
|
||
|
EType []int32 `asn1:"explicit,tag:8"`
|
||
|
Addresses []types.HostAddress `asn1:"explicit,optional,tag:9"`
|
||
|
EncAuthData types.EncryptedData `asn1:"explicit,optional,tag:10"`
|
||
|
AdditionalTickets []Ticket `asn1:"explicit,optional,tag:11"`
|
||
|
}
|
||
|
|
||
|
// NewASReqForTGT generates a new KRB_AS_REQ struct for a TGT request.
|
||
|
func NewASReqForTGT(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) {
|
||
|
sname := types.PrincipalName{
|
||
|
NameType: nametype.KRB_NT_SRV_INST,
|
||
|
NameString: []string{"krbtgt", realm},
|
||
|
}
|
||
|
return NewASReq(realm, c, cname, sname)
|
||
|
}
|
||
|
|
||
|
// NewASReqForChgPasswd generates a new KRB_AS_REQ struct for a change password request.
|
||
|
func NewASReqForChgPasswd(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) {
|
||
|
sname := types.PrincipalName{
|
||
|
NameType: nametype.KRB_NT_PRINCIPAL,
|
||
|
NameString: []string{"kadmin", "changepw"},
|
||
|
}
|
||
|
return NewASReq(realm, c, cname, sname)
|
||
|
}
|
||
|
|
||
|
// NewASReq generates a new KRB_AS_REQ struct for a given SNAME.
|
||
|
func NewASReq(realm string, c *config.Config, cname, sname types.PrincipalName) (ASReq, error) {
|
||
|
nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32))
|
||
|
if err != nil {
|
||
|
return ASReq{}, err
|
||
|
}
|
||
|
t := time.Now().UTC()
|
||
|
// Copy the default options to make this thread safe
|
||
|
kopts := types.NewKrbFlags()
|
||
|
copy(kopts.Bytes, c.LibDefaults.KDCDefaultOptions.Bytes)
|
||
|
kopts.BitLength = c.LibDefaults.KDCDefaultOptions.BitLength
|
||
|
a := ASReq{
|
||
|
KDCReqFields{
|
||
|
PVNO: iana.PVNO,
|
||
|
MsgType: msgtype.KRB_AS_REQ,
|
||
|
PAData: types.PADataSequence{},
|
||
|
ReqBody: KDCReqBody{
|
||
|
KDCOptions: kopts,
|
||
|
Realm: realm,
|
||
|
CName: cname,
|
||
|
SName: sname,
|
||
|
Till: t.Add(c.LibDefaults.TicketLifetime),
|
||
|
Nonce: int(nonce.Int64()),
|
||
|
EType: c.LibDefaults.DefaultTktEnctypeIDs,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
if c.LibDefaults.Forwardable {
|
||
|
types.SetFlag(&a.ReqBody.KDCOptions, flags.Forwardable)
|
||
|
}
|
||
|
if c.LibDefaults.Canonicalize {
|
||
|
types.SetFlag(&a.ReqBody.KDCOptions, flags.Canonicalize)
|
||
|
}
|
||
|
if c.LibDefaults.Proxiable {
|
||
|
types.SetFlag(&a.ReqBody.KDCOptions, flags.Proxiable)
|
||
|
}
|
||
|
if c.LibDefaults.RenewLifetime != 0 {
|
||
|
types.SetFlag(&a.ReqBody.KDCOptions, flags.Renewable)
|
||
|
a.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime)
|
||
|
a.ReqBody.RTime = t.Add(time.Duration(48) * time.Hour)
|
||
|
}
|
||
|
if !c.LibDefaults.NoAddresses {
|
||
|
ha, err := types.LocalHostAddresses()
|
||
|
if err != nil {
|
||
|
return a, fmt.Errorf("could not get local addresses: %v", err)
|
||
|
}
|
||
|
ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...)
|
||
|
a.ReqBody.Addresses = ha
|
||
|
}
|
||
|
return a, nil
|
||
|
}
|
||
|
|
||
|
// NewTGSReq generates a new KRB_TGS_REQ struct.
|
||
|
func NewTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, tgt Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool) (TGSReq, error) {
|
||
|
a, err := tgsReq(cname, sname, kdcRealm, renewal, c)
|
||
|
if err != nil {
|
||
|
return a, err
|
||
|
}
|
||
|
err = a.setPAData(tgt, sessionKey)
|
||
|
return a, err
|
||
|
}
|
||
|
|
||
|
// NewUser2UserTGSReq returns a TGS-REQ suitable for user-to-user authentication (https://tools.ietf.org/html/rfc4120#section-3.7)
|
||
|
func NewUser2UserTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, clientTGT Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool, verifyingTGT Ticket) (TGSReq, error) {
|
||
|
a, err := tgsReq(cname, sname, kdcRealm, renewal, c)
|
||
|
if err != nil {
|
||
|
return a, err
|
||
|
}
|
||
|
a.ReqBody.AdditionalTickets = []Ticket{verifyingTGT}
|
||
|
types.SetFlag(&a.ReqBody.KDCOptions, flags.EncTktInSkey)
|
||
|
err = a.setPAData(clientTGT, sessionKey)
|
||
|
return a, err
|
||
|
}
|
||
|
|
||
|
// tgsReq populates the fields for a TGS_REQ
|
||
|
func tgsReq(cname, sname types.PrincipalName, kdcRealm string, renewal bool, c *config.Config) (TGSReq, error) {
|
||
|
nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32))
|
||
|
if err != nil {
|
||
|
return TGSReq{}, err
|
||
|
}
|
||
|
t := time.Now().UTC()
|
||
|
k := KDCReqFields{
|
||
|
PVNO: iana.PVNO,
|
||
|
MsgType: msgtype.KRB_TGS_REQ,
|
||
|
ReqBody: KDCReqBody{
|
||
|
KDCOptions: types.NewKrbFlags(),
|
||
|
Realm: kdcRealm,
|
||
|
CName: cname, // Add the CName to make validation of the reply easier
|
||
|
SName: sname,
|
||
|
Till: t.Add(c.LibDefaults.TicketLifetime),
|
||
|
Nonce: int(nonce.Int64()),
|
||
|
EType: c.LibDefaults.DefaultTGSEnctypeIDs,
|
||
|
},
|
||
|
Renewal: renewal,
|
||
|
}
|
||
|
if c.LibDefaults.Forwardable {
|
||
|
types.SetFlag(&k.ReqBody.KDCOptions, flags.Forwardable)
|
||
|
}
|
||
|
if c.LibDefaults.Canonicalize {
|
||
|
types.SetFlag(&k.ReqBody.KDCOptions, flags.Canonicalize)
|
||
|
}
|
||
|
if c.LibDefaults.Proxiable {
|
||
|
types.SetFlag(&k.ReqBody.KDCOptions, flags.Proxiable)
|
||
|
}
|
||
|
if c.LibDefaults.RenewLifetime > time.Duration(0) {
|
||
|
types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable)
|
||
|
k.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime)
|
||
|
}
|
||
|
if !c.LibDefaults.NoAddresses {
|
||
|
ha, err := types.LocalHostAddresses()
|
||
|
if err != nil {
|
||
|
return TGSReq{}, fmt.Errorf("could not get local addresses: %v", err)
|
||
|
}
|
||
|
ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...)
|
||
|
k.ReqBody.Addresses = ha
|
||
|
}
|
||
|
if renewal {
|
||
|
types.SetFlag(&k.ReqBody.KDCOptions, flags.Renew)
|
||
|
types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable)
|
||
|
}
|
||
|
return TGSReq{
|
||
|
k,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (k *TGSReq) setPAData(tgt Ticket, sessionKey types.EncryptionKey) error {
|
||
|
// Marshal the request and calculate checksum
|
||
|
b, err := k.ReqBody.Marshal()
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.EncodingError, "error marshaling TGS_REQ body")
|
||
|
}
|
||
|
etype, err := crypto.GetEtype(sessionKey.KeyType)
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.EncryptingError, "error getting etype to encrypt authenticator")
|
||
|
}
|
||
|
cb, err := etype.GetChecksumHash(sessionKey.KeyValue, b, keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM)
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.ChksumError, "error getting etype checksum hash")
|
||
|
}
|
||
|
|
||
|
// Form PAData for TGS_REQ
|
||
|
// Create authenticator
|
||
|
auth, err := types.NewAuthenticator(tgt.Realm, k.ReqBody.CName)
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator")
|
||
|
}
|
||
|
auth.Cksum = types.Checksum{
|
||
|
CksumType: etype.GetHashID(),
|
||
|
Checksum: cb,
|
||
|
}
|
||
|
// Create AP_REQ
|
||
|
apReq, err := NewAPReq(tgt, sessionKey, auth)
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AP_REQ")
|
||
|
}
|
||
|
apb, err := apReq.Marshal()
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.EncodingError, "error marshaling AP_REQ for pre-authentication data")
|
||
|
}
|
||
|
k.PAData = types.PADataSequence{
|
||
|
types.PAData{
|
||
|
PADataType: patype.PA_TGS_REQ,
|
||
|
PADataValue: apb,
|
||
|
},
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Unmarshal bytes b into the ASReq struct.
|
||
|
func (k *ASReq) Unmarshal(b []byte) error {
|
||
|
var m marshalKDCReq
|
||
|
_, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREQ))
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling AS_REQ")
|
||
|
}
|
||
|
expectedMsgType := msgtype.KRB_AS_REQ
|
||
|
if m.MsgType != expectedMsgType {
|
||
|
return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a AS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType)
|
||
|
}
|
||
|
var reqb KDCReqBody
|
||
|
err = reqb.Unmarshal(m.ReqBody.Bytes)
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.EncodingError, "error processing AS_REQ body")
|
||
|
}
|
||
|
k.MsgType = m.MsgType
|
||
|
k.PAData = m.PAData
|
||
|
k.PVNO = m.PVNO
|
||
|
k.ReqBody = reqb
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Unmarshal bytes b into the TGSReq struct.
|
||
|
func (k *TGSReq) Unmarshal(b []byte) error {
|
||
|
var m marshalKDCReq
|
||
|
_, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.TGSREQ))
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling TGS_REQ")
|
||
|
}
|
||
|
expectedMsgType := msgtype.KRB_TGS_REQ
|
||
|
if m.MsgType != expectedMsgType {
|
||
|
return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a TGS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType)
|
||
|
}
|
||
|
var reqb KDCReqBody
|
||
|
err = reqb.Unmarshal(m.ReqBody.Bytes)
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.EncodingError, "error processing TGS_REQ body")
|
||
|
}
|
||
|
k.MsgType = m.MsgType
|
||
|
k.PAData = m.PAData
|
||
|
k.PVNO = m.PVNO
|
||
|
k.ReqBody = reqb
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Unmarshal bytes b into the KRB_KDC_REQ body struct.
|
||
|
func (k *KDCReqBody) Unmarshal(b []byte) error {
|
||
|
var m marshalKDCReqBody
|
||
|
_, err := asn1.Unmarshal(b, &m)
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling KDC_REQ body")
|
||
|
}
|
||
|
k.KDCOptions = m.KDCOptions
|
||
|
if len(k.KDCOptions.Bytes) < 4 {
|
||
|
tb := make([]byte, 4-len(k.KDCOptions.Bytes))
|
||
|
k.KDCOptions.Bytes = append(tb, k.KDCOptions.Bytes...)
|
||
|
k.KDCOptions.BitLength = len(k.KDCOptions.Bytes) * 8
|
||
|
}
|
||
|
k.CName = m.CName
|
||
|
k.Realm = m.Realm
|
||
|
k.SName = m.SName
|
||
|
k.From = m.From
|
||
|
k.Till = m.Till
|
||
|
k.RTime = m.RTime
|
||
|
k.Nonce = m.Nonce
|
||
|
k.EType = m.EType
|
||
|
k.Addresses = m.Addresses
|
||
|
k.EncAuthData = m.EncAuthData
|
||
|
if len(m.AdditionalTickets.Bytes) > 0 {
|
||
|
k.AdditionalTickets, err = unmarshalTicketsSequence(m.AdditionalTickets)
|
||
|
if err != nil {
|
||
|
return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling additional tickets")
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Marshal ASReq struct.
|
||
|
func (k *ASReq) Marshal() ([]byte, error) {
|
||
|
m := marshalKDCReq{
|
||
|
PVNO: k.PVNO,
|
||
|
MsgType: k.MsgType,
|
||
|
PAData: k.PAData,
|
||
|
}
|
||
|
b, err := k.ReqBody.Marshal()
|
||
|
if err != nil {
|
||
|
var mk []byte
|
||
|
return mk, err
|
||
|
}
|
||
|
m.ReqBody = asn1.RawValue{
|
||
|
Class: asn1.ClassContextSpecific,
|
||
|
IsCompound: true,
|
||
|
Tag: 4,
|
||
|
Bytes: b,
|
||
|
}
|
||
|
mk, err := asn1.Marshal(m)
|
||
|
if err != nil {
|
||
|
return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ")
|
||
|
}
|
||
|
mk = asn1tools.AddASNAppTag(mk, asnAppTag.ASREQ)
|
||
|
return mk, nil
|
||
|
}
|
||
|
|
||
|
// Marshal TGSReq struct.
|
||
|
func (k *TGSReq) Marshal() ([]byte, error) {
|
||
|
m := marshalKDCReq{
|
||
|
PVNO: k.PVNO,
|
||
|
MsgType: k.MsgType,
|
||
|
PAData: k.PAData,
|
||
|
}
|
||
|
b, err := k.ReqBody.Marshal()
|
||
|
if err != nil {
|
||
|
var mk []byte
|
||
|
return mk, err
|
||
|
}
|
||
|
m.ReqBody = asn1.RawValue{
|
||
|
Class: asn1.ClassContextSpecific,
|
||
|
IsCompound: true,
|
||
|
Tag: 4,
|
||
|
Bytes: b,
|
||
|
}
|
||
|
mk, err := asn1.Marshal(m)
|
||
|
if err != nil {
|
||
|
return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ")
|
||
|
}
|
||
|
mk = asn1tools.AddASNAppTag(mk, asnAppTag.TGSREQ)
|
||
|
return mk, nil
|
||
|
}
|
||
|
|
||
|
// Marshal KRB_KDC_REQ body struct.
|
||
|
func (k *KDCReqBody) Marshal() ([]byte, error) {
|
||
|
var b []byte
|
||
|
m := marshalKDCReqBody{
|
||
|
KDCOptions: k.KDCOptions,
|
||
|
CName: k.CName,
|
||
|
Realm: k.Realm,
|
||
|
SName: k.SName,
|
||
|
From: k.From,
|
||
|
Till: k.Till,
|
||
|
RTime: k.RTime,
|
||
|
Nonce: k.Nonce,
|
||
|
EType: k.EType,
|
||
|
Addresses: k.Addresses,
|
||
|
EncAuthData: k.EncAuthData,
|
||
|
}
|
||
|
rawtkts, err := MarshalTicketSequence(k.AdditionalTickets)
|
||
|
if err != nil {
|
||
|
return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body additional tickets")
|
||
|
}
|
||
|
//The asn1.rawValue needs the tag setting on it for where it is in the KDCReqBody
|
||
|
rawtkts.Tag = 11
|
||
|
if len(rawtkts.Bytes) > 0 {
|
||
|
m.AdditionalTickets = rawtkts
|
||
|
}
|
||
|
b, err = asn1.Marshal(m)
|
||
|
if err != nil {
|
||
|
return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body")
|
||
|
}
|
||
|
return b, nil
|
||
|
}
|