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
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 )
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 )
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 ()
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
}
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 (),
)
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .