mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2024-04-21 12:32:08 +00:00
384 lines
8.8 KiB
Go
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(),
|
|
)
|
|
}
|