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  }