MICToken.go 5.9 KB

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