Files

384 lines
8.8 KiB
Go

package smb2
import (
"bytes"
"context"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"github.com/hirochachacha/go-smb2/internal/crypto/ccm"
"github.com/hirochachacha/go-smb2/internal/crypto/cmac"
. "github.com/hirochachacha/go-smb2/internal/erref"
. "github.com/hirochachacha/go-smb2/internal/smb2"
)
func sessionSetup(conn *conn, i Initiator, ctx context.Context) (*session, error) {
spnego := newSpnegoClient([]Initiator{i})
outputToken, err := spnego.initSecContext()
if err != nil {
return nil, &InvalidResponseError{err.Error()}
}
req := &SessionSetupRequest{
Flags: 0,
Capabilities: conn.capabilities & (SMB2_GLOBAL_CAP_DFS),
Channel: 0,
SecurityBuffer: outputToken,
PreviousSessionId: 0,
}
if conn.requireSigning {
req.SecurityMode = SMB2_NEGOTIATE_SIGNING_REQUIRED
} else {
req.SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED
}
req.CreditCharge = 1
req.CreditRequestResponse = conn.account.initRequest()
rr, err := conn.send(req, ctx)
if err != nil {
return nil, err
}
pkt, err := conn.recv(rr)
if err != nil {
return nil, err
}
p := PacketCodec(pkt)
if NtStatus(p.Status()) != STATUS_MORE_PROCESSING_REQUIRED {
return nil, &InvalidResponseError{fmt.Sprintf("expected status: %v, got %v", STATUS_MORE_PROCESSING_REQUIRED, NtStatus(p.Status()))}
}
res, err := accept(SMB2_SESSION_SETUP, pkt)
if err != nil {
return nil, err
}
r := SessionSetupResponseDecoder(res)
if r.IsInvalid() {
return nil, &InvalidResponseError{"broken session setup response format"}
}
sessionFlags := r.SessionFlags()
if conn.requireSigning {
if sessionFlags&SMB2_SESSION_FLAG_IS_GUEST != 0 {
return nil, &InvalidResponseError{"guest account doesn't support signing"}
}
if sessionFlags&SMB2_SESSION_FLAG_IS_NULL != 0 {
return nil, &InvalidResponseError{"anonymous account doesn't support signing"}
}
}
s := &session{
conn: conn,
treeConnTables: make(map[uint32]*treeConn),
sessionFlags: sessionFlags,
sessionId: p.SessionId(),
}
switch conn.dialect {
case SMB311:
s.preauthIntegrityHashValue = conn.preauthIntegrityHashValue
switch conn.preauthIntegrityHashId {
case SHA512:
h := sha512.New()
h.Write(s.preauthIntegrityHashValue[:])
h.Write(rr.pkt)
h.Sum(s.preauthIntegrityHashValue[:0])
h.Reset()
h.Write(s.preauthIntegrityHashValue[:])
h.Write(pkt)
h.Sum(s.preauthIntegrityHashValue[:0])
}
}
outputToken, err = spnego.acceptSecContext(r.SecurityBuffer())
if err != nil {
return nil, &InvalidResponseError{err.Error()}
}
req.SecurityBuffer = outputToken
req.CreditRequestResponse = 0
// We set session before sending packet just for setting hdr.SessionId.
// But, we should not permit access from receiver until the session information is completed.
conn.session = s
rr, err = s.send(req, ctx)
if err != nil {
return nil, err
}
if s.sessionFlags&(SMB2_SESSION_FLAG_IS_GUEST|SMB2_SESSION_FLAG_IS_NULL) == 0 {
sessionKey := spnego.sessionKey()
switch conn.dialect {
case SMB202, SMB210:
s.signer = hmac.New(sha256.New, sessionKey)
s.verifier = hmac.New(sha256.New, sessionKey)
case SMB300, SMB302:
signingKey := kdf(sessionKey, []byte("SMB2AESCMAC\x00"), []byte("SmbSign\x00"))
ciph, err := aes.NewCipher(signingKey)
if err != nil {
return nil, &InternalError{err.Error()}
}
s.signer = cmac.New(ciph)
s.verifier = cmac.New(ciph)
// s.applicationKey = kdf(sessionKey, []byte("SMB2APP\x00"), []byte("SmbRpc\x00"))
encryptionKey := kdf(sessionKey, []byte("SMB2AESCCM\x00"), []byte("ServerIn \x00"))
decryptionKey := kdf(sessionKey, []byte("SMB2AESCCM\x00"), []byte("ServerOut\x00"))
ciph, err = aes.NewCipher(encryptionKey)
if err != nil {
return nil, &InternalError{err.Error()}
}
s.encrypter, err = ccm.NewCCMWithNonceAndTagSizes(ciph, 11, 16)
if err != nil {
return nil, &InternalError{err.Error()}
}
ciph, err = aes.NewCipher(decryptionKey)
if err != nil {
return nil, &InternalError{err.Error()}
}
s.decrypter, err = ccm.NewCCMWithNonceAndTagSizes(ciph, 11, 16)
if err != nil {
return nil, &InternalError{err.Error()}
}
case SMB311:
switch conn.preauthIntegrityHashId {
case SHA512:
h := sha512.New()
h.Write(s.preauthIntegrityHashValue[:])
h.Write(rr.pkt)
h.Sum(s.preauthIntegrityHashValue[:0])
}
signingKey := kdf(sessionKey, []byte("SMBSigningKey\x00"), s.preauthIntegrityHashValue[:])
ciph, err := aes.NewCipher(signingKey)
if err != nil {
return nil, &InternalError{err.Error()}
}
s.signer = cmac.New(ciph)
s.verifier = cmac.New(ciph)
// s.applicationKey = kdf(sessionKey, []byte("SMBAppKey\x00"), preauthIntegrityHashValue)
encryptionKey := kdf(sessionKey, []byte("SMBC2SCipherKey\x00"), s.preauthIntegrityHashValue[:])
decryptionKey := kdf(sessionKey, []byte("SMBS2CCipherKey\x00"), s.preauthIntegrityHashValue[:])
switch s.cipherId {
case AES128CCM:
ciph, err := aes.NewCipher(encryptionKey)
if err != nil {
return nil, &InternalError{err.Error()}
}
s.encrypter, err = ccm.NewCCMWithNonceAndTagSizes(ciph, 11, 16)
if err != nil {
return nil, &InternalError{err.Error()}
}
ciph, err = aes.NewCipher(decryptionKey)
if err != nil {
return nil, &InternalError{err.Error()}
}
s.decrypter, err = ccm.NewCCMWithNonceAndTagSizes(ciph, 11, 16)
if err != nil {
return nil, &InternalError{err.Error()}
}
case AES128GCM:
ciph, err := aes.NewCipher(encryptionKey)
if err != nil {
return nil, &InternalError{err.Error()}
}
s.encrypter, err = cipher.NewGCMWithNonceSize(ciph, 12)
if err != nil {
return nil, &InternalError{err.Error()}
}
ciph, err = aes.NewCipher(decryptionKey)
if err != nil {
return nil, &InternalError{err.Error()}
}
s.decrypter, err = cipher.NewGCMWithNonceSize(ciph, 12)
if err != nil {
return nil, &InternalError{err.Error()}
}
}
}
}
pkt, err = s.recv(rr)
if err != nil {
return nil, err
}
res, err = accept(SMB2_SESSION_SETUP, pkt)
if err != nil {
return nil, err
}
r = SessionSetupResponseDecoder(res)
if r.IsInvalid() {
return nil, &InvalidResponseError{"broken session setup response format"}
}
if NtStatus(PacketCodec(pkt).Status()) != STATUS_SUCCESS {
return nil, &InvalidResponseError{"broken session setup response format"}
}
s.sessionFlags = r.SessionFlags()
// now, allow access from receiver
s.enableSession()
return s, nil
}
type session struct {
*conn
treeConnTables map[uint32]*treeConn
sessionFlags uint16
sessionId uint64
preauthIntegrityHashValue [64]byte
signer hash.Hash
verifier hash.Hash
encrypter cipher.AEAD
decrypter cipher.AEAD
// applicationKey []byte
}
func (s *session) logoff(ctx context.Context) error {
req := new(LogoffRequest)
req.CreditCharge = 1
_, err := s.sendRecv(SMB2_LOGOFF, req, ctx)
if err != nil {
return err
}
s.conn.rdone <- struct{}{}
s.conn.t.Close()
return nil
}
func (s *session) sendRecv(cmd uint16, req Packet, ctx context.Context) (res []byte, err error) {
rr, err := s.send(req, ctx)
if err != nil {
return nil, err
}
pkt, err := s.recv(rr)
if err != nil {
return nil, err
}
return accept(cmd, pkt)
}
func (s *session) recv(rr *requestResponse) (pkt []byte, err error) {
pkt, err = s.conn.recv(rr)
if err != nil {
return nil, err
}
if sessionId := PacketCodec(pkt).SessionId(); sessionId != s.sessionId {
return nil, &InvalidResponseError{fmt.Sprintf("expected session id: %v, got %v", s.sessionId, sessionId)}
}
return pkt, err
}
func (s *session) sign(pkt []byte) []byte {
p := PacketCodec(pkt)
p.SetFlags(p.Flags() | SMB2_FLAGS_SIGNED)
h := s.signer
h.Reset()
h.Write(pkt)
p.SetSignature(h.Sum(nil))
return pkt
}
func (s *session) verify(pkt []byte) (ok bool) {
p := PacketCodec(pkt)
signature := append([]byte{}, p.Signature()...)
p.SetSignature(zero[:])
h := s.verifier
h.Reset()
h.Write(pkt)
p.SetSignature(h.Sum(nil))
return bytes.Equal(signature, p.Signature())
}
func (s *session) encrypt(pkt []byte) ([]byte, error) {
nonce := make([]byte, s.encrypter.NonceSize())
_, err := rand.Read(nonce)
if err != nil {
return nil, err
}
c := make([]byte, 52+len(pkt)+16)
t := TransformCodec(c)
t.SetProtocolId()
t.SetNonce(nonce)
t.SetOriginalMessageSize(uint32(len(pkt)))
t.SetFlags(Encrypted)
t.SetSessionId(s.sessionId)
s.encrypter.Seal(c[:52], nonce, pkt, t.AssociatedData())
t.SetSignature(c[len(c)-16:])
c = c[:len(c)-16]
return c, nil
}
func (s *session) decrypt(pkt []byte) ([]byte, error) {
t := TransformCodec(pkt)
c := append(t.EncryptedData(), t.Signature()...)
return s.decrypter.Open(
c[:0],
t.Nonce()[:s.decrypter.NonceSize()],
c,
t.AssociatedData(),
)
}