1 Star 0 Fork 0

叶海丰/ipmi-go

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
types_session.go 19.84 KB
一键复制 编辑 原始数据 按行查看 历史
叶海丰 提交于 1年前 . init
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
package ipmi
import (
"crypto/md5"
"encoding/binary"
"fmt"
)
type (
AuthType uint8
)
const (
AuthTypeNone AuthType = 0x00
AuthTypeMD2 AuthType = 0x01
AuthTypeMD5 AuthType = 0x02
AuthTypePassword AuthType = 0x04
AuthTypeOEM AuthType = 0x05
AuthTypeRMCPPlus AuthType = 0x06
)
const (
SessionHeader20SizeMax int = 18
SessionHeader20SizeMin int = 12
SessionHeader15SizeMax int = 26
SessionHeader15SizeMin int = 10
)
// SessionHeader15 for IPMI 1.5
// see 22.12, Table 13.
//
// Whether the session header fields are present in a packet is based on
// whether the channel is specified as supporting multiple sessions or not.
// In addition, which session fields are present is based on the authentication type.
// Single-session connections and session-less channels do not include session header fields.
//
// Session header fields are present on all packets where the channel and
// connection mode is specified as supporting multiple sessions, even if
// the particular implementation only supports one session.
//
// Note that the command tables do not show the session header fields except for the
// Get Channel Authentication Capabilities, Get Session Challenge, and Activate Session commands.
// However, they are still required for all commands on a multi-session connection.
type SessionHeader15 struct {
// For IPMI 1.5, it's value is 00h, 01h, 02h, 04h, 05h
AuthType AuthType
// For IPMI v2.0 RMCP+ there are separate sequence numbers tracked for authenticated and unauthenticated packets.
// 0000_0000h is used for packets that are sent outside of a session.
Sequence uint32
SessionID uint32
// The Authentication Code field in the session header may or may not be present based on the Authentication Type. The authentication code field is absent whenever the Authentication Type is NONE. Whether the authentication code field is present or not when the Authentication Type = OEM is dependent on the OEM identified in the Get Channel Authentication Capabilities command.
//
// 16 bytes, not present when Authentication Type set to none
AuthCode []byte // IPMI 1.5
// Payload length in bytes. 1-based.
// IPMI 1.5 should be uint8
// You should construct SessionHeader after the payload is created, thus you can fill the length here.
PayloadLength uint8
}
func (h *SessionHeader15) Pack() []byte {
var msg = make([]byte, 10+len(h.AuthCode))
packUint8(uint8(h.AuthType), msg, 0)
packUint32L(h.Sequence, msg, 1)
packUint32L(h.SessionID, msg, 5)
if h.AuthType != AuthTypeNone {
packBytes(h.AuthCode, msg, 9)
}
packUint8(h.PayloadLength, msg, 9+len(h.AuthCode))
return msg
}
func (h *SessionHeader15) Unpack(msg []byte) error {
if len(msg) < SessionHeader15SizeMin {
return ErrUnpackedDataTooShortWith(len(msg), SessionHeader15SizeMin)
}
b, _, _ := unpackUint8(msg, 0)
h.AuthType = AuthType(b)
h.Sequence, _, _ = unpackUint32L(msg, 1)
h.SessionID, _, _ = unpackUint32L(msg, 5)
var payloadLengthIndex = 9
if h.AuthType != AuthTypeNone {
if len(msg) < SessionHeader15SizeMax {
return ErrUnpackedDataTooShortWith(len(msg), SessionHeader15SizeMax)
}
h.AuthCode, _, _ = unpackBytes(msg, 9, 16)
payloadLengthIndex = 25
}
h.PayloadLength, _, _ = unpackUint8(msg, payloadLengthIndex)
return nil
}
type Session15 struct {
SessionHeader15 *SessionHeader15
Payload []byte
// legacy PAD not needed for IPMI v2.0
LegacyPAD byte
}
func (s *Session15) Pack() []byte {
out := s.SessionHeader15.Pack()
out = append(out, s.Payload...)
return out
}
func (s *Session15) Unpack(msg []byte) error {
sessionHeader := &SessionHeader15{}
err := sessionHeader.Unpack(msg)
if err != nil {
return fmt.Errorf("unpack SessionHeader15 failed, err: %s", err)
}
s.SessionHeader15 = sessionHeader
sessionHeaderSize := len(sessionHeader.Pack())
sessionPayloadSize := int(sessionHeader.PayloadLength)
if len(msg) < sessionHeaderSize+sessionPayloadSize {
return ErrUnpackedDataTooShortWith(len(msg), sessionHeaderSize+sessionPayloadSize)
}
s.Payload, _, _ = unpackBytes(msg, sessionHeaderSize, sessionPayloadSize)
return nil
}
// SessionHeader20 for IPMI 2.0
type SessionHeader20 struct {
// For IPMI 2.0, it's value is always 06h
AuthType AuthType
PayloadEncrypted bool
PayloadAuthenticated bool
PayloadType PayloadType
// The complete identification of an OEM Payload is given by the combination of a three-byte IANA ID for the OEM, a reserved byte, plus a twobyte OEM Payload ID that is assigned and defined by the given OEM
OEMIANA uint32
OEMPayloadID uint16
// Should be set to bmcSessionID (generated by bmc, cached by remote console)
SessionID uint32
// For IPMI v2.0 RMCP+ there are separate sequence numbers tracked for authenticated and unauthenticated packets.
// 0000_0000h is used for packets that are sent outside of a session.
Sequence uint32
// Payload length in bytes. 1-based.
// You should construct SessionHeader after the payload is created, thus you can fill the length here.
// IPMI 2.0 should be uint16
PayloadLength uint16
}
func (h *SessionHeader20) Pack() []byte {
// the longest length of SessionHeader20 is 18
msg := make([]byte, 18)
packUint8(uint8(h.AuthType), msg, 0)
var encryptedMask uint8 = 0x00
if h.PayloadEncrypted {
encryptedMask = 0x80
}
var authenticatedMask uint8 = 0x00
if h.PayloadAuthenticated {
authenticatedMask = 0x40
}
packUint8(encryptedMask|authenticatedMask|uint8(h.PayloadType), msg, 1)
var sessionIDIndex int
var msgEndIndex int
if h.PayloadType == PayloadTypeOEM {
packUint32L(h.OEMIANA, msg, 2)
packUint16L(h.OEMPayloadID, msg, 6)
sessionIDIndex = 8
msgEndIndex = 18
} else {
sessionIDIndex = 2
msgEndIndex = 12
}
packUint32L(h.SessionID, msg, sessionIDIndex)
packUint32L(h.Sequence, msg, sessionIDIndex+4)
packUint16L(h.PayloadLength, msg, sessionIDIndex+8)
return msg[:msgEndIndex]
}
func (h *SessionHeader20) Unpack(msg []byte) error {
if len(msg) < SessionHeader20SizeMin {
return ErrUnpackedDataTooShortWith(len(msg), SessionHeader20SizeMin)
}
authType, _, _ := unpackUint8(msg, 0)
h.AuthType = AuthType(authType)
payloadType, _, _ := unpackUint8(msg, 1)
h.PayloadEncrypted = isBit7Set(payloadType)
h.PayloadAuthenticated = isBit6Set(payloadType)
h.PayloadType = PayloadType(payloadType & 0x3f)
var sessionIDIndex int
if h.PayloadType == PayloadTypeOEM {
if len(msg) < SessionHeader20SizeMax {
return ErrUnpackedDataTooShortWith(len(msg), SessionHeader20SizeMax)
}
h.OEMIANA, _, _ = unpackUint32L(msg, 2)
h.OEMPayloadID, _, _ = unpackUint16L(msg, 6)
sessionIDIndex = 8
} else {
sessionIDIndex = 2
}
h.SessionID, _, _ = unpackUint32L(msg, sessionIDIndex)
h.Sequence, _, _ = unpackUint32L(msg, sessionIDIndex+4)
h.PayloadLength, _, _ = unpackUint16L(msg, sessionIDIndex+8)
return nil
}
type Session20 struct {
SessionHeader20 *SessionHeader20
// for encrypted packets, it should contain Confidentiality Header, Encrypted Payload, and Confidentiality Trailer.
SessionPayload []byte
// For IPMI v2.0 RMCP+ packets, the IPMI Session Trailer is absent whenever the Session ID is 0000_0000h, or whenever bit 6 in the payload type field indicates the packet is unauthenticated.
SessionTrailer *SessionTrailer
}
func (s *Session20) Pack() []byte {
out := s.SessionHeader20.Pack()
out = append(out, s.SessionPayload...)
if s.SessionTrailer != nil {
out = append(out, s.SessionTrailer.Pack()...)
}
return out
}
func (s *Session20) Unpack(msg []byte) error {
sessionHeader := &SessionHeader20{}
if err := sessionHeader.Unpack(msg); err != nil {
return fmt.Errorf("unpack SessionHeader failed, err: %s", err)
}
s.SessionHeader20 = sessionHeader
var sessionHeaderSize int
if sessionHeader.PayloadType == PayloadTypeOEM {
sessionHeaderSize = SessionHeader20SizeMax
} else {
sessionHeaderSize = SessionHeader20SizeMin
}
payloadLength := int(s.SessionHeader20.PayloadLength)
if len(msg) < sessionHeaderSize+payloadLength {
return ErrUnpackedDataTooShortWith(len(msg), sessionHeaderSize+payloadLength)
}
s.SessionPayload, _, _ = unpackBytes(msg, sessionHeaderSize, payloadLength)
s.SessionTrailer = nil
sessionTrailerIndex := sessionHeaderSize + payloadLength
if s.SessionHeader20.PayloadAuthenticated && s.SessionHeader20.SessionID != 0 {
padSize := genSessionTrailerPadLength(sessionHeader.Pack(), s.SessionPayload)
sessionTrailer := &SessionTrailer{}
_, err := sessionTrailer.Unpack(msg, sessionTrailerIndex, padSize)
if err != nil {
return fmt.Errorf("unpack SessionTrailer failed, err: %s", err)
}
s.SessionTrailer = sessionTrailer
}
return nil
}
// For IPMI v2.0 RMCP+ packets, the IPMI Session Trailer is absent whenever the Session ID is 0000_0000h, or whenever bit 6 in the payload type field indicates the packet is unauthenticated
type SessionTrailer struct {
// IPMI 2.0 and ASF only
// Added as needed to cause the number of bytes in the data range covered by the AuthCode (Integrity Data) field to be a multiple of 4 bytes (DWORD). If present, each Integrity Pad byte is set to FFh.
IntegrityPAD []byte
// indicates how many pad bytes were added so that the amount of non-pad data can be determined.
PadLength uint8
// Reserved in IPMI v2.0. Set to 07h for RMCP+ packets defined in this specification.
NextHeader uint8
// For IPMI v2.0 (RMCP+) if this field is present, then it is calculated according to the Integrity Algorithm that was negotiated during the session open process. See Table 13-, Integrity Algorithm Numbers.
// This field is absent when the packet is unauthenticated.
AuthCode []byte // Integrity Data
}
func (s *SessionTrailer) Pack() []byte {
msg := make([]byte, len(s.IntegrityPAD)+2+len(s.AuthCode))
packBytes(s.IntegrityPAD, msg, 0)
packUint8(s.PadLength, msg, len(s.IntegrityPAD))
packUint8(s.NextHeader, msg, len(s.IntegrityPAD)+1)
packBytes(s.AuthCode, msg, len(s.IntegrityPAD)+2)
return msg
}
func (s *SessionTrailer) Unpack(msg []byte, off int, padSize int) (int, error) {
var err error
s.IntegrityPAD, off, _ = unpackBytes(msg, off, padSize)
s.PadLength, off, _ = unpackUint8(msg, off)
s.NextHeader, off, _ = unpackUint8(msg, off)
s.AuthCode, off, _ = unpackBytesMost(msg, off, 16)
return off, err
}
type SessionState uint8
const (
SessionStatePreSession SessionState = 0x00
SessionStateOpenSessionSent SessionState = 0x01
SessionStateOpenSessionReceived SessionState = 0x02
SessionStateRakp1Sent SessionState = 0x03
SessionStateRakp2Received SessionState = 0x04
SessionStateRakp3Sent SessionState = 0x05
SessionStateActive SessionState = 0x06
SessionStateCloseSent SessionState = 0x07
)
func (c *Client) genSession15(rawPayload []byte) (*Session15, error) {
c.lock()
defer c.unlock()
sessionHeader := &SessionHeader15{
AuthType: AuthTypeNone,
Sequence: 0,
SessionID: 0,
AuthCode: nil, // AuthCode would be filled afterward
PayloadLength: uint8(len(rawPayload)),
}
if c.session.v15.preSession || c.session.v15.active {
sessionHeader.AuthType = c.session.authType
sessionHeader.SessionID = c.session.v15.sessionID
}
if c.session.v15.active {
c.session.v15.inSeq += 1
sessionHeader.Sequence = c.session.v15.inSeq
}
if sessionHeader.AuthType != AuthTypeNone {
authCode := c.genAuthCodeForMultiSession(rawPayload)
sessionHeader.AuthCode = authCode
}
return &Session15{
SessionHeader15: sessionHeader,
Payload: rawPayload,
}, nil
}
func (c *Client) genSession20(payloadType PayloadType, rawPayload []byte) (*Session20, error) {
c.lock()
defer c.unlock()
//
// Session Header
//
sessionHeader := &SessionHeader20{
AuthType: AuthTypeRMCPPlus, // Auth Type / Format is always 0x06 for IPMI v2
PayloadType: payloadType,
PayloadAuthenticated: false,
PayloadEncrypted: false,
SessionID: 0,
Sequence: 0,
PayloadLength: 0, // PayloadLength would be updated later after encryption if necessary.
}
if c.session.v20.state == SessionStateActive {
sessionHeader.PayloadAuthenticated = true
sessionHeader.PayloadEncrypted = true
sessionHeader.SessionID = c.session.v20.bmcSessionID // use bmc session id
c.session.v20.sequence += 1
sessionHeader.Sequence = c.session.v20.sequence
}
//
// Session Payload
//
sessionPayload := rawPayload
if c.session.v20.state == SessionStateActive && sessionHeader.PayloadEncrypted {
e, err := c.encryptPayload(rawPayload, nil)
if err != nil {
return nil, fmt.Errorf("encrypt payload failed, err: %s", err)
}
sessionPayload = e
}
// now we can fill PayloadLength field of the SessionHeader
sessionHeader.PayloadLength = uint16(len(sessionPayload))
c.DebugBytes("sessionPayload(final)", sessionPayload, 16)
sessionHeaderBytes := sessionHeader.Pack()
c.DebugBytes("sessionHeader", sessionHeaderBytes, 16)
//
// Session Trailer
//
var sessionTrailer *SessionTrailer = nil
var err error
// For IPMI v2.0 RMCP+ packets, the IPMI Session Trailer is absent
// whenever the Session ID is 0000_0000h, or the packet is unauthenticated
if sessionHeader.PayloadAuthenticated && sessionHeader.SessionID != 0 {
sessionTrailer, err = c.genSessionTrailer(sessionHeaderBytes, sessionPayload)
if err != nil {
return nil, fmt.Errorf("genSessionTrailer failed, err: %s", err)
}
}
return &Session20{
SessionHeader20: sessionHeader,
SessionPayload: sessionPayload,
SessionTrailer: sessionTrailer,
}, nil
}
func genSessionTrailerPadLength(sessionHeader []byte, sessionPayload []byte) int {
// (12) sessionHeader length
// sessionPayload length
// (1) pad length field
// (1) next header field
length := len(sessionHeader) + len(sessionPayload) + 1 + 1
var padSize int = 0
if length%4 != 0 {
padSize = 4 - int(length%4)
}
return padSize
}
// genSessionTrailer will create the SessionTrailer.
//
// see 13.28.4 Integrity Algorithms
// Unless otherwise specified, the integrity algorithm is applied to the packet
// data starting with the AuthType/Format field up to and including the field
// that immediately precedes the AuthCode field itself.
func (c *Client) genSessionTrailer(sessionHeader []byte, sessionPayload []byte) (*SessionTrailer, error) {
padSize := genSessionTrailerPadLength(sessionHeader, sessionPayload)
var pad = make([]byte, padSize)
for i := 0; i < padSize; i++ {
pad[i] = 0xff
}
sessionTrailer := &SessionTrailer{
IntegrityPAD: pad,
PadLength: uint8(padSize),
NextHeader: 0x07, /* Hardcoded per the spec, table 13-8 */
AuthCode: nil,
}
var input []byte = sessionHeader
input = append(input, sessionPayload...)
input = append(input, sessionTrailer.IntegrityPAD...)
input = append(input, sessionTrailer.PadLength)
input = append(input, sessionTrailer.NextHeader)
c.DebugBytes("auth code input", input, 16)
authCode, err := c.genIntegrityAuthCode(input)
if err != nil {
return nil, fmt.Errorf("generate integrity authcode failed, err: %s", err)
}
c.DebugBytes("generated auth code", authCode, 16)
sessionTrailer.AuthCode = authCode
return sessionTrailer, nil
}
// the input data only represents the serialized ipmi msg request bytes.
// the output bytes contains the
// - Confidentiality Header (clear text)
// - Encrypted Payload.
// - the cipher text of both rawPayload
// - padded Confidentiality Trailer.
func (c *Client) encryptPayload(rawPayload []byte, iv []byte) ([]byte, error) {
switch c.session.v20.cryptAlg {
case CryptAlg_None:
return rawPayload, nil
case CryptAlg_AES_CBC_128:
// The input to the AES encryption algorithm has to be a multiple of the block size (16 bytes).
// The extra byte we are adding is the pad length byte.
var paddedData = rawPayload
var padLength uint8
if mod := (len(rawPayload) + 1) % int(Encryption_AES_CBS_128_BlockSize); mod > 0 {
padLength = Encryption_AES_CBS_128_BlockSize - uint8(mod)
} else {
padLength = 0
}
for i := uint8(0); i < padLength; i++ {
paddedData = append(paddedData, i+1)
}
paddedData = append(paddedData, padLength) // now, the length of data SHOULD be multiple of 16
c.DebugBytes("padded data (before encrypt)", paddedData, 16)
// see 13.29 Table 13-, AES-CBC Encrypted Payload Fields
if len(iv) == 0 {
iv = randomBytes(16) // Initialization Vector
}
c.DebugBytes("random iv", iv, 16)
// see 13.29.2 Encryption with AES
// AES-128 uses a 128-bit Cipher Key. The Cipher Key is the first 128-bits of key K2
cipherKey := c.session.v20.k2[0:16]
c.DebugBytes("cipher key (k2)", cipherKey, 16)
encryptedPayload, err := encryptAES(paddedData, cipherKey, iv)
if err != nil {
return nil, fmt.Errorf("encrypt payload with AES_CBC_128 failed, err: %s", err)
}
c.DebugBytes("encrypted data", encryptedPayload, 16)
var out []byte
// write Confidentiality Header
out = append(out, iv...)
// write Encrypted Payload
out = append(out, encryptedPayload...)
c.DebugBytes("encrypted session payload", out, 16)
return out, nil
case CryptAlg_xRC4_40, CryptAlg_xRC4_128:
var out []byte
// see 13.30 Table 13-, xRC4-Encrypted Payload Fields
var confidentialityHeader []byte
var offset = make([]byte, 4)
if c.session.v20.accumulatedPayloadSize == 0 {
// means this is the first sent packet
for i := 0; i < 4; i++ {
offset[i] = 0
}
c.session.v20.rc4EncryptIV = array16(randomBytes(16))
confidentialityHeader = append(offset, c.session.v20.rc4EncryptIV[:]...)
} else {
binary.BigEndian.PutUint32(offset, c.session.v20.accumulatedPayloadSize)
confidentialityHeader = offset
}
c.session.v20.accumulatedPayloadSize += uint32(len(rawPayload))
iv := c.session.v20.rc4EncryptIV[:]
out = append(out, confidentialityHeader...)
input := append(c.session.v20.k2, iv...)
keyRC := md5.New().Sum(input)
var cipherKey []byte
switch c.session.v20.cryptAlg {
case CryptAlg_xRC4_40:
// For xRC4 using a 40-bit key, only the most significant forty bits of Krc are used
cipherKey = keyRC[:5]
case CryptAlg_xRC4_128:
// For xRC4 using a 128-bit key, all bits of Krc are used for initialization
cipherKey = keyRC[:16]
}
encryptedPayload, err := encryptRC4(rawPayload, cipherKey, iv)
if err != nil {
return nil, fmt.Errorf("encrypt payload with xRC4_40 or xRC4_128 failed, err: %s", err)
}
// write Encrypted Payload
out = append(out, encryptedPayload...)
// xRC4 does not use a confidentiality trailer.
return out, nil
default:
return nil, fmt.Errorf("not supported encryption algorithm %x", c.session.v20.cryptAlg)
}
}
// the input data is the encrypted session payload.
// the output bytes is the decrypted IPMI Message bytes with padding removed.
func (c *Client) decryptPayload(data []byte) ([]byte, error) {
switch c.session.v20.cryptAlg {
case CryptAlg_None:
return data, nil
case CryptAlg_AES_CBC_128:
iv := data[0:16] // the first 16 byte is the initialization vector
cipherText := data[16:]
cipherKey := c.session.v20.k2[0:16]
d, err := decryptAES(cipherText, cipherKey, iv)
if err != nil {
return nil, fmt.Errorf("decrypt payload with AES_CBC_128 failed, err: %s", err)
}
padLength := d[len(d)-1]
dEnd := len(d) - int(padLength) - 1
return d[0:dEnd], nil
case CryptAlg_xRC4_40, CryptAlg_xRC4_128:
// the first received packet
if data[0] == 0x0 && data[1] == 0x0 && data[2] == 0x0 && data[3] == 0x0 {
c.session.v20.rc4DecryptIV = array16(data[4:20])
}
iv := c.session.v20.rc4DecryptIV[:]
input := append(c.session.v20.k2, iv...)
keyRC := md5.New().Sum(input)
var cipherKey []byte
switch c.session.v20.cryptAlg {
case CryptAlg_xRC4_40:
// For xRC4 using a 40-bit key, only the most significant forty bits of Krc are used
cipherKey = keyRC[:5]
case CryptAlg_xRC4_128:
// For xRC4 using a 128-bit key, all bits of Krc are used for initialization
cipherKey = keyRC[:16]
}
payloadData := data[20:]
b, err := decryptRC4(payloadData, cipherKey, iv)
if err != nil {
return nil, fmt.Errorf("decrypt payload with xRC4_128 failed, err: %s", err)
}
return b, nil
default:
return nil, fmt.Errorf("not supported encryption algorithm %0x", c.session.v20.cryptAlg)
}
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/rogax/ipmi-go.git
git@gitee.com:rogax/ipmi-go.git
rogax
ipmi-go
ipmi-go
86ae46cfb58e

搜索帮助