github.com/jcmturner/gokrb5/v8@v8.4.4/gssapi/MICToken.go (about) 1 package gssapi 2 3 import ( 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 18 const ( 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 27 const ( 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 35 type 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 45 func getGSSMICTokenID() *[2]byte { 46 return &[2]byte{0x04, 0x04} 47 } 48 49 // Return the filler bytes used in header 50 func 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. 56 func (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. 71 func (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. 85 func (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 101 func (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. 113 func (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. 129 func (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. 162 func 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 }