From 62ca1cb9994d5dc4e9958fbc2e4edb5184502103 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 12 Mar 2023 10:33:54 -0400 Subject: [PATCH 1/3] support NTLM message encryption --- encryption.go | 413 ++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 11 +- go.sum | 65 ++++---- 3 files changed, 453 insertions(+), 36 deletions(-) create mode 100644 encryption.go diff --git a/encryption.go b/encryption.go new file mode 100644 index 0000000..d494f71 --- /dev/null +++ b/encryption.go @@ -0,0 +1,413 @@ +package winrm + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + + ntlmssp "github.com/CalypsoSys/bobntlmssp" + ntlmhttp "github.com/CalypsoSys/bobntlmssp/http" + "github.com/masterzen/winrm/soap" +) + +type Encryption struct { + ntlm *ClientNTLM + SIXTEN_KB int + MIME_BOUNDARY []byte + protocol string + protocolString []byte + httpClient *http.Client + ntlmClient *ntlmssp.Client + ntlmhttp *ntlmhttp.Client +} + +const ( + SixteenKB = 16384 + MimeBoundary = "--Encrypted Boundary" + defaultCipher = "RC4-HMAC-NTLM" + BoundaryLength = len(MimeBoundary) +) + +/* +Encrypted Message Types +When using Encryption, there are three options available + 1. Negotiate/SPNEGO + 2. Kerberos + 3. CredSSP + + protocol: The protocol string used for the particular auth protocol + + The auth protocol used, will determine the wrapping and unwrapping method plus + the protocol string to use. Currently only NTLM is supported + + based on the python code from https://pypi.org/project/pywinrm/ + + uses the most excellent NTLM library from https://github.com/bodgit/ntlmssp + the modified vesion only differs in not outputing raw http request and response +*/ +func NewEncryption(protocol string) (*Encryption, error) { + encryption := &Encryption{ + ntlm: &ClientNTLM{}, + SIXTEN_KB: SixteenKB, + MIME_BOUNDARY: []byte(MimeBoundary), + protocol: protocol, + } + + switch protocol { + case "ntlm": + encryption.protocolString = []byte("application/HTTP-SPNEGO-session-encrypted") + return encryption, nil + case "credssp": + encryption.protocolString = []byte("application/HTTP-CredSSP-session-encrypted") + case "kerberos": + encryption.protocolString = []byte("application/HTTP-SPNEGO-session-encrypted") + } + + return nil, fmt.Errorf("Encryption for protocol '%s' not supported in bobwinrm", protocol) +} + +func (e *Encryption) Transport(endpoint *Endpoint) error { + e.httpClient = &http.Client{} + return e.ntlm.Transport(endpoint) +} + +func (e *Encryption) Post(client *Client, message *soap.SoapMessage) (string, error) { + e.ntlmClient, _ = ntlmssp.NewClient(ntlmssp.SetUserInfo(client.username, client.password), ntlmssp.SetVersion(ntlmssp.DefaultVersion())) + e.ntlmhttp, _ = ntlmhttp.NewClient(e.httpClient, e.ntlmClient) + e.ntlmhttp.Debug = false + + if e.PrepareRequest(client, client.url) == nil { + return e.PrepareEncryptedRequest(client, client.url, []byte(message.String())) + } else { + return e.ntlm.Post(client, message) + } +} + +func (e *Encryption) PrepareRequest(client *Client, endpoint string) error { + req, err := http.NewRequest("POST", endpoint, nil) + if err != nil { + return err + } + + req.Header.Set("User-Agent", "Bob WinRM client") + req.Header.Set("Content-Length", "0") + req.Header.Set("Content-Type", "application/soap+xml;charset=UTF-8") + req.Header.Set("Connection", "Keep-Alive") + + resp, err := e.ntlmhttp.Do(req) + if err != nil { + return fmt.Errorf("unknown error %w", err) + } + + if resp.StatusCode != 200 { + return fmt.Errorf("http error %d", resp.StatusCode) + } + + return nil +} + +/* +Creates a prepared request to send to the server with an encrypted message +and correct headers + +:param endpoint: The endpoint/server to prepare requests to +:param message: The unencrypted message to send to the server +:return: A prepared request that has an decrypted message +*/ +func (e *Encryption) PrepareEncryptedRequest(client *Client, endpoint string, message []byte) (string, error) { + url, err := url.Parse(endpoint) + if err != nil { + return "", err + } + host := strings.Split(url.Hostname(), ":")[0] + + var content_type string + var encrypted_message []byte + + if e.protocol == "credssp" && len(message) > e.SIXTEN_KB { + content_type = "multipart/x-multi-encrypted" + encrypted_message = []byte{} + message_chunks := [][]byte{} + for i := 0; i < len(message); i += e.SIXTEN_KB { + message_chunks = append(message_chunks, message[i:i+e.SIXTEN_KB]) + } + for _, message_chunk := range message_chunks { + encrypted_chunk := e.encryptMessage(message_chunk, host) + encrypted_message = append(encrypted_message, encrypted_chunk...) + } + } else { + content_type = "multipart/encrypted" + encrypted_message = e.encryptMessage(message, host) + } + + encrypted_message = append(encrypted_message, e.MIME_BOUNDARY...) + encrypted_message = append(encrypted_message, []byte("--\r\n")...) + + req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(encrypted_message)) + if err != nil { + return "", err + } + + req.Header.Set("User-Agent", "Bob WinRM client") + req.Header.Set("Connection", "Keep-Alive") + req.Header.Set("Content-Length", fmt.Sprintf("%d", len(encrypted_message))) + req.Header.Set("Content-Type", content_type+";protocol=\""+string(e.protocolString)+"\";boundary=\"Encrypted Boundary\"") + + resp, err := e.ntlmhttp.Do(req) + if err != nil { + return "", fmt.Errorf("unknown error %w", err) + } + + body, err := e.ParseEncryptedResponse(resp) + + return string(body), err +} + +/* +Takes in the encrypted response from the server and decrypts it + +:param response: The response that needs to be decrytped +:return: The unencrypted message from the server +*/ +func (e *Encryption) ParseEncryptedResponse(response *http.Response) ([]byte, error) { + contentType := response.Header.Get("Content-Type") + if strings.Contains(contentType, fmt.Sprintf(`protocol="%s"`, e.protocolString)) { + return e.decryptResponse(response, response.Request.URL.Hostname()) + } + body, err := ioutil.ReadAll(response.Body) + response.Body.Close() + if err != nil { + return nil, err + } + return body, nil +} + +func (e *Encryption) encryptMessage(message []byte, host string) []byte { + messageLength := []byte(fmt.Sprintf("%d", len(message))) + encryptedStream, _ := e.buildMessage(message, host) + + messagePayload := bytes.Join([][]byte{ + e.MIME_BOUNDARY, + []byte("\r\n"), + []byte("\tContent-Type: " + string(e.protocolString) + "\r\n"), + []byte("\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=" + string(messageLength) + "\r\n"), + e.MIME_BOUNDARY, + []byte("\r\n"), + []byte("\tContent-Type: application/octet-stream\r\n"), + encryptedStream, + }, []byte{}) + + return messagePayload +} + +func deleteEmpty(b [][]byte) [][]byte { + var r [][]byte + for _, by := range b { + if len(by) != 0 { + r = append(r, by) + } + } + return r +} + +func (e *Encryption) decryptResponse(response *http.Response, host string) ([]byte, error) { + body, _ := ioutil.ReadAll(response.Body) + parts := deleteEmpty(bytes.Split(body, []byte(MimeBoundary+"\r\n"))) + var message []byte + + for i := 0; i < len(parts); i += 2 { + header := parts[i] + payload := parts[i+1] + + expectedLengthStr := bytes.SplitAfter(header, []byte("Length="))[1] + expectedLength, _ := strconv.Atoi(string(bytes.TrimSpace(expectedLengthStr))) + + // remove the end MIME block if it exists + if bytes.HasSuffix(payload, []byte(MimeBoundary+"--\r\n")) { + payload = payload[:len(payload)-BoundaryLength-4] + } + encryptedData := bytes.ReplaceAll(payload, []byte("\tContent-Type: application/octet-stream\r\n"), []byte{}) + decryptedMessage, err := e.decryptMessage(encryptedData, host) + if err != nil { + return nil, err + } + + actualLength := int(len(decryptedMessage)) + if actualLength != expectedLength { + return nil, errors.New("encrypted length from server does not match the expected size, message has been tampered with") + } + + message = append(message, decryptedMessage...) + } + + return message, nil +} + +func (e *Encryption) decryptMessage(encryptedData []byte, host string) ([]byte, error) { + switch e.protocol { + case "ntlm": + return e.decryptNtlmMessage(encryptedData, host) + case "credssp": + return e.decryptCredsspMessage(encryptedData, host) + case "kerberos": + return e.decryptKerberosMessage(encryptedData, host) + default: + return nil, errors.New("Encryption for protocol " + e.protocol + " not supported in pywinrm") + } +} + +func (e *Encryption) decryptNtlmMessage(encryptedData []byte, host string) ([]byte, error) { + signatureLength := int(binary.LittleEndian.Uint32(encryptedData[:4])) + signature := encryptedData[4 : signatureLength+4] + encryptedMessage := encryptedData[signatureLength+4:] + + message, err := e.ntlmClient.SecuritySession().Unwrap(encryptedMessage, signature) + if err != nil { + return nil, err + } + return message, nil +} + +func (e *Encryption) decryptCredsspMessage(encryptedData []byte, host string) ([]byte, error) { + /* + TODO + encryptedMessage := encryptedData[4:] + + credsspContext, ok := e.session.Auth.Contexts()[host] + if !ok { + return nil, fmt.Errorf("credssp context not found for host: %s", host) + } + + message, err := credsspContext.Unwrap(encryptedMessage) + if err != nil { + return nil, err + } + return message, nil + */ + return nil, nil +} + +func (enc *Encryption) decryptKerberosMessage(encryptedData []byte, host string) ([]byte, error) { + /* + TODO + signatureLength := binary.LittleEndian.Uint32(encryptedData[0:4]) + signature := encryptedData[4 : 4+signatureLength] + encryptedMessage := encryptedData[4+signatureLength:] + + message, err := enc.session.Auth.UnwrapWinrm(host, encryptedMessage, signature) + if err != nil { + return nil, err + } + + return message, nil + */ + return nil, nil +} + +func (e *Encryption) buildMessage(encryptedData []byte, host string) ([]byte, error) { + switch e.protocol { + case "ntlm": + return e.buildNTLMMessage(encryptedData, host) + case "credssp": + return e.buildCredSSPMessage(encryptedData, host) + case "kerberos": + return e.buildKerberosMessage(encryptedData, host) + default: + return nil, errors.New("Encryption for protocol " + e.protocol + " not supported in pywinrm") + } +} + +func (enc *Encryption) buildNTLMMessage(message []byte, host string) ([]byte, error) { + if enc.ntlmClient.SecuritySession() == nil { + return nil, nil + } + sealedMessage, signature, err := enc.ntlmClient.SecuritySession().Wrap(message) + if err != nil { + return nil, err + } + + buf := new(bytes.Buffer) + if err = binary.Write(buf, binary.LittleEndian, uint32(len(signature))); err != nil { + return nil, err + } + + buf.Write(signature) + buf.Write(sealedMessage) + + return buf.Bytes(), nil +} + +func (e *Encryption) buildCredSSPMessage(message []byte, host string) ([]byte, error) { + /* + TODO + context := e.session.Auth.Contexts[host] + sealedMessage := context.Wrap(message) + + cipherNegotiated := context.TLSConnection.ConnectionState().CipherSuite.Name + trailerLength := e.getCredSSPTrailerLength(len(message), cipherNegotiated) + + trailer := make([]byte, 4) + binary.LittleEndian.PutUint32(trailer, uint32(trailerLength)) + + return append(trailer, sealedMessage...), nil + */ + return nil, nil +} + +func (e *Encryption) buildKerberosMessage(message []byte, host string) ([]byte, error) { + /* + sealedMessage, signature := e.session.Auth.WrapWinrm(host, message) + + signatureLength := make([]byte, 4) + binary.LittleEndian.PutUint32(signatureLength, uint32(len(signature))) + + return append(append(signatureLength, signature...), sealedMessage...), nil + */ + return nil, nil +} + +func (e *Encryption) getCredSSPTrailerLength(messageLength int, cipherSuite string) int { + var trailerLength int + + if match, _ := regexp.MatchString("^.*-GCM-[\\w\\d]*$", cipherSuite); match { + trailerLength = 16 + } else { + hashAlgorithm := cipherSuite[strings.LastIndex(cipherSuite, "-")+1:] + var hashLength int + + if hashAlgorithm == "MD5" { + hashLength = 16 + } else if hashAlgorithm == "SHA" { + hashLength = 20 + } else if hashAlgorithm == "SHA256" { + hashLength = 32 + } else if hashAlgorithm == "SHA384" { + hashLength = 48 + } else { + hashLength = 0 + } + + prePadLength := messageLength + hashLength + paddingLength := 0 + + if strings.Contains(cipherSuite, "RC4") { + paddingLength = 0 + } else if strings.Contains(cipherSuite, "DES") || strings.Contains(cipherSuite, "3DES") { + paddingLength = 8 - (prePadLength % 8) + + } else { + // AES is a 128 bit block cipher + paddingLength = 16 - (prePadLength % 16) + } + + trailerLength = (prePadLength + paddingLength) - messageLength + } + return trailerLength +} diff --git a/go.mod b/go.mod index 002a338..ee99d0e 100644 --- a/go.mod +++ b/go.mod @@ -4,20 +4,13 @@ go 1.14 require ( github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e + github.com/CalypsoSys/bobntlmssp v0.0.0-20230312131519-5d92ca710dc8 github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 github.com/gofrs/uuid v4.2.0+incompatible github.com/google/go-cmp v0.5.6 // indirect - github.com/hashicorp/go-uuid v1.0.2 // indirect - github.com/jcmturner/aescts/v2 v2.0.0 // indirect - github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect - github.com/jcmturner/gofork v1.0.0 // indirect - github.com/jcmturner/goidentity/v6 v6.0.1 // indirect github.com/jcmturner/gokrb5/v8 v8.4.2 - github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 - golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect - golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect - golang.org/x/text v0.3.7 + golang.org/x/text v0.6.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 ) diff --git a/go.sum b/go.sum index 897cf0b..5954f9d 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,14 @@ github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/CalypsoSys/bobntlmssp v0.0.0-20230312131519-5d92ca710dc8 h1:ilCicqekIaB0g/qxeF39HG6uTiI9eRhE9SOZEzbHNno= +github.com/CalypsoSys/bobntlmssp v0.0.0-20230312131519-5d92ca710dc8/go.mod h1:Vjp4JyzHu88bTXaIwegBl5+CCyIuodh38/4APRgTzgo= github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 h1:w0E0fgc1YafGEh5cROhlROMWXiNoZqApk2PDN0M1+Ns= github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= +github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= @@ -16,9 +17,10 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -38,51 +40,60 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL43sCxI2jhPLRv73oVVOjEKZjKkflyqxg= github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/myesui/uuid v1.0.0/go.mod h1:2CDfNgU0LR8mIdO8vdWd8i9gWWxLlcoIGGpSNgafq84= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/revel/config v1.0.0/go.mod h1:GT4a9px5kDGRqLizcw/md0QFErrhen76toz4qS3oIoI= -github.com/revel/log15 v2.11.20+incompatible/go.mod h1:l0WmLRs+IM1hBl4noJiBc2tZQiOgZyXzS1mdmFt+5Gc= -github.com/revel/pathtree v0.0.0-20140121041023-41257a1839e9/go.mod h1:TmlwoRLDvgRjoTe6rbsxIaka/CulzYrgfef7iNJcEWY= -github.com/revel/revel v1.0.0/go.mod h1:VZWJnHjpDEtuGUuZJ2NO42XryitrtwsdVaJxfDeo5yc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY= -github.com/xeonx/timeago v1.0.0-rc4/go.mod h1:qDLrYEFynLO7y5Ho7w3GwgtYgpy5UfhcXIIQvMKVDkA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde h1:AMNpJRc7P+GTwVbl8DkK2I9I8BBUzNiHuH/tlxrpan0= +github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde/go.mod h1:MvrEmduDUz4ST5pGZ7CABCnOU5f3ZiOAZzT6b1A6nX8= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0/go.mod h1:kl/bNzW/jgTgUOCGDj3XPn9/Hbfhw6pjfBRUnaTioFQ= -gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From b9ee740885fd103579d5fe1594b3446f8db26b50 Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 10 Nov 2023 16:02:12 -0500 Subject: [PATCH 2/3] update from PR comments --- client.go | 24 +++++++ encryption.go | 192 ++++++++++++++++++++++++++------------------------ go.mod | 4 +- go.sum | 37 ++++++---- 4 files changed, 152 insertions(+), 105 deletions(-) diff --git a/client.go b/client.go index a38a1e7..113ce0b 100644 --- a/client.go +++ b/client.go @@ -135,6 +135,15 @@ func (c *Client) RunWithContextWithString(ctx context.Context, command string, s return outWriter.String(), errWriter.String(), exitCode, err } +// RunCmdWithContext will run command on the the remote host, returning the process stdout and stderr +// as strings +// If the context is canceled, the remote command is canceled. +func (c *Client) RunCmdWithContext(ctx context.Context, command string) (string, string, int, error) { + var outWriter, errWriter bytes.Buffer + exitCode, err := c.RunWithContextWithInput(ctx, command, &outWriter, &errWriter, nil) + return outWriter.String(), errWriter.String(), exitCode, err +} + // RunPSWithString will basically wrap your code to execute commands in powershell.exe. Default RunWithString // runs commands in cmd.exe // @@ -157,6 +166,21 @@ func (c *Client) RunPSWithContextWithString(ctx context.Context, command string, return c.RunWithContextWithString(ctx, command, stdin) } +// RunPSWithContext will basically wrap your code to execute commands in powershell.exe. +// runs commands in cmd.exe +func (c *Client) RunPSWithContext(ctx context.Context, command string) (string, string, int, error) { + command = Powershell(command) + + // Let's check if we actually created a command + if command == "" { + return "", "", 1, errors.New("cannot encode the given command") + } + + var outWriter, errWriter bytes.Buffer + exitCode, err := c.RunWithContextWithInput(ctx, command, &outWriter, &errWriter, nil) + return outWriter.String(), errWriter.String(), exitCode, err +} + // RunWithInput will run command on the the remote host, writing the process stdout and stderr to // the given writers, and injecting the process stdin with the stdin reader. // Warning stdin (not stdout/stderr) are bufferized, which means reading only one byte in stdin will diff --git a/encryption.go b/encryption.go index d494f71..849bc30 100644 --- a/encryption.go +++ b/encryption.go @@ -8,19 +8,16 @@ import ( "io/ioutil" "net/http" "net/url" - "regexp" "strconv" "strings" - ntlmssp "github.com/CalypsoSys/bobntlmssp" - ntlmhttp "github.com/CalypsoSys/bobntlmssp/http" + "github.com/bodgit/ntlmssp" + ntlmhttp "github.com/bodgit/ntlmssp/http" "github.com/masterzen/winrm/soap" ) type Encryption struct { ntlm *ClientNTLM - SIXTEN_KB int - MIME_BOUNDARY []byte protocol string protocolString []byte httpClient *http.Client @@ -29,10 +26,10 @@ type Encryption struct { } const ( - SixteenKB = 16384 - MimeBoundary = "--Encrypted Boundary" + sixTenKB = 16384 + mimeBoundary = "--Encrypted Boundary" defaultCipher = "RC4-HMAC-NTLM" - BoundaryLength = len(MimeBoundary) + boundaryLength = len(mimeBoundary) ) /* @@ -49,28 +46,29 @@ When using Encryption, there are three options available based on the python code from https://pypi.org/project/pywinrm/ + see https://github.com/diyan/pywinrm/blob/master/winrm/encryption.py + uses the most excellent NTLM library from https://github.com/bodgit/ntlmssp - the modified vesion only differs in not outputing raw http request and response */ func NewEncryption(protocol string) (*Encryption, error) { encryption := &Encryption{ - ntlm: &ClientNTLM{}, - SIXTEN_KB: SixteenKB, - MIME_BOUNDARY: []byte(MimeBoundary), - protocol: protocol, + ntlm: &ClientNTLM{}, + protocol: protocol, } switch protocol { case "ntlm": encryption.protocolString = []byte("application/HTTP-SPNEGO-session-encrypted") return encryption, nil - case "credssp": - encryption.protocolString = []byte("application/HTTP-CredSSP-session-encrypted") - case "kerberos": - encryption.protocolString = []byte("application/HTTP-SPNEGO-session-encrypted") + /* credssp and kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation + case "credssp": + encryption.protocolString = []byte("application/HTTP-CredSSP-session-encrypted") + case "kerberos": // kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation + encryption.protocolString = []byte("application/HTTP-SPNEGO-session-encrypted") + */ } - return nil, fmt.Errorf("Encryption for protocol '%s' not supported in bobwinrm", protocol) + return nil, fmt.Errorf("Encryption for protocol '%s' not supported in winrm", protocol) } func (e *Encryption) Transport(endpoint *Endpoint) error { @@ -79,11 +77,24 @@ func (e *Encryption) Transport(endpoint *Endpoint) error { } func (e *Encryption) Post(client *Client, message *soap.SoapMessage) (string, error) { - e.ntlmClient, _ = ntlmssp.NewClient(ntlmssp.SetUserInfo(client.username, client.password), ntlmssp.SetVersion(ntlmssp.DefaultVersion())) + var userName, domain string + if strings.Contains(client.username, "@") { + parts := strings.Split(client.username, "@") + domain = parts[1] + userName = parts[0] + } else if strings.Contains(client.username, "\\") { + parts := strings.Split(client.username, "\\") + domain = parts[0] + userName = parts[1] + } else { + userName = client.username + } + + e.ntlmClient, _ = ntlmssp.NewClient(ntlmssp.SetUserInfo(userName, client.password), ntlmssp.SetDomain(domain), ntlmssp.SetVersion(ntlmssp.DefaultVersion())) e.ntlmhttp, _ = ntlmhttp.NewClient(e.httpClient, e.ntlmClient) - e.ntlmhttp.Debug = false - if e.PrepareRequest(client, client.url) == nil { + var err error + if err = e.PrepareRequest(client, client.url); err == nil { return e.PrepareEncryptedRequest(client, client.url, []byte(message.String())) } else { return e.ntlm.Post(client, message) @@ -96,7 +107,7 @@ func (e *Encryption) PrepareRequest(client *Client, endpoint string) error { return err } - req.Header.Set("User-Agent", "Bob WinRM client") + req.Header.Set("User-Agent", "WinRM client") req.Header.Set("Content-Length", "0") req.Header.Set("Content-Type", "application/soap+xml;charset=UTF-8") req.Header.Set("Connection", "Keep-Alive") @@ -131,12 +142,12 @@ func (e *Encryption) PrepareEncryptedRequest(client *Client, endpoint string, me var content_type string var encrypted_message []byte - if e.protocol == "credssp" && len(message) > e.SIXTEN_KB { + if e.protocol == "credssp" && len(message) > sixTenKB { content_type = "multipart/x-multi-encrypted" encrypted_message = []byte{} message_chunks := [][]byte{} - for i := 0; i < len(message); i += e.SIXTEN_KB { - message_chunks = append(message_chunks, message[i:i+e.SIXTEN_KB]) + for i := 0; i < len(message); i += sixTenKB { + message_chunks = append(message_chunks, message[i:i+sixTenKB]) } for _, message_chunk := range message_chunks { encrypted_chunk := e.encryptMessage(message_chunk, host) @@ -147,7 +158,7 @@ func (e *Encryption) PrepareEncryptedRequest(client *Client, endpoint string, me encrypted_message = e.encryptMessage(message, host) } - encrypted_message = append(encrypted_message, e.MIME_BOUNDARY...) + encrypted_message = append(encrypted_message, []byte(mimeBoundary)...) encrypted_message = append(encrypted_message, []byte("--\r\n")...) req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(encrypted_message)) @@ -155,10 +166,10 @@ func (e *Encryption) PrepareEncryptedRequest(client *Client, endpoint string, me return "", err } - req.Header.Set("User-Agent", "Bob WinRM client") + req.Header.Set("User-Agent", "WinRM client") req.Header.Set("Connection", "Keep-Alive") req.Header.Set("Content-Length", fmt.Sprintf("%d", len(encrypted_message))) - req.Header.Set("Content-Type", content_type+";protocol=\""+string(e.protocolString)+"\";boundary=\"Encrypted Boundary\"") + req.Header.Set("Content-Type", fmt.Sprintf(`%s;protocol="%s";boundary="Encrypted Boundary"`, content_type, e.protocolString)) resp, err := e.ntlmhttp.Do(req) if err != nil { @@ -190,15 +201,14 @@ func (e *Encryption) ParseEncryptedResponse(response *http.Response) ([]byte, er } func (e *Encryption) encryptMessage(message []byte, host string) []byte { - messageLength := []byte(fmt.Sprintf("%d", len(message))) encryptedStream, _ := e.buildMessage(message, host) messagePayload := bytes.Join([][]byte{ - e.MIME_BOUNDARY, + []byte(mimeBoundary), []byte("\r\n"), - []byte("\tContent-Type: " + string(e.protocolString) + "\r\n"), - []byte("\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=" + string(messageLength) + "\r\n"), - e.MIME_BOUNDARY, + []byte(fmt.Sprintf("\tContent-Type: %s\r\n", string(e.protocolString))), + []byte(fmt.Sprintf("\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=%d\r\n", len(message))), + []byte(mimeBoundary), []byte("\r\n"), []byte("\tContent-Type: application/octet-stream\r\n"), encryptedStream, @@ -219,7 +229,7 @@ func deleteEmpty(b [][]byte) [][]byte { func (e *Encryption) decryptResponse(response *http.Response, host string) ([]byte, error) { body, _ := ioutil.ReadAll(response.Body) - parts := deleteEmpty(bytes.Split(body, []byte(MimeBoundary+"\r\n"))) + parts := deleteEmpty(bytes.Split(body, []byte(fmt.Sprintf("%s\r\n", mimeBoundary)))) var message []byte for i := 0; i < len(parts); i += 2 { @@ -227,11 +237,14 @@ func (e *Encryption) decryptResponse(response *http.Response, host string) ([]by payload := parts[i+1] expectedLengthStr := bytes.SplitAfter(header, []byte("Length="))[1] - expectedLength, _ := strconv.Atoi(string(bytes.TrimSpace(expectedLengthStr))) + expectedLength, err := strconv.Atoi(string(bytes.TrimSpace(expectedLengthStr))) + if err != nil { + return nil, err + } // remove the end MIME block if it exists - if bytes.HasSuffix(payload, []byte(MimeBoundary+"--\r\n")) { - payload = payload[:len(payload)-BoundaryLength-4] + if bytes.HasSuffix(payload, []byte(fmt.Sprintf("%s--\r\n", mimeBoundary))) { + payload = payload[:len(payload)-boundaryLength-4] } encryptedData := bytes.ReplaceAll(payload, []byte("\tContent-Type: application/octet-stream\r\n"), []byte{}) decryptedMessage, err := e.decryptMessage(encryptedData, host) @@ -254,10 +267,12 @@ func (e *Encryption) decryptMessage(encryptedData []byte, host string) ([]byte, switch e.protocol { case "ntlm": return e.decryptNtlmMessage(encryptedData, host) - case "credssp": - return e.decryptCredsspMessage(encryptedData, host) - case "kerberos": - return e.decryptKerberosMessage(encryptedData, host) + /* credssp and kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation + case "credssp": + return e.decryptCredsspMessage(encryptedData, host) + case "kerberos": + return e.decryptKerberosMessage(encryptedData, host) + */ default: return nil, errors.New("Encryption for protocol " + e.protocol + " not supported in pywinrm") } @@ -275,50 +290,48 @@ func (e *Encryption) decryptNtlmMessage(encryptedData []byte, host string) ([]by return message, nil } +/* credssp and kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation func (e *Encryption) decryptCredsspMessage(encryptedData []byte, host string) ([]byte, error) { - /* - TODO - encryptedMessage := encryptedData[4:] - - credsspContext, ok := e.session.Auth.Contexts()[host] - if !ok { - return nil, fmt.Errorf("credssp context not found for host: %s", host) - } - - message, err := credsspContext.Unwrap(encryptedMessage) - if err != nil { - return nil, err - } - return message, nil - */ - return nil, nil + // // TODO + // encryptedMessage := encryptedData[4:] + + // credsspContext, ok := e.session.Auth.Contexts()[host] + // if !ok { + // return nil, fmt.Errorf("credssp context not found for host: %s", host) + // } + + // message, err := credsspContext.Unwrap(encryptedMessage) + // if err != nil { + // return nil, err + // } + // return message, nil } func (enc *Encryption) decryptKerberosMessage(encryptedData []byte, host string) ([]byte, error) { - /* - TODO - signatureLength := binary.LittleEndian.Uint32(encryptedData[0:4]) - signature := encryptedData[4 : 4+signatureLength] - encryptedMessage := encryptedData[4+signatureLength:] + // //TODO + // signatureLength := binary.LittleEndian.Uint32(encryptedData[0:4]) + // signature := encryptedData[4 : 4+signatureLength] + // encryptedMessage := encryptedData[4+signatureLength:] - message, err := enc.session.Auth.UnwrapWinrm(host, encryptedMessage, signature) - if err != nil { - return nil, err - } + // message, err := enc.session.Auth.UnwrapWinrm(host, encryptedMessage, signature) + // if err != nil { + // return nil, err + // } - return message, nil - */ - return nil, nil + // return message, nil } +*/ func (e *Encryption) buildMessage(encryptedData []byte, host string) ([]byte, error) { switch e.protocol { case "ntlm": return e.buildNTLMMessage(encryptedData, host) - case "credssp": - return e.buildCredSSPMessage(encryptedData, host) - case "kerberos": - return e.buildKerberosMessage(encryptedData, host) + /* credssp and kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation + case "credssp": + return e.buildCredSSPMessage(encryptedData, host) + case "kerberos": + return e.buildKerberosMessage(encryptedData, host) + */ default: return nil, errors.New("Encryption for protocol " + e.protocol + " not supported in pywinrm") } @@ -344,33 +357,29 @@ func (enc *Encryption) buildNTLMMessage(message []byte, host string) ([]byte, er return buf.Bytes(), nil } +/* credssp and kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation func (e *Encryption) buildCredSSPMessage(message []byte, host string) ([]byte, error) { - /* - TODO - context := e.session.Auth.Contexts[host] - sealedMessage := context.Wrap(message) + // //TODO + // context := e.session.Auth.Contexts[host] + // sealedMessage := context.Wrap(message) - cipherNegotiated := context.TLSConnection.ConnectionState().CipherSuite.Name - trailerLength := e.getCredSSPTrailerLength(len(message), cipherNegotiated) + // cipherNegotiated := context.TLSConnection.ConnectionState().CipherSuite.Name + // trailerLength := e.getCredSSPTrailerLength(len(message), cipherNegotiated) - trailer := make([]byte, 4) - binary.LittleEndian.PutUint32(trailer, uint32(trailerLength)) + // trailer := make([]byte, 4) + // binary.LittleEndian.PutUint32(trailer, uint32(trailerLength)) - return append(trailer, sealedMessage...), nil - */ - return nil, nil + // return append(trailer, sealedMessage...), nil } func (e *Encryption) buildKerberosMessage(message []byte, host string) ([]byte, error) { - /* - sealedMessage, signature := e.session.Auth.WrapWinrm(host, message) + // //TODO + // sealedMessage, signature := e.session.Auth.WrapWinrm(host, message) - signatureLength := make([]byte, 4) - binary.LittleEndian.PutUint32(signatureLength, uint32(len(signature))) + // signatureLength := make([]byte, 4) + // binary.LittleEndian.PutUint32(signatureLength, uint32(len(signature))) - return append(append(signatureLength, signature...), sealedMessage...), nil - */ - return nil, nil + // return append(append(signatureLength, signature...), sealedMessage...), nil } func (e *Encryption) getCredSSPTrailerLength(messageLength int, cipherSuite string) int { @@ -411,3 +420,4 @@ func (e *Encryption) getCredSSPTrailerLength(messageLength int, cipherSuite stri } return trailerLength } +*/ diff --git a/go.mod b/go.mod index ee99d0e..dc822eb 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,13 @@ go 1.14 require ( github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e - github.com/CalypsoSys/bobntlmssp v0.0.0-20230312131519-5d92ca710dc8 github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 + github.com/bodgit/ntlmssp v0.0.0-20231106151141-9c8ca384e779 github.com/gofrs/uuid v4.2.0+incompatible github.com/google/go-cmp v0.5.6 // indirect github.com/jcmturner/gokrb5/v8 v8.4.2 github.com/kr/pretty v0.1.0 // indirect github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 - golang.org/x/text v0.6.0 + golang.org/x/text v0.14.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 ) diff --git a/go.sum b/go.sum index 5954f9d..d96f6a7 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,16 @@ github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/CalypsoSys/bobntlmssp v0.0.0-20230312131519-5d92ca710dc8 h1:ilCicqekIaB0g/qxeF39HG6uTiI9eRhE9SOZEzbHNno= -github.com/CalypsoSys/bobntlmssp v0.0.0-20230312131519-5d92ca710dc8/go.mod h1:Vjp4JyzHu88bTXaIwegBl5+CCyIuodh38/4APRgTzgo= github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 h1:w0E0fgc1YafGEh5cROhlROMWXiNoZqApk2PDN0M1+Ns= github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= +github.com/bodgit/ntlmssp v0.0.0-20231106151141-9c8ca384e779 h1:G5A5PWIW8cxeROezDOwsSDrWKVQgGPJjNY6z7/sP/nM= +github.com/bodgit/ntlmssp v0.0.0-20231106151141-9c8ca384e779/go.mod h1:JTsuaH4GfVMygSKlsvcdGdko5KpoSSnWCAykYfBRHhY= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= @@ -49,44 +51,55 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde h1:AMNpJRc7P+GTwVbl8DkK2I9I8BBUzNiHuH/tlxrpan0= github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde/go.mod h1:MvrEmduDUz4ST5pGZ7CABCnOU5f3ZiOAZzT6b1A6nX8= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 30b2a0f97f9386de8d62885e868fc3ce29e29dd2 Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 10 Nov 2023 16:35:43 -0500 Subject: [PATCH 3/3] mulipart parse comment --- encryption.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/encryption.go b/encryption.go index 849bc30..30e5477 100644 --- a/encryption.go +++ b/encryption.go @@ -227,6 +227,10 @@ func deleteEmpty(b [][]byte) [][]byte { return r } +// tried using pkg.go.dev/mime/multipart here but parsing fails with with +// because in the header we have "\tContent-Type: application/HTTP-SPNEGO-session-encrypted\r\n" +// on call to textproto.ReadMIMEHeader +// because of "The first line cannot start with a leading space." func (e *Encryption) decryptResponse(response *http.Response, host string) ([]byte, error) { body, _ := ioutil.ReadAll(response.Body) parts := deleteEmpty(bytes.Split(body, []byte(fmt.Sprintf("%s\r\n", mimeBoundary))))