blob: 856412baf78b9bad09b11ca4960e356b8ccd2c6e [file] [log] [blame]
Scott Baker2c1c4822019-10-16 11:02:41 -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.1:
18
19 Use of the GSS_GetMIC() call yields a token (referred as the MIC
20 token in this document), separate from the user data being protected,
21 which can be used to verify the integrity of that data as received.
22 The token has the following format:
23
24 Octet no Name Description
25 --------------------------------------------------------------
26 0..1 TOK_ID Identification field. Tokens emitted by
27 GSS_GetMIC() contain the hex value 04 04
28 expressed in big-endian order in this
29 field.
30 2 Flags Attributes field, as described in section
31 4.2.2.
32 3..7 Filler Contains five octets of hex value FF.
33 8..15 SND_SEQ Sequence number field in clear text,
34 expressed in big-endian order.
35 16..last SGN_CKSUM Checksum of the "to-be-signed" data and
36 octet 0..15, as described in section 4.2.4.
37
38 The Filler field is included in the checksum calculation for
39 simplicity.
40
41*/
42
43const (
44 // MICTokenFlagSentByAcceptor - this flag indicates the sender is the context acceptor. When not set, it indicates the sender is the context initiator
45 MICTokenFlagSentByAcceptor = 1 << iota
46 // MICTokenFlagSealed - this flag indicates confidentiality is provided for. It SHALL NOT be set in MIC tokens
47 MICTokenFlagSealed
48 // MICTokenFlagAcceptorSubkey - a subkey asserted by the context acceptor is used to protect the message
49 MICTokenFlagAcceptorSubkey
50)
51
52const (
53 micHdrLen = 16 // Length of the MIC Token's header
54)
55
56// MICToken represents a GSS API MIC token, as defined in RFC 4121.
57// It contains the header fields, the payload (this is not transmitted) and
58// the checksum, and provides the logic for converting to/from bytes plus
59// computing and verifying checksums
60type MICToken struct {
61 // const GSS Token ID: 0x0404
62 Flags byte // contains three flags: acceptor, sealed, acceptor subkey
63 // const Filler: 0xFF 0xFF 0xFF 0xFF 0xFF
64 SndSeqNum uint64 // sender's sequence number. big-endian
65 Payload []byte // your data! :)
66 Checksum []byte // checksum of { payload | header }
67}
68
69// Return the 2 bytes identifying a GSS API MIC token
70func getGSSMICTokenID() *[2]byte {
71 return &[2]byte{0x04, 0x04}
72}
73
74// Return the filler bytes used in header
75func fillerBytes() *[5]byte {
76 return &[5]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
77}
78
79// Marshal the MICToken into a byte slice.
80// The payload should have been set and the checksum computed, otherwise an error is returned.
81func (mt *MICToken) Marshal() ([]byte, error) {
82 if mt.Checksum == nil {
83 return nil, errors.New("checksum has not been set")
84 }
85
86 bytes := make([]byte, micHdrLen+len(mt.Checksum))
87 copy(bytes[0:micHdrLen], mt.getMICChecksumHeader()[:])
88 copy(bytes[micHdrLen:], mt.Checksum)
89
90 return bytes, nil
91}
92
93// SetChecksum uses the passed encryption key and key usage to compute the checksum over the payload and
94// the header, and sets the Checksum field of this MICToken.
95// If the payload has not been set or the checksum has already been set, an error is returned.
96func (mt *MICToken) SetChecksum(key types.EncryptionKey, keyUsage uint32) error {
97 if mt.Checksum != nil {
98 return errors.New("checksum has already been computed")
99 }
100 checksum, err := mt.checksum(key, keyUsage)
101 if err != nil {
102 return err
103 }
104 mt.Checksum = checksum
105 return nil
106}
107
108// Compute and return the checksum of this token, computed using the passed key and key usage.
109// Confirms to RFC 4121 in that the checksum will be computed over { body | header }.
110// In the context of Kerberos MIC tokens, mostly keyusage GSSAPI_ACCEPTOR_SIGN (=23)
111// and GSSAPI_INITIATOR_SIGN (=25) will be used.
112// Note: This will NOT update the struct's Checksum field.
113func (mt *MICToken) checksum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) {
114 if mt.Payload == nil {
115 return nil, errors.New("cannot compute checksum with uninitialized payload")
116 }
117 d := make([]byte, micHdrLen+len(mt.Payload))
118 copy(d[0:], mt.Payload)
119 copy(d[len(mt.Payload):], mt.getMICChecksumHeader())
120
121 encType, err := crypto.GetEtype(key.KeyType)
122 if err != nil {
123 return nil, err
124 }
125 return encType.GetChecksumHash(key.KeyValue, d, keyUsage)
126}
127
128// Build a header suitable for a checksum computation
129func (mt *MICToken) getMICChecksumHeader() []byte {
130 header := make([]byte, micHdrLen)
131 copy(header[0:2], getGSSMICTokenID()[:])
132 header[2] = mt.Flags
133 copy(header[3:8], fillerBytes()[:])
134 binary.BigEndian.PutUint64(header[8:16], mt.SndSeqNum)
135 return header
136}
137
138// Verify computes the token's checksum with the provided key and usage,
139// and compares it to the checksum present in the token.
140// In case of any failure, (false, err) is returned, with err an explanatory error.
141func (mt *MICToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) {
142 computed, err := mt.checksum(key, keyUsage)
143 if err != nil {
144 return false, err
145 }
146 if !hmac.Equal(computed, mt.Checksum) {
147 return false, fmt.Errorf(
148 "checksum mismatch. Computed: %s, Contained in token: %s",
149 hex.EncodeToString(computed), hex.EncodeToString(mt.Checksum))
150 }
151 return true, nil
152}
153
154// Unmarshal bytes into the corresponding MICToken.
155// If expectFromAcceptor is true we expect the token to have been emitted by the gss acceptor,
156// and will check the according flag, returning an error if the token does not match the expectation.
157func (mt *MICToken) Unmarshal(b []byte, expectFromAcceptor bool) error {
158 if len(b) < micHdrLen {
159 return errors.New("bytes shorter than header length")
160 }
161 if !bytes.Equal(getGSSMICTokenID()[:], b[0:2]) {
162 return fmt.Errorf("wrong Token ID, Expected %s, was %s",
163 hex.EncodeToString(getGSSMICTokenID()[:]),
164 hex.EncodeToString(b[0:2]))
165 }
166 flags := b[2]
167 isFromAcceptor := flags&MICTokenFlagSentByAcceptor != 0
168 if isFromAcceptor && !expectFromAcceptor {
169 return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor")
170 }
171 if !isFromAcceptor && expectFromAcceptor {
172 return errors.New("unexpected acceptor flag is not set: expecting a token from the acceptor, not in the initiator")
173 }
174 if !bytes.Equal(b[3:8], fillerBytes()[:]) {
175 return fmt.Errorf("unexpected filler bytes: expecting %s, was %s",
176 hex.EncodeToString(fillerBytes()[:]),
177 hex.EncodeToString(b[3:8]))
178 }
179
180 mt.Flags = flags
181 mt.SndSeqNum = binary.BigEndian.Uint64(b[8:16])
182 mt.Checksum = b[micHdrLen:]
183 return nil
184}
185
186// NewInitiatorMICToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum.
187// Other flags are set to 0.
188// Note that in certain circumstances you may need to provide a sequence number that has been defined earlier.
189// This is currently not supported.
190func NewInitiatorMICToken(payload []byte, key types.EncryptionKey) (*MICToken, error) {
191 token := MICToken{
192 Flags: 0x00,
193 SndSeqNum: 0,
194 Payload: payload,
195 }
196
197 if err := token.SetChecksum(key, keyusage.GSSAPI_INITIATOR_SIGN); err != nil {
198 return nil, err
199 }
200
201 return &token, nil
202}