blob: 9dbf96baa8e5a31e66c8b1addccda2055d4db8dc [file] [log] [blame]
Scott Baker8487c5d2019-10-18 12:49:46 -07001package gssapi
2
3import (
4 "bytes"
5 "crypto/hmac"
6 "encoding/binary"
7 "encoding/hex"
8 "errors"
9 "fmt"
10
11 "gopkg.in/jcmturner/gokrb5.v7/crypto"
12 "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
13 "gopkg.in/jcmturner/gokrb5.v7/types"
14)
15
16/*
17From RFC 4121, section 4.2.6.2:
18
19 Use of the GSS_Wrap() call yields a token (referred as the Wrap token
20 in this document), which consists of a descriptive header, followed
21 by a body portion that contains either the input user data in
22 plaintext concatenated with the checksum, or the input user data
23 encrypted. The GSS_Wrap() token SHALL have the following format:
24
25 Octet no Name Description
26 --------------------------------------------------------------
27 0..1 TOK_ID Identification field. Tokens emitted by
28 GSS_Wrap() contain the hex value 05 04
29 expressed in big-endian order in this
30 field.
31 2 Flags Attributes field, as described in section
32 4.2.2.
33 3 Filler Contains the hex value FF.
34 4..5 EC Contains the "extra count" field, in big-
35 endian order as described in section 4.2.3.
36 6..7 RRC Contains the "right rotation count" in big-
37 endian order, as described in section
38 4.2.5.
39 8..15 SndSeqNum Sequence number field in clear text,
40 expressed in big-endian order.
41 16..last Data Encrypted data for Wrap tokens with
42 confidentiality, or plaintext data followed
43 by the checksum for Wrap tokens without
44 confidentiality, as described in section
45 4.2.4.
46
47Quick notes:
48 - "EC" or "Extra Count" refers to the length of the checksum.
49 - "Flags" (complete details in section 4.2.2) is a set of bits:
50 - if bit 0 is set, it means the token was sent by the acceptor (generally the kerberized service).
51 - bit 1 indicates that the token's payload is encrypted
52 - bit 2 indicates if the message is protected using a subkey defined by the acceptor.
53 - When computing checksums, EC and RRC MUST be set to 0.
54 - Wrap Tokens are not ASN.1 encoded.
55*/
56const (
57 HdrLen = 16 // Length of the Wrap Token's header
58 FillerByte byte = 0xFF
59)
60
61// WrapToken represents a GSS API Wrap token, as defined in RFC 4121.
62// It contains the header fields, the payload and the checksum, and provides
63// the logic for converting to/from bytes plus computing and verifying checksums
64type WrapToken struct {
65 // const GSS Token ID: 0x0504
66 Flags byte // contains three flags: acceptor, sealed, acceptor subkey
67 // const Filler: 0xFF
68 EC uint16 // checksum length. big-endian
69 RRC uint16 // right rotation count. big-endian
70 SndSeqNum uint64 // sender's sequence number. big-endian
71 Payload []byte // your data! :)
72 CheckSum []byte // authenticated checksum of { payload | header }
73}
74
75// Return the 2 bytes identifying a GSS API Wrap token
76func getGssWrapTokenId() *[2]byte {
77 return &[2]byte{0x05, 0x04}
78}
79
80// Marshal the WrapToken into a byte slice.
81// The payload should have been set and the checksum computed, otherwise an error is returned.
82func (wt *WrapToken) Marshal() ([]byte, error) {
83 if wt.CheckSum == nil {
84 return nil, errors.New("checksum has not been set")
85 }
86 if wt.Payload == nil {
87 return nil, errors.New("payload has not been set")
88 }
89
90 pldOffset := HdrLen // Offset of the payload in the token
91 chkSOffset := HdrLen + len(wt.Payload) // Offset of the checksum in the token
92
93 bytes := make([]byte, chkSOffset+int(wt.EC))
94 copy(bytes[0:], getGssWrapTokenId()[:])
95 bytes[2] = wt.Flags
96 bytes[3] = FillerByte
97 binary.BigEndian.PutUint16(bytes[4:6], wt.EC)
98 binary.BigEndian.PutUint16(bytes[6:8], wt.RRC)
99 binary.BigEndian.PutUint64(bytes[8:16], wt.SndSeqNum)
100 copy(bytes[pldOffset:], wt.Payload)
101 copy(bytes[chkSOffset:], wt.CheckSum)
102 return bytes, nil
103}
104
105// SetCheckSum uses the passed encryption key and key usage to compute the checksum over the payload and
106// the header, and sets the CheckSum field of this WrapToken.
107// If the payload has not been set or the checksum has already been set, an error is returned.
108func (wt *WrapToken) SetCheckSum(key types.EncryptionKey, keyUsage uint32) error {
109 if wt.Payload == nil {
110 return errors.New("payload has not been set")
111 }
112 if wt.CheckSum != nil {
113 return errors.New("checksum has already been computed")
114 }
115 chkSum, cErr := wt.computeCheckSum(key, keyUsage)
116 if cErr != nil {
117 return cErr
118 }
119 wt.CheckSum = chkSum
120 return nil
121}
122
123// ComputeCheckSum computes and returns the checksum of this token, computed using the passed key and key usage.
124// Conforms to RFC 4121 in that the checksum will be computed over { body | header },
125// with the EC and RRC flags zeroed out.
126// In the context of Kerberos Wrap tokens, mostly keyusage GSSAPI_ACCEPTOR_SEAL (=22)
127// and GSSAPI_INITIATOR_SEAL (=24) will be used.
128// Note: This will NOT update the struct's Checksum field.
129func (wt *WrapToken) computeCheckSum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) {
130 if wt.Payload == nil {
131 return nil, errors.New("cannot compute checksum with uninitialized payload")
132 }
133 // Build a slice containing { payload | header }
134 checksumMe := make([]byte, HdrLen+len(wt.Payload))
135 copy(checksumMe[0:], wt.Payload)
136 copy(checksumMe[len(wt.Payload):], getChecksumHeader(wt.Flags, wt.SndSeqNum))
137
138 encType, err := crypto.GetEtype(key.KeyType)
139 if err != nil {
140 return nil, err
141 }
142 return encType.GetChecksumHash(key.KeyValue, checksumMe, keyUsage)
143}
144
145// Build a header suitable for a checksum computation
146func getChecksumHeader(flags byte, senderSeqNum uint64) []byte {
147 header := make([]byte, 16)
148 copy(header[0:], []byte{0x05, 0x04, flags, 0xFF, 0x00, 0x00, 0x00, 0x00})
149 binary.BigEndian.PutUint64(header[8:], senderSeqNum)
150 return header
151}
152
153// Verify computes the token's checksum with the provided key and usage,
154// and compares it to the checksum present in the token.
155// In case of any failure, (false, Err) is returned, with Err an explanatory error.
156func (wt *WrapToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) {
157 computed, cErr := wt.computeCheckSum(key, keyUsage)
158 if cErr != nil {
159 return false, cErr
160 }
161 if !hmac.Equal(computed, wt.CheckSum) {
162 return false, fmt.Errorf(
163 "checksum mismatch. Computed: %s, Contained in token: %s",
164 hex.EncodeToString(computed), hex.EncodeToString(wt.CheckSum))
165 }
166 return true, nil
167}
168
169// Unmarshal bytes into the corresponding WrapToken.
170// If expectFromAcceptor is true, we expect the token to have been emitted by the gss acceptor,
171// and will check the according flag, returning an error if the token does not match the expectation.
172func (wt *WrapToken) Unmarshal(b []byte, expectFromAcceptor bool) error {
173 // Check if we can read a whole header
174 if len(b) < 16 {
175 return errors.New("bytes shorter than header length")
176 }
177 // Is the Token ID correct?
178 if !bytes.Equal(getGssWrapTokenId()[:], b[0:2]) {
179 return fmt.Errorf("wrong Token ID. Expected %s, was %s",
180 hex.EncodeToString(getGssWrapTokenId()[:]),
181 hex.EncodeToString(b[0:2]))
182 }
183 // Check the acceptor flag
184 flags := b[2]
185 isFromAcceptor := flags&0x01 == 1
186 if isFromAcceptor && !expectFromAcceptor {
187 return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor")
188 }
189 if !isFromAcceptor && expectFromAcceptor {
190 return errors.New("expected acceptor flag is not set: expecting a token from the acceptor, not the initiator")
191 }
192 // Check the filler byte
193 if b[3] != FillerByte {
194 return fmt.Errorf("unexpected filler byte: expecting 0xFF, was %s ", hex.EncodeToString(b[3:4]))
195 }
196 checksumL := binary.BigEndian.Uint16(b[4:6])
197 // Sanity check on the checksum length
198 if int(checksumL) > len(b)-HdrLen {
199 return fmt.Errorf("inconsistent checksum length: %d bytes to parse, checksum length is %d", len(b), checksumL)
200 }
201
202 wt.Flags = flags
203 wt.EC = checksumL
204 wt.RRC = binary.BigEndian.Uint16(b[6:8])
205 wt.SndSeqNum = binary.BigEndian.Uint64(b[8:16])
206 wt.Payload = b[16 : len(b)-int(checksumL)]
207 wt.CheckSum = b[len(b)-int(checksumL):]
208 return nil
209}
210
211// NewInitiatorWrapToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum.
212// Other flags are set to 0, and the RRC and sequence number are initialized to 0.
213// Note that in certain circumstances you may need to provide a sequence number that has been defined earlier.
214// This is currently not supported.
215func NewInitiatorWrapToken(payload []byte, key types.EncryptionKey) (*WrapToken, error) {
216 encType, err := crypto.GetEtype(key.KeyType)
217 if err != nil {
218 return nil, err
219 }
220
221 token := WrapToken{
222 Flags: 0x00, // all zeroed out (this is a token sent by the initiator)
223 // Checksum size: length of output of the HMAC function, in bytes.
224 EC: uint16(encType.GetHMACBitLength() / 8),
225 RRC: 0,
226 SndSeqNum: 0,
227 Payload: payload,
228 }
229
230 if err := token.SetCheckSum(key, keyusage.GSSAPI_INITIATOR_SEAL); err != nil {
231 return nil, err
232 }
233
234 return &token, nil
235}