func (s *SecureChannel) sendMACCommand(c *commands.CommandMessage) (commands.Response, error) {
	c.SessionID = &s.ID
	sum, err := s.calculateMAC(c, MessageTypeCommand)
	if err != nil {
		return nil, err
	}
	s.MACChainValue = sum
	c.MAC = sum[:MACLength]
	return s.SendCommand(c)
}
sendMACCommand sends a MAC authenticated command to the HSM and returns a parsed response
package securechannel
import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/binary"
	"errors"
	"sync"
	"github.com/enceve/crypto/cmac"
	"github.com/certusone/yubihsm-go/authkey"
	"github.com/certusone/yubihsm-go/commands"
	"github.com/certusone/yubihsm-go/connector"
)
type (
	SecureChannel struct {
		connector connector.Connector
		authKeySlot uint16
		keyChain *KeyChain
		channelLock sync.Mutex
		ID uint8
		Counter uint32
		SecurityLevel SecurityLevel
		HostChallenge []byte
		DeviceChallenge []byte
		AuthKey authkey.AuthKey
		MACChainValue []byte
	}
	KeyDerivationConstant byte
	SecurityLevel byte
	KeyChain struct {
		EncKey  []byte
		MACKey  []byte
		RMACKey []byte
	}
	MessageType byte
)
const (
	MACLength        = 8
	ChallengeLength  = 8
	CryptogramLength = 8
	KeyLength        = 16
	DerivationConstantEncKey  KeyDerivationConstant = 0x04
	DerivationConstantMACKey  KeyDerivationConstant = 0x06
	DerivationConstantRMACKey KeyDerivationConstant = 0x07
	DerivationConstantDeviceCryptogram KeyDerivationConstant = 0x00
	DerivationConstantHostCryptogram   KeyDerivationConstant = 0x01
	SecurityLevelUnauthenticated SecurityLevel = 0
	SecurityLevelAuthenticated   SecurityLevel = 1
	MessageTypeCommand  MessageType = 0
	MessageTypeResponse MessageType = 1
	MaxMessagesPerSession = 10000
)
var ErrAuthCryptogram = errors.New("authentication failed: device sent wrong cryptogram")
func NewSecureChannel(connector connector.Connector, authKeySlot uint16, password string) (*SecureChannel, error) {
	channel := &SecureChannel{
		ID:            0,
		AuthKey:       authkey.NewFromPassword(password),
		MACChainValue: make([]byte, 16),
		SecurityLevel: SecurityLevelUnauthenticated,
		authKeySlot:   authKeySlot,
		connector:     connector,
	}
	hostChallenge := make([]byte, 8)
	_, err := rand.Read(hostChallenge)
	if err != nil {
		return nil, err
	}
	channel.HostChallenge = hostChallenge
	return channel, nil
}
func (s *SecureChannel) Authenticate() error {
	if s.SecurityLevel != SecurityLevelUnauthenticated {
		return errors.New("the session is already authenticated")
	}
	s.channelLock.Lock()
	defer s.channelLock.Unlock()
	command, _ := commands.CreateCreateSessionCommand(s.authKeySlot, s.HostChallenge)
	response, err := s.SendCommand(command)
	if err != nil {
		return err
	}
	createSessionResp, match := response.(*commands.CreateSessionResponse)
	if !match {
		return errors.New("invalid response type")
	}
	s.ID = createSessionResp.SessionID
	s.DeviceChallenge = createSessionResp.CardChallenge
	err = s.updateKeychain()
	if err != nil {
		return err
	}
	deviceCryptogram, err := s.deriveKDF(s.keyChain.MACKey, DerivationConstantDeviceCryptogram, CryptogramLength)
	if err != nil {
		return err
	}
	if !bytes.Equal(deviceCryptogram, createSessionResp.CardCryptogram) {
		return ErrAuthCryptogram
	}
	hostCryptogram, err := s.deriveKDF(s.keyChain.MACKey, DerivationConstantHostCryptogram, CryptogramLength)
	if err != nil {
		return err
	}
	authenticateCommand, err := commands.CreateAuthenticateSessionCommand(hostCryptogram)
	if err != nil {
		return err
	}
	_, err = s.sendMACCommand(authenticateCommand)
	if err != nil {
		return err
	}
	s.Counter = 1
	s.SecurityLevel = SecurityLevelAuthenticated
	return nil
}
func (s *SecureChannel) SendCommand(c *commands.CommandMessage) (commands.Response, error) {
	resp, err := s.connector.Request(c)
	if err != nil {
		return nil, err
	}
	return commands.ParseResponse(resp)
}
func (s *SecureChannel) SendEncryptedCommand(c *commands.CommandMessage) (commands.Response, error) {
	if s.SecurityLevel != SecurityLevelAuthenticated {
		return nil, errors.New("the session is not authenticated")
	}
	if s.Counter >= MaxMessagesPerSession {
		return nil, errors.New("channel has reached its message limit; please recreate")
	}
	s.channelLock.Lock()
	defer s.channelLock.Unlock()
	block, err := aes.NewCipher(s.keyChain.EncKey)
	if err != nil {
		return nil, err
	}
	icv := new(bytes.Buffer)
	icv.Write(bytes.Repeat([]byte{0}, 12))
	binary.Write(icv, binary.BigEndian, s.Counter)
	iv := make([]byte, KeyLength)
	block.Encrypt(iv, icv.Bytes())
	encrypter := cipher.NewCBCEncrypter(block, iv)
	commandData, _ := c.Serialize()
	encryptedCommand := make([]byte, len(pad(commandData)))
	encrypter.CryptBlocks(encryptedCommand, pad(commandData))
	resp, err := s.sendMACCommand(&commands.CommandMessage{
		CommandType: commands.CommandTypeSessionMessage,
		Data:        encryptedCommand,
	})
	if err != nil {
		return nil, err
	}
	sessionMessage, match := resp.(*commands.SessionMessageResponse)
	if !match {
		return nil, errors.New("invalid response type")
	}
	expectedMac, err := s.calculateMAC(&commands.CommandMessage{
		CommandType: commands.CommandTypeSessionMessage + commands.ResponseCommandOffset,
		SessionID:   &sessionMessage.SessionID,
		Data:        sessionMessage.EncryptedData,
	}, MessageTypeResponse)
	if !bytes.Equal(expectedMac[:MACLength], sessionMessage.MAC) {
		return nil, errors.New("invalid response MAC")
	}
	s.Counter++
	decrypter := cipher.NewCBCDecrypter(block, iv)
	decryptedResponse := make([]byte, len(sessionMessage.EncryptedData))
	decrypter.CryptBlocks(decryptedResponse, sessionMessage.EncryptedData)
	return commands.ParseResponse(unpad(decryptedResponse))
}
func (s *SecureChannel) Close() error {
	command, err := commands.CreateCloseSessionCommand()
	if err != nil {
		return err
	}
	_, err = s.SendEncryptedCommand(command)
	if err != nil {
		return err
	}
	return nil
}