1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
|
package ipmi
import (
"context"
"fmt"
)
const IPMI_MAX_USER_NAME_LENGTH = 16
const IPMI_RAKP1_MESSAGE_SIZE = 44
// 13.20 RAKP Message 1
type RAKPMessage1 struct {
MessageTag uint8
// The Managed System's Session ID for this session, returned by the Managed System on the
// previous RMCP+ Open Session Response message.
ManagedSystemSessionID uint32
// 16 bytes
RemoteConsoleRandomNumber [16]byte
// bit 4
// 0b = Username/Privilege lookup.
// 1b = Name-only lookup.
NameOnlyLookup bool
RequestedMaximumPrivilegeLevel PrivilegeLevel
UsernameLength uint8
Username []byte
}
type RAKPMessage2 struct {
// authAlg describes the authentication algorithm was agreed upon in
// the open session request/response phase.
// We need to know that here so that we know how many bytes (if any) to read from the packet for KeyExchangeAuthenticationCode
authAlg AuthAlg
MessageTag uint8
// RMCP+ Status Code - Identifies the status of the previous message.
//
// If the previous message generated an error, then only the Completion Code, Reserved, and
// Remote Console Session ID fields are returned.
//
// If the Remote Console Session ID field is indeterminate
// (as would be the case if the Managed System Session ID in RAKP Message 1 were invalid)
// then the Remote Console Session ID field will be set to all zeros.
//
// On error, the remote console can attempt to correct the error and send a new RAKP Message 1.
//
// Note that the remote console must change the Message Tag value to ensure the BMC sees the message as a new message and not as a retry.
//
// See Table 13-15, RMCP+ and RAKP Message Status Codes for the status codes defined for this message.
RmcpStatusCode RmcpStatusCode
// The Remote Console Session ID specified by the RMCP+ Open Session Request message associated with this response.
RemoteConsoleSessionID uint32
// Random number generated/selected by the managed system.
ManagedSystemRandomNumber [16]byte
// The Globally Unique ID (GUID) of the Managed System.
// This value is typically specified by the client system's SMBIOS implementation. See
// 22.14, Get System GUID Command, for additional information
ManagedSystemGUID [16]byte
// An integrity check value over the relevant items specified by the RAKP algorithm for RAKP Message 2.
// The size of this field depends on the specific Authentication Algorithm
// This field may be 0-bytes (absent) for some algorithms (e.g. RAKP-none).
//
// see 13.31 for how the managed system generate this HMAC
KeyExchangeAuthenticationCode []byte
}
func (req *RAKPMessage1) Command() Command {
return CommandNone
}
func (r *RAKPMessage1) Pack() []byte {
var msg = make([]byte, 28+len(r.Username))
packUint8(r.MessageTag, msg, 0)
packUint24L(0, msg, 1) // 3 bytes reserved
packUint32L(r.ManagedSystemSessionID, msg, 4)
packBytes((r.RemoteConsoleRandomNumber[:]), msg, 8)
packUint8(r.Role(), msg, 24)
packUint16L(0, msg, 25) // 2 bytes reserved
packUint8(r.UsernameLength, msg, 27)
packBytes(r.Username, msg, 28)
return msg
}
// the combination of RequestedMaximumPrivilegeLevel and NameOnlyLookup field
// The whole byte should be stored to client session for computing auth code of rakp2
func (r *RAKPMessage1) Role() uint8 {
privilegeLevel := uint8(r.RequestedMaximumPrivilegeLevel)
if r.NameOnlyLookup {
privilegeLevel = setBit4(privilegeLevel)
}
return privilegeLevel
}
func (res *RAKPMessage2) Unpack(msg []byte) error {
// If RAKPMessage1 failed to be validated, the returned RAKPMessage2 only holds 8 bytes.
if len(msg) < 8 {
return ErrUnpackedDataTooShortWith(len(msg), 8)
}
res.MessageTag = msg[0]
res.RmcpStatusCode = RmcpStatusCode(msg[1])
// 2 bytes reserved
res.RemoteConsoleSessionID, _, _ = unpackUint32L(msg, 4)
// Now we can check whether RmcpStatusCode indicates error
if res.RmcpStatusCode != RmcpStatusCodeNoErrors {
return fmt.Errorf("the return status of rakp2 has error: %v", res.RmcpStatusCode)
}
if len(msg) < 40 {
return ErrUnpackedDataTooShortWith(len(msg), 40)
}
res.ManagedSystemRandomNumber = array16(msg[8:24])
res.ManagedSystemGUID = array16(msg[24:40])
var authCodeLen int = 0
switch res.authAlg {
case AuthAlgRAKP_None:
break
case AuthAlgRAKP_HMAC_MD5:
authCodeLen = 16
case AuthAlgRAKP_HMAC_SHA1:
authCodeLen = 20
case AuthAlgRAKP_HMAC_SHA256:
authCodeLen = 32
}
if len(msg) < 40+authCodeLen {
return fmt.Errorf("the unpacked data does not contain enough auth code")
}
res.KeyExchangeAuthenticationCode = make([]byte, authCodeLen)
copy(res.KeyExchangeAuthenticationCode, msg[40:40+authCodeLen])
return nil
}
func (*RAKPMessage2) CompletionCodes() map[uint8]string {
// no command-specific cc
return map[uint8]string{}
}
func (res *RAKPMessage2) Format() string {
return fmt.Sprintf("%v", res)
}
// ValidateRAKP2 validates RAKPMessage2 returned by BMC.
func (c *Client) ValidateRAKP2(ctx context.Context, rakp2 *RAKPMessage2) (bool, error) {
if c.session.v20.consoleSessionID != rakp2.RemoteConsoleSessionID {
return false, fmt.Errorf("session id not matched, cached console session id: %x, rakp2 returned session id: %x", c.session.v20.consoleSessionID, rakp2.RemoteConsoleSessionID)
}
// rakp2 authcode is valid
authcode, err := c.generate_rakp2_authcode()
if err != nil {
return false, fmt.Errorf("generate rakp2 authcode failed, err: %w", err)
}
c.DebugBytes("rakp2 returned auth code", rakp2.KeyExchangeAuthenticationCode, 16)
if !isByteSliceEqual(authcode, rakp2.KeyExchangeAuthenticationCode) {
return false, fmt.Errorf("rakp2 authcode not equal, console: %x, bmc: %x", authcode, rakp2.KeyExchangeAuthenticationCode)
}
return true, nil
}
func (c *Client) RAKPMessage1(ctx context.Context) (response *RAKPMessage2, err error) {
c.session.v20.consoleRand = array16(randomBytes(16))
c.DebugBytes("console generate console random number", c.session.v20.consoleRand[:], 16)
request := &RAKPMessage1{
MessageTag: 0,
ManagedSystemSessionID: c.session.v20.bmcSessionID, // set by previous RMCP+ Open Session Request
RemoteConsoleRandomNumber: c.session.v20.consoleRand,
RequestedMaximumPrivilegeLevel: c.maxPrivilegeLevel,
NameOnlyLookup: true,
UsernameLength: uint8(len(c.Username)),
Username: []byte(c.Username),
}
c.session.v20.role = request.Role()
response = &RAKPMessage2{
authAlg: c.session.v20.authAlg,
}
c.session.v20.state = SessionStateRakp1Sent
err = c.Exchange(ctx, request, response)
if err != nil {
return nil, err
}
// the following fields must be set before generate_sik/generate_k1/generate_k2
c.session.v20.rakp2ReturnCode = uint8(response.RmcpStatusCode)
c.session.v20.bmcGUID = response.ManagedSystemGUID
c.session.v20.bmcRand = response.ManagedSystemRandomNumber // will be used in rakp3 to generate authCode
if _, err = c.ValidateRAKP2(ctx, response); err != nil {
err = fmt.Errorf("validate rakp2 message failed, err: %w", err)
return
}
c.session.v20.state = SessionStateRakp2Received
return
}
|