Skip to content

Commit

Permalink
comment from main PR
Browse files Browse the repository at this point in the history
  • Loading branch information
CalypsoSys committed Nov 11, 2023
1 parent 7e20e8d commit a06c2e3
Showing 1 changed file with 87 additions and 86 deletions.
173 changes: 87 additions & 86 deletions encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"

Expand All @@ -19,8 +18,6 @@ import (

type Encryption struct {
ntlm *ClientNTLM
SIXTEN_KB int
MIME_BOUNDARY []byte
protocol string
protocolString []byte
httpClient *http.Client
Expand All @@ -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)
)

/*
Expand All @@ -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 {
Expand Down Expand Up @@ -109,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")
Expand Down Expand Up @@ -144,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)
Expand All @@ -160,18 +158,18 @@ 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))
if err != nil {
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 {
Expand Down Expand Up @@ -203,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,
Expand All @@ -230,21 +227,28 @@ 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(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 {
header := parts[i]
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)
Expand All @@ -267,10 +271,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")
}
Expand All @@ -288,50 +294,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")
}
Expand All @@ -357,33 +361,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 {
Expand Down Expand Up @@ -424,3 +424,4 @@ func (e *Encryption) getCredSSPTrailerLength(messageLength int, cipherSuite stri
}
return trailerLength
}
*/

0 comments on commit a06c2e3

Please sign in to comment.