123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- package gssapi
- import (
- "bytes"
- "crypto/hmac"
- "encoding/binary"
- "encoding/hex"
- "errors"
- "fmt"
- "github.com/jcmturner/gokrb5/v8/crypto"
- "github.com/jcmturner/gokrb5/v8/iana/keyusage"
- "github.com/jcmturner/gokrb5/v8/types"
- )
- // RFC 4121, section 4.2.6.1
- const (
- // MICTokenFlagSentByAcceptor - this flag indicates the sender is the context acceptor. When not set, it indicates the sender is the context initiator
- MICTokenFlagSentByAcceptor = 1 << iota
- // MICTokenFlagSealed - this flag indicates confidentiality is provided for. It SHALL NOT be set in MIC tokens
- MICTokenFlagSealed
- // MICTokenFlagAcceptorSubkey - a subkey asserted by the context acceptor is used to protect the message
- MICTokenFlagAcceptorSubkey
- )
- const (
- micHdrLen = 16 // Length of the MIC Token's header
- )
- // MICToken represents a GSS API MIC token, as defined in RFC 4121.
- // It contains the header fields, the payload (this is not transmitted) and
- // the checksum, and provides the logic for converting to/from bytes plus
- // computing and verifying checksums
- type MICToken struct {
- // const GSS Token ID: 0x0404
- Flags byte // contains three flags: acceptor, sealed, acceptor subkey
- // const Filler: 0xFF 0xFF 0xFF 0xFF 0xFF
- SndSeqNum uint64 // sender's sequence number. big-endian
- Payload []byte // your data! :)
- Checksum []byte // checksum of { payload | header }
- }
- // Return the 2 bytes identifying a GSS API MIC token
- func getGSSMICTokenID() *[2]byte {
- return &[2]byte{0x04, 0x04}
- }
- // Return the filler bytes used in header
- func fillerBytes() *[5]byte {
- return &[5]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
- }
- // Marshal the MICToken into a byte slice.
- // The payload should have been set and the checksum computed, otherwise an error is returned.
- func (mt *MICToken) Marshal() ([]byte, error) {
- if mt.Checksum == nil {
- return nil, errors.New("checksum has not been set")
- }
- bytes := make([]byte, micHdrLen+len(mt.Checksum))
- copy(bytes[0:micHdrLen], mt.getMICChecksumHeader()[:])
- copy(bytes[micHdrLen:], mt.Checksum)
- return bytes, nil
- }
- // SetChecksum uses the passed encryption key and key usage to compute the checksum over the payload and
- // the header, and sets the Checksum field of this MICToken.
- // If the payload has not been set or the checksum has already been set, an error is returned.
- func (mt *MICToken) SetChecksum(key types.EncryptionKey, keyUsage uint32) error {
- if mt.Checksum != nil {
- return errors.New("checksum has already been computed")
- }
- checksum, err := mt.checksum(key, keyUsage)
- if err != nil {
- return err
- }
- mt.Checksum = checksum
- return nil
- }
- // Compute and return the checksum of this token, computed using the passed key and key usage.
- // Note: This will NOT update the struct's Checksum field.
- func (mt *MICToken) checksum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) {
- if mt.Payload == nil {
- return nil, errors.New("cannot compute checksum with uninitialized payload")
- }
- d := make([]byte, micHdrLen+len(mt.Payload))
- copy(d[0:], mt.Payload)
- copy(d[len(mt.Payload):], mt.getMICChecksumHeader())
- encType, err := crypto.GetEtype(key.KeyType)
- if err != nil {
- return nil, err
- }
- return encType.GetChecksumHash(key.KeyValue, d, keyUsage)
- }
- // Build a header suitable for a checksum computation
- func (mt *MICToken) getMICChecksumHeader() []byte {
- header := make([]byte, micHdrLen)
- copy(header[0:2], getGSSMICTokenID()[:])
- header[2] = mt.Flags
- copy(header[3:8], fillerBytes()[:])
- binary.BigEndian.PutUint64(header[8:16], mt.SndSeqNum)
- return header
- }
- // Verify computes the token's checksum with the provided key and usage,
- // and compares it to the checksum present in the token.
- // In case of any failure, (false, err) is returned, with err an explanatory error.
- func (mt *MICToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) {
- computed, err := mt.checksum(key, keyUsage)
- if err != nil {
- return false, err
- }
- if !hmac.Equal(computed, mt.Checksum) {
- return false, fmt.Errorf(
- "checksum mismatch. Computed: %s, Contained in token: %s",
- hex.EncodeToString(computed), hex.EncodeToString(mt.Checksum))
- }
- return true, nil
- }
- // Unmarshal bytes into the corresponding MICToken.
- // If expectFromAcceptor is true we expect the token to have been emitted by the gss acceptor,
- // and will check the according flag, returning an error if the token does not match the expectation.
- func (mt *MICToken) Unmarshal(b []byte, expectFromAcceptor bool) error {
- if len(b) < micHdrLen {
- return errors.New("bytes shorter than header length")
- }
- if !bytes.Equal(getGSSMICTokenID()[:], b[0:2]) {
- return fmt.Errorf("wrong Token ID, Expected %s, was %s",
- hex.EncodeToString(getGSSMICTokenID()[:]),
- hex.EncodeToString(b[0:2]))
- }
- flags := b[2]
- isFromAcceptor := flags&MICTokenFlagSentByAcceptor != 0
- if isFromAcceptor && !expectFromAcceptor {
- return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor")
- }
- if !isFromAcceptor && expectFromAcceptor {
- return errors.New("unexpected acceptor flag is not set: expecting a token from the acceptor, not in the initiator")
- }
- if !bytes.Equal(b[3:8], fillerBytes()[:]) {
- return fmt.Errorf("unexpected filler bytes: expecting %s, was %s",
- hex.EncodeToString(fillerBytes()[:]),
- hex.EncodeToString(b[3:8]))
- }
- mt.Flags = flags
- mt.SndSeqNum = binary.BigEndian.Uint64(b[8:16])
- mt.Checksum = b[micHdrLen:]
- return nil
- }
- // NewInitiatorMICToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum.
- // Other flags are set to 0.
- // Note that in certain circumstances you may need to provide a sequence number that has been defined earlier.
- // This is currently not supported.
- func NewInitiatorMICToken(payload []byte, key types.EncryptionKey) (*MICToken, error) {
- token := MICToken{
- Flags: 0x00,
- SndSeqNum: 0,
- Payload: payload,
- }
- if err := token.SetChecksum(key, keyusage.GSSAPI_INITIATOR_SIGN); err != nil {
- return nil, err
- }
- return &token, nil
- }
|