|
| 1 | +package kerberos |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "time" |
| 6 | + |
| 7 | + kerbcrypto "github.com/TheManticoreProject/Manticore/network/kerberos/v5/crypto" |
| 8 | + "github.com/TheManticoreProject/Manticore/network/kerberos/v5/messages" |
| 9 | +) |
| 10 | + |
| 11 | +// buildU2UTGSReq constructs the TGS-REQ for a user-to-user exchange: a normal |
| 12 | +// PA-TGS-REQ (AP-REQ over the client's TGT), the ENC-TKT-IN-SKEY KDC option, the |
| 13 | +// target user's TGT in additional-tickets, and the target user as sname. It is |
| 14 | +// separated from GetTGSU2U so the request shape can be tested without a KDC. |
| 15 | +func (c *KerberosClient) buildU2UTGSReq(targetUser, targetRealm string, targetTGTRaw []byte, nonce int) (*messages.TGSReq, error) { |
| 16 | + apReqBytes, err := c.buildAPReq() |
| 17 | + if err != nil { |
| 18 | + return nil, fmt.Errorf("kerberos: build AP-REQ: %w", err) |
| 19 | + } |
| 20 | + sname := messages.PrincipalName{ |
| 21 | + NameType: messages.NameTypePrincipal, |
| 22 | + NameString: []string{targetUser}, |
| 23 | + } |
| 24 | + realm := c.realm |
| 25 | + if targetRealm != "" { |
| 26 | + realm = targetRealm |
| 27 | + } |
| 28 | + return &messages.TGSReq{ |
| 29 | + PVNO: messages.KerberosV5, |
| 30 | + MsgType: messages.MsgTypeTGSReq, |
| 31 | + PAData: []messages.PAData{ |
| 32 | + {PADataType: messages.PATGSReq, PADataValue: apReqBytes}, |
| 33 | + }, |
| 34 | + ReqBody: messages.KDCReqBody{ |
| 35 | + KDCOptions: encodeKDCOptions( |
| 36 | + kdcOptionForwardable, |
| 37 | + kdcOptionRenewable, |
| 38 | + kdcOptionCanonicalize, |
| 39 | + kdcOptionEncTktInSKey, |
| 40 | + ), |
| 41 | + Realm: realm, |
| 42 | + SName: sname, |
| 43 | + Till: time.Now().UTC().Add(24 * time.Hour), |
| 44 | + Nonce: nonce, |
| 45 | + EType: []int{ |
| 46 | + messages.ETypeAES256CTSHMACSHA196, |
| 47 | + messages.ETypeAES128CTSHMACSHA196, |
| 48 | + messages.ETypeRC4HMAC, |
| 49 | + }, |
| 50 | + AdditTicketsRaw: [][]byte{targetTGTRaw}, |
| 51 | + }, |
| 52 | + }, nil |
| 53 | +} |
| 54 | + |
| 55 | +// GetTGSU2U performs a user-to-user (U2U) TGS exchange: it requests a service |
| 56 | +// ticket to the target user (targetUser in targetRealm, or the client's realm |
| 57 | +// if empty), presenting the target user's TGT (targetTGTRaw, raw APPLICATION[1] |
| 58 | +// bytes) in additional-tickets with the ENC-TKT-IN-SKEY option. The KDC issues a |
| 59 | +// ticket whose enc-part is encrypted with the session key of the target's TGT |
| 60 | +// (not a long-term key), which the target verifies via an AP-REQ carrying the |
| 61 | +// USE-SESSION-KEY option. |
| 62 | +// |
| 63 | +// GetTGT must have succeeded first (for the client's own TGT). Returns the |
| 64 | +// service ticket, its raw bytes, and the ticket session key. |
| 65 | +func (c *KerberosClient) GetTGSU2U(targetUser, targetRealm string, targetTGTRaw []byte) (messages.Ticket, []byte, []byte, error) { |
| 66 | + if !c.hasTGT { |
| 67 | + return messages.Ticket{}, nil, nil, fmt.Errorf("kerberos: no TGT: call GetTGT first") |
| 68 | + } |
| 69 | + if targetUser == "" { |
| 70 | + return messages.Ticket{}, nil, nil, fmt.Errorf("kerberos: U2U requires a target user") |
| 71 | + } |
| 72 | + if len(targetTGTRaw) == 0 { |
| 73 | + return messages.Ticket{}, nil, nil, fmt.Errorf("kerberos: U2U requires the target user's TGT") |
| 74 | + } |
| 75 | + |
| 76 | + nonce := randomNonce() |
| 77 | + tgsReq, err := c.buildU2UTGSReq(targetUser, targetRealm, targetTGTRaw, nonce) |
| 78 | + if err != nil { |
| 79 | + return messages.Ticket{}, nil, nil, err |
| 80 | + } |
| 81 | + |
| 82 | + tgsReqBytes, err := tgsReq.Marshal() |
| 83 | + if err != nil { |
| 84 | + return messages.Ticket{}, nil, nil, fmt.Errorf("kerberos: marshal U2U TGS-REQ: %w", err) |
| 85 | + } |
| 86 | + resp, err := kdcSend(c.kdcHost, defaultKDCPort, tgsReqBytes) |
| 87 | + if err != nil { |
| 88 | + return messages.Ticket{}, nil, nil, err |
| 89 | + } |
| 90 | + |
| 91 | + var krbErr messages.KRBError |
| 92 | + if _, parseErr := krbErr.Unmarshal(resp); parseErr == nil { |
| 93 | + return messages.Ticket{}, nil, nil, fmt.Errorf("kerberos: U2U error %d: %s", krbErr.ErrorCode, krbErr.EText) |
| 94 | + } |
| 95 | + |
| 96 | + var tgsRep messages.TGSRep |
| 97 | + if _, err := tgsRep.Unmarshal(resp); err != nil { |
| 98 | + return messages.Ticket{}, nil, nil, fmt.Errorf("kerberos: parse U2U TGS-REP: %w", err) |
| 99 | + } |
| 100 | + |
| 101 | + encPlain, err := kerbcrypto.Decrypt(c.sessionEType, c.sessionKey, kerbcrypto.KeyUsageTGSRepEncSessionKey, tgsRep.EncPart.Cipher) |
| 102 | + if err != nil { |
| 103 | + return messages.Ticket{}, nil, nil, fmt.Errorf("kerberos: decrypt U2U TGS-REP enc-part: %w", err) |
| 104 | + } |
| 105 | + var encTGSRep messages.EncTGSRepPart |
| 106 | + if _, err := encTGSRep.Unmarshal(encPlain); err != nil { |
| 107 | + return messages.Ticket{}, nil, nil, fmt.Errorf("kerberos: parse U2U EncTGSRepPart: %w", err) |
| 108 | + } |
| 109 | + if encTGSRep.Nonce != nonce { |
| 110 | + return messages.Ticket{}, nil, nil, fmt.Errorf("kerberos: U2U nonce mismatch: got %d, want %d", encTGSRep.Nonce, nonce) |
| 111 | + } |
| 112 | + |
| 113 | + return tgsRep.Ticket, tgsRep.TicketRaw, encTGSRep.Key.KeyValue, nil |
| 114 | +} |
0 commit comments