blob: ea7d0543e090a7c63cf537c44e0c3d790fd9acd8 [file] [log] [blame]
David K. Bainbridgebd6b2882021-08-26 13:31:02 +00001package gssapi
2
3import (
4 "bytes"
5 "crypto/hmac"
6 "encoding/binary"
7 "encoding/hex"
8 "errors"
9 "fmt"
10
11 "github.com/jcmturner/gokrb5/v8/crypto"
12 "github.com/jcmturner/gokrb5/v8/iana/keyusage"
13 "github.com/jcmturner/gokrb5/v8/types"
14)
15
16// RFC 4121, section 4.2.6.2
17
18const (
19 // HdrLen is the length of the Wrap Token's header
20 HdrLen = 16
21 // FillerByte is a filler in the WrapToken structure
22 FillerByte byte = 0xFF
23)
24
25// WrapToken represents a GSS API Wrap token, as defined in RFC 4121.
26// It contains the header fields, the payload and the checksum, and provides
27// the logic for converting to/from bytes plus computing and verifying checksums
28type WrapToken struct {
29 // const GSS Token ID: 0x0504
30 Flags byte // contains three flags: acceptor, sealed, acceptor subkey
31 // const Filler: 0xFF
32 EC uint16 // checksum length. big-endian
33 RRC uint16 // right rotation count. big-endian
34 SndSeqNum uint64 // sender's sequence number. big-endian
35 Payload []byte // your data! :)
36 CheckSum []byte // authenticated checksum of { payload | header }
37}
38
39// Return the 2 bytes identifying a GSS API Wrap token
40func getGssWrapTokenId() *[2]byte {
41 return &[2]byte{0x05, 0x04}
42}
43
44// Marshal the WrapToken into a byte slice.
45// The payload should have been set and the checksum computed, otherwise an error is returned.
46func (wt *WrapToken) Marshal() ([]byte, error) {
47 if wt.CheckSum == nil {
48 return nil, errors.New("checksum has not been set")
49 }
50 if wt.Payload == nil {
51 return nil, errors.New("payload has not been set")
52 }
53
54 pldOffset := HdrLen // Offset of the payload in the token
55 chkSOffset := HdrLen + len(wt.Payload) // Offset of the checksum in the token
56
57 bytes := make([]byte, chkSOffset+int(wt.EC))
58 copy(bytes[0:], getGssWrapTokenId()[:])
59 bytes[2] = wt.Flags
60 bytes[3] = FillerByte
61 binary.BigEndian.PutUint16(bytes[4:6], wt.EC)
62 binary.BigEndian.PutUint16(bytes[6:8], wt.RRC)
63 binary.BigEndian.PutUint64(bytes[8:16], wt.SndSeqNum)
64 copy(bytes[pldOffset:], wt.Payload)
65 copy(bytes[chkSOffset:], wt.CheckSum)
66 return bytes, nil
67}
68
69// SetCheckSum uses the passed encryption key and key usage to compute the checksum over the payload and
70// the header, and sets the CheckSum field of this WrapToken.
71// If the payload has not been set or the checksum has already been set, an error is returned.
72func (wt *WrapToken) SetCheckSum(key types.EncryptionKey, keyUsage uint32) error {
73 if wt.Payload == nil {
74 return errors.New("payload has not been set")
75 }
76 if wt.CheckSum != nil {
77 return errors.New("checksum has already been computed")
78 }
79 chkSum, cErr := wt.computeCheckSum(key, keyUsage)
80 if cErr != nil {
81 return cErr
82 }
83 wt.CheckSum = chkSum
84 return nil
85}
86
87// ComputeCheckSum computes and returns the checksum of this token, computed using the passed key and key usage.
88// Note: This will NOT update the struct's Checksum field.
89func (wt *WrapToken) computeCheckSum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) {
90 if wt.Payload == nil {
91 return nil, errors.New("cannot compute checksum with uninitialized payload")
92 }
93 // Build a slice containing { payload | header }
94 checksumMe := make([]byte, HdrLen+len(wt.Payload))
95 copy(checksumMe[0:], wt.Payload)
96 copy(checksumMe[len(wt.Payload):], getChecksumHeader(wt.Flags, wt.SndSeqNum))
97
98 encType, err := crypto.GetEtype(key.KeyType)
99 if err != nil {
100 return nil, err
101 }
102 return encType.GetChecksumHash(key.KeyValue, checksumMe, keyUsage)
103}
104
105// Build a header suitable for a checksum computation
106func getChecksumHeader(flags byte, senderSeqNum uint64) []byte {
107 header := make([]byte, 16)
108 copy(header[0:], []byte{0x05, 0x04, flags, 0xFF, 0x00, 0x00, 0x00, 0x00})
109 binary.BigEndian.PutUint64(header[8:], senderSeqNum)
110 return header
111}
112
113// Verify computes the token's checksum with the provided key and usage,
114// and compares it to the checksum present in the token.
115// In case of any failure, (false, Err) is returned, with Err an explanatory error.
116func (wt *WrapToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) {
117 computed, cErr := wt.computeCheckSum(key, keyUsage)
118 if cErr != nil {
119 return false, cErr
120 }
121 if !hmac.Equal(computed, wt.CheckSum) {
122 return false, fmt.Errorf(
123 "checksum mismatch. Computed: %s, Contained in token: %s",
124 hex.EncodeToString(computed), hex.EncodeToString(wt.CheckSum))
125 }
126 return true, nil
127}
128
129// Unmarshal bytes into the corresponding WrapToken.
130// If expectFromAcceptor is true, we expect the token to have been emitted by the gss acceptor,
131// and will check the according flag, returning an error if the token does not match the expectation.
132func (wt *WrapToken) Unmarshal(b []byte, expectFromAcceptor bool) error {
133 // Check if we can read a whole header
134 if len(b) < 16 {
135 return errors.New("bytes shorter than header length")
136 }
137 // Is the Token ID correct?
138 if !bytes.Equal(getGssWrapTokenId()[:], b[0:2]) {
139 return fmt.Errorf("wrong Token ID. Expected %s, was %s",
140 hex.EncodeToString(getGssWrapTokenId()[:]),
141 hex.EncodeToString(b[0:2]))
142 }
143 // Check the acceptor flag
144 flags := b[2]
145 isFromAcceptor := flags&0x01 == 1
146 if isFromAcceptor && !expectFromAcceptor {
147 return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor")
148 }
149 if !isFromAcceptor && expectFromAcceptor {
150 return errors.New("expected acceptor flag is not set: expecting a token from the acceptor, not the initiator")
151 }
152 // Check the filler byte
153 if b[3] != FillerByte {
154 return fmt.Errorf("unexpected filler byte: expecting 0xFF, was %s ", hex.EncodeToString(b[3:4]))
155 }
156 checksumL := binary.BigEndian.Uint16(b[4:6])
157 // Sanity check on the checksum length
158 if int(checksumL) > len(b)-HdrLen {
159 return fmt.Errorf("inconsistent checksum length: %d bytes to parse, checksum length is %d", len(b), checksumL)
160 }
161
162 wt.Flags = flags
163 wt.EC = checksumL
164 wt.RRC = binary.BigEndian.Uint16(b[6:8])
165 wt.SndSeqNum = binary.BigEndian.Uint64(b[8:16])
166 wt.Payload = b[16 : len(b)-int(checksumL)]
167 wt.CheckSum = b[len(b)-int(checksumL):]
168 return nil
169}
170
171// NewInitiatorWrapToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum.
172// Other flags are set to 0, and the RRC and sequence number are initialized to 0.
173// Note that in certain circumstances you may need to provide a sequence number that has been defined earlier.
174// This is currently not supported.
175func NewInitiatorWrapToken(payload []byte, key types.EncryptionKey) (*WrapToken, error) {
176 encType, err := crypto.GetEtype(key.KeyType)
177 if err != nil {
178 return nil, err
179 }
180
181 token := WrapToken{
182 Flags: 0x00, // all zeroed out (this is a token sent by the initiator)
183 // Checksum size: length of output of the HMAC function, in bytes.
184 EC: uint16(encType.GetHMACBitLength() / 8),
185 RRC: 0,
186 SndSeqNum: 0,
187 Payload: payload,
188 }
189
190 if err := token.SetCheckSum(key, keyusage.GSSAPI_INITIATOR_SEAL); err != nil {
191 return nil, err
192 }
193
194 return &token, nil
195}