wrapToken.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. package gssapi
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "encoding/binary"
  6. "encoding/hex"
  7. "errors"
  8. "fmt"
  9. "gopkg.in/jcmturner/gokrb5.v7/crypto"
  10. "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
  11. "gopkg.in/jcmturner/gokrb5.v7/types"
  12. )
  13. /*
  14. From RFC 4121, section 4.2.6.2:
  15. Use of the GSS_Wrap() call yields a token (referred as the Wrap token
  16. in this document), which consists of a descriptive header, followed
  17. by a body portion that contains either the input user data in
  18. plaintext concatenated with the checksum, or the input user data
  19. encrypted. The GSS_Wrap() token SHALL have the following format:
  20. Octet no Name Description
  21. --------------------------------------------------------------
  22. 0..1 TOK_ID Identification field. Tokens emitted by
  23. GSS_Wrap() contain the hex value 05 04
  24. expressed in big-endian order in this
  25. field.
  26. 2 Flags Attributes field, as described in section
  27. 4.2.2.
  28. 3 Filler Contains the hex value FF.
  29. 4..5 EC Contains the "extra count" field, in big-
  30. endian order as described in section 4.2.3.
  31. 6..7 RRC Contains the "right rotation count" in big-
  32. endian order, as described in section
  33. 4.2.5.
  34. 8..15 SndSeqNum Sequence number field in clear text,
  35. expressed in big-endian order.
  36. 16..last Data Encrypted data for Wrap tokens with
  37. confidentiality, or plaintext data followed
  38. by the checksum for Wrap tokens without
  39. confidentiality, as described in section
  40. 4.2.4.
  41. Quick notes:
  42. - "EC" or "Extra Count" refers to the length of the checksum.
  43. - "Flags" (complete details in section 4.2.2) is a set of bits:
  44. - if bit 0 is set, it means the token was sent by the acceptor (generally the kerberized service).
  45. - bit 1 indicates that the token's payload is encrypted
  46. - bit 2 indicates if the message is protected using a subkey defined by the acceptor.
  47. - When computing checksums, EC and RRC MUST be set to 0.
  48. - Wrap Tokens are not ASN.1 encoded.
  49. */
  50. const (
  51. HdrLen = 16 // Length of the Wrap Token's header
  52. FillerByte byte = 0xFF
  53. )
  54. // WrapToken represents a GSS API Wrap token, as defined in RFC 4121.
  55. // It contains the header fields, the payload and the checksum, and provides
  56. // the logic for converting to/from bytes plus computing and verifying checksums
  57. type WrapToken struct {
  58. // const GSS Token ID: 0x0504
  59. Flags byte // contains three flags: acceptor, sealed, acceptor subkey
  60. // const Filler: 0xFF
  61. EC uint16 // checksum length. big-endian
  62. RRC uint16 // right rotation count. big-endian
  63. SndSeqNum uint64 // sender's sequence number. big-endian
  64. Payload []byte // your data! :)
  65. CheckSum []byte // authenticated checksum of { payload | header }
  66. }
  67. // Return the 2 bytes identifying a GSS API Wrap token
  68. func getGssWrapTokenId() *[2]byte {
  69. return &[2]byte{0x05, 0x04}
  70. }
  71. // Marshal the WrapToken into a byte slice.
  72. // The payload should have been set and the checksum computed, otherwise an error is returned.
  73. func (wt *WrapToken) Marshal() ([]byte, error) {
  74. if wt.CheckSum == nil {
  75. return nil, errors.New("checksum has not been set")
  76. }
  77. if wt.Payload == nil {
  78. return nil, errors.New("payload has not been set")
  79. }
  80. pldOffset := HdrLen // Offset of the payload in the token
  81. chkSOffset := HdrLen + len(wt.Payload) // Offset of the checksum in the token
  82. bytes := make([]byte, chkSOffset+int(wt.EC))
  83. copy(bytes[0:], getGssWrapTokenId()[:])
  84. bytes[2] = wt.Flags
  85. bytes[3] = FillerByte
  86. binary.BigEndian.PutUint16(bytes[4:6], wt.EC)
  87. binary.BigEndian.PutUint16(bytes[6:8], wt.RRC)
  88. binary.BigEndian.PutUint64(bytes[8:16], wt.SndSeqNum)
  89. copy(bytes[pldOffset:], wt.Payload)
  90. copy(bytes[chkSOffset:], wt.CheckSum)
  91. return bytes, nil
  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 WrapToken.
  95. // If the payload has not been set or the checksum has already been set, an error is returned.
  96. func (wt *WrapToken) SetCheckSum(key types.EncryptionKey, keyUsage uint32) error {
  97. if wt.Payload == nil {
  98. return errors.New("payload has not been set")
  99. }
  100. if wt.CheckSum != nil {
  101. return errors.New("checksum has already been computed")
  102. }
  103. chkSum, cErr := wt.computeCheckSum(key, keyUsage)
  104. if cErr != nil {
  105. return cErr
  106. }
  107. wt.CheckSum = chkSum
  108. return nil
  109. }
  110. // ComputeCheckSum computes and returns the checksum of this token, computed using the passed key and key usage.
  111. // Conforms to RFC 4121 in that the checksum will be computed over { body | header },
  112. // with the EC and RRC flags zeroed out.
  113. // In the context of Kerberos Wrap tokens, mostly keyusage GSSAPI_ACCEPTOR_SEAL (=22)
  114. // and GSSAPI_INITIATOR_SEAL (=24) will be used.
  115. // Note: This will NOT update the struct's Checksum field.
  116. func (wt *WrapToken) computeCheckSum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) {
  117. if wt.Payload == nil {
  118. return nil, errors.New("cannot compute checksum with uninitialized payload")
  119. }
  120. // Build a slice containing { payload | header }
  121. checksumMe := make([]byte, HdrLen+len(wt.Payload))
  122. copy(checksumMe[0:], wt.Payload)
  123. copy(checksumMe[len(wt.Payload):], getChecksumHeader(wt.Flags, wt.SndSeqNum))
  124. encType, err := crypto.GetEtype(key.KeyType)
  125. if err != nil {
  126. return nil, err
  127. }
  128. return encType.GetChecksumHash(key.KeyValue, checksumMe, keyUsage)
  129. }
  130. // Build a header suitable for a checksum computation
  131. func getChecksumHeader(flags byte, senderSeqNum uint64) []byte {
  132. header := make([]byte, 16)
  133. copy(header[0:], []byte{0x05, 0x04, flags, 0xFF, 0x00, 0x00, 0x00, 0x00})
  134. binary.BigEndian.PutUint64(header[8:], senderSeqNum)
  135. return header
  136. }
  137. // Verify computes the token's checksum with the provided key and usage,
  138. // and compares it to the checksum present in the token.
  139. // In case of any failure, (false, Err) is returned, with Err an explanatory error.
  140. func (wt *WrapToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) {
  141. computed, cErr := wt.computeCheckSum(key, keyUsage)
  142. if cErr != nil {
  143. return false, cErr
  144. }
  145. if !hmac.Equal(computed, wt.CheckSum) {
  146. return false, fmt.Errorf(
  147. "checksum mismatch. Computed: %s, Contained in token: %s",
  148. hex.EncodeToString(computed), hex.EncodeToString(wt.CheckSum))
  149. }
  150. return true, nil
  151. }
  152. // Unmarshal bytes into the corresponding WrapToken.
  153. // If expectFromAcceptor is true, we expect the token to have been emitted by the gss acceptor,
  154. // and will check the according flag, returning an error if the token does not match the expectation.
  155. func (wt *WrapToken) Unmarshal(b []byte, expectFromAcceptor bool) error {
  156. // Check if we can read a whole header
  157. if len(b) < 16 {
  158. return errors.New("bytes shorter than header length")
  159. }
  160. // Is the Token ID correct?
  161. if !bytes.Equal(getGssWrapTokenId()[:], b[0:2]) {
  162. return fmt.Errorf("wrong Token ID. Expected %s, was %s",
  163. hex.EncodeToString(getGssWrapTokenId()[:]),
  164. hex.EncodeToString(b[0:2]))
  165. }
  166. // Check the acceptor flag
  167. flags := b[2]
  168. isFromAcceptor := flags&0x01 == 1
  169. if isFromAcceptor && !expectFromAcceptor {
  170. return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor")
  171. }
  172. if !isFromAcceptor && expectFromAcceptor {
  173. return errors.New("expected acceptor flag is not set: expecting a token from the acceptor, not the initiator")
  174. }
  175. // Check the filler byte
  176. if b[3] != FillerByte {
  177. return fmt.Errorf("unexpected filler byte: expecting 0xFF, was %s ", hex.EncodeToString(b[3:4]))
  178. }
  179. checksumL := binary.BigEndian.Uint16(b[4:6])
  180. // Sanity check on the checksum length
  181. if int(checksumL) > len(b)-HdrLen {
  182. return fmt.Errorf("inconsistent checksum length: %d bytes to parse, checksum length is %d", len(b), checksumL)
  183. }
  184. wt.Flags = flags
  185. wt.EC = checksumL
  186. wt.RRC = binary.BigEndian.Uint16(b[6:8])
  187. wt.SndSeqNum = binary.BigEndian.Uint64(b[8:16])
  188. wt.Payload = b[16 : len(b)-int(checksumL)]
  189. wt.CheckSum = b[len(b)-int(checksumL):]
  190. return nil
  191. }
  192. // NewInitiatorWrapToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum.
  193. // Other flags are set to 0, and the RRC and sequence number are initialized to 0.
  194. // Note that in certain circumstances you may need to provide a sequence number that has been defined earlier.
  195. // This is currently not supported.
  196. func NewInitiatorWrapToken(payload []byte, key types.EncryptionKey) (*WrapToken, error) {
  197. encType, err := crypto.GetEtype(key.KeyType)
  198. if err != nil {
  199. return nil, err
  200. }
  201. token := WrapToken{
  202. Flags: 0x00, // all zeroed out (this is a token sent by the initiator)
  203. // Checksum size: length of output of the HMAC function, in bytes.
  204. EC: uint16(encType.GetHMACBitLength() / 8),
  205. RRC: 0,
  206. SndSeqNum: 0,
  207. Payload: payload,
  208. }
  209. if err := token.SetCheckSum(key, keyusage.GSSAPI_INITIATOR_SEAL); err != nil {
  210. return nil, err
  211. }
  212. return &token, nil
  213. }