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