winrm-s 0.2.4 → 0.3.0.dev.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +27 -27
- data/.rspec +2 -2
- data/CHANGELOG.md +29 -29
- data/CONTRIBUTING.md +5 -5
- data/Gemfile +7 -7
- data/LICENSE +201 -201
- data/README.md +69 -69
- data/Rakefile +1 -1
- data/lib/winrm-s.rb +30 -30
- data/lib/winrm-s/version.rb +22 -22
- data/lib/winrm/helpers/assert_patch.rb +17 -17
- data/lib/winrm/http/auth.rb +191 -191
- data/lib/winrm/http/transport_patch.rb +35 -35
- data/lib/winrm/win32/sspi.rb +256 -256
- data/winrm-s.gemspec +25 -25
- metadata +23 -25
@@ -1,35 +1,35 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
|
3
|
-
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
|
4
|
-
# License:: Apache License, Version 2.0
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#
|
18
|
-
|
19
|
-
module WinRM
|
20
|
-
module HTTP
|
21
|
-
|
22
|
-
class HttpSSPINegotiate < HttpTransport
|
23
|
-
def initialize(endpoint, user, pass, opts)
|
24
|
-
# Override the relevant functionality in httpclient to make sspi work.
|
25
|
-
require 'winrm/http/auth'
|
26
|
-
super(endpoint)
|
27
|
-
@httpcli.set_auth(nil, user, pass)
|
28
|
-
# Remove non-sspi auths
|
29
|
-
auths = @httpcli.www_auth.instance_variable_get('@authenticator')
|
30
|
-
auths.delete_if {|i| not i.is_a?(HTTPClient::SSPINegotiateAuth)}
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
end # WinRM
|
1
|
+
#
|
2
|
+
# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
|
3
|
+
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module WinRM
|
20
|
+
module HTTP
|
21
|
+
|
22
|
+
class HttpSSPINegotiate < HttpTransport
|
23
|
+
def initialize(endpoint, user, pass, opts)
|
24
|
+
# Override the relevant functionality in httpclient to make sspi work.
|
25
|
+
require 'winrm/http/auth'
|
26
|
+
super(endpoint)
|
27
|
+
@httpcli.set_auth(nil, user, pass)
|
28
|
+
# Remove non-sspi auths
|
29
|
+
auths = @httpcli.www_auth.instance_variable_get('@authenticator')
|
30
|
+
auths.delete_if {|i| not i.is_a?(HTTPClient::SSPINegotiateAuth)}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end # WinRM
|
data/lib/winrm/win32/sspi.rb
CHANGED
@@ -1,256 +1,256 @@
|
|
1
|
-
|
2
|
-
require 'winrm/helpers/assert_patch'
|
3
|
-
|
4
|
-
def validate_patch
|
5
|
-
# The code below patches the Win32::SSPI module from ruby core to add support
|
6
|
-
# for encrypt/decrypt as described below.
|
7
|
-
# Add few restrictions to make sure the patched methods are still
|
8
|
-
# available, but still give a way to consciously use later versions
|
9
|
-
PatchAssertions.assert_arity_of_patched_method(Win32::SSPI::NegotiateAuth, "initialize", -1)
|
10
|
-
PatchAssertions.assert_arity_of_patched_method(Win32::SSPI::NegotiateAuth, "complete_authentication", 1)
|
11
|
-
PatchAssertions.assert_arity_of_patched_method(Win32::SSPI::NegotiateAuth, "get_credentials", 0)
|
12
|
-
end
|
13
|
-
|
14
|
-
# Perform the patch validations
|
15
|
-
validate_patch
|
16
|
-
|
17
|
-
# Overrides and enhances the ruby core win32 sspi module to add support to
|
18
|
-
# encrypt/decrypt data to be sent over channel, example using SSP Negotiate auth
|
19
|
-
|
20
|
-
module Win32
|
21
|
-
module SSPI
|
22
|
-
# QueryContextAttributes attributes flags
|
23
|
-
SECPKG_ATTR_SIZES = 0x00000000
|
24
|
-
|
25
|
-
module API
|
26
|
-
QueryContextAttributes = Win32API.new("secur32", "QueryContextAttributes", 'pLp', 'L')
|
27
|
-
EncryptMessage = Win32API.new("secur32", "EncryptMessage", 'pLpL', 'L')
|
28
|
-
DecryptMessage = Win32API.new("secur32", "DecryptMessage", 'ppLp', 'L')
|
29
|
-
end
|
30
|
-
|
31
|
-
class SecPkgContext_Sizes
|
32
|
-
attr_accessor :cbMaxToken, :cbMaxSignature, :cbBlockSize, :cbSecurityTrailer
|
33
|
-
|
34
|
-
def initialize
|
35
|
-
@cbMaxToken = @cbMaxSignature = @cbBlockSize = @cbSecurityTrailer = 0
|
36
|
-
end
|
37
|
-
|
38
|
-
def cbMaxToken
|
39
|
-
@cbMaxToken = @struct.unpack("LLLL")[0] if @struct
|
40
|
-
end
|
41
|
-
|
42
|
-
def cbMaxSignature
|
43
|
-
@cbMaxSignature = @struct.unpack("LLLL")[1] if @struct
|
44
|
-
end
|
45
|
-
|
46
|
-
def cbBlockSize
|
47
|
-
@cbBlockSize = @struct.unpack("LLLL")[2] if @struct
|
48
|
-
end
|
49
|
-
|
50
|
-
def cbSecurityTrailer
|
51
|
-
@cbSecurityTrailer = @struct.unpack("LLLL")[3] if @struct
|
52
|
-
end
|
53
|
-
|
54
|
-
def to_p
|
55
|
-
@struct ||= [@cbMaxToken, @cbMaxSignature, @cbBlockSize, @cbSecurityTrailer].pack("LLLL")
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# SecurityBuffer for data to be encrypted
|
60
|
-
class EncryptedSecurityBuffer
|
61
|
-
|
62
|
-
SECBUFFER_DATA = 0x1 # Security buffer data
|
63
|
-
SECBUFFER_TOKEN = 0x2 # Security token
|
64
|
-
SECBUFFER_VERSION = 0
|
65
|
-
|
66
|
-
def initialize(data_buffer, sizes)
|
67
|
-
@original_msg_len = data_buffer.length
|
68
|
-
@cbSecurityTrailer = sizes.cbSecurityTrailer
|
69
|
-
@data_buffer = data_buffer
|
70
|
-
@security_trailer = "\0" * sizes.cbSecurityTrailer
|
71
|
-
end
|
72
|
-
|
73
|
-
def to_p
|
74
|
-
# Assumption is that when to_p is called we are going to get a packed structure. Therefore,
|
75
|
-
# set @unpacked back to nil so we know to unpack when accessors are next accessed.
|
76
|
-
@unpacked = nil
|
77
|
-
# Assignment of inner structure to variable is very important here. Without it,
|
78
|
-
# will not be able to unpack changes to the structure. Alternative, nested unpacks,
|
79
|
-
# does not work (i.e. @struct.unpack("LLP12")[2].unpack("LLP12") results in "no associated pointer")
|
80
|
-
@sec_buffer = [@original_msg_len, SECBUFFER_DATA, @data_buffer, @cbSecurityTrailer, SECBUFFER_TOKEN, @security_trailer].pack("LLPLLP")
|
81
|
-
@struct ||= [SECBUFFER_VERSION, 2, @sec_buffer].pack("LLP")
|
82
|
-
end
|
83
|
-
|
84
|
-
def buffer
|
85
|
-
unpack
|
86
|
-
@buffer
|
87
|
-
end
|
88
|
-
|
89
|
-
private
|
90
|
-
|
91
|
-
# Unpacks the SecurityBufferDesc structure into member variables. We
|
92
|
-
# only want to do this once per struct, so the struct is deleted
|
93
|
-
# after unpacking.
|
94
|
-
def unpack
|
95
|
-
if ! @unpacked && @sec_buffer && @struct
|
96
|
-
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLPLLP")
|
97
|
-
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLP#{dataBufferSize}LLP#{tokenBufferSize}")
|
98
|
-
# Form the buffer stream as required by server
|
99
|
-
@buffer = [tokenBufferSize].pack("L")
|
100
|
-
@buffer << tokenBuffer << dataBuffer
|
101
|
-
@struct = nil
|
102
|
-
@sec_buffer = nil
|
103
|
-
@unpacked = true
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
class DecryptedSecurityBuffer
|
109
|
-
|
110
|
-
SECBUFFER_DATA = 0x1 # Security buffer data
|
111
|
-
SECBUFFER_TOKEN = 0x2 # Security token
|
112
|
-
SECBUFFER_VERSION = 0
|
113
|
-
|
114
|
-
def initialize(buffer)
|
115
|
-
# unpack to extract the msg and token
|
116
|
-
token_size, token_buffer, enc_buffer = buffer.unpack("L")
|
117
|
-
@original_msg_len = buffer.length - token_size - 4
|
118
|
-
@cbSecurityTrailer, @security_trailer, @data_buffer = buffer.unpack("La#{token_size}a#{@original_msg_len}")
|
119
|
-
end
|
120
|
-
|
121
|
-
def to_p
|
122
|
-
# Assumption is that when to_p is called we are going to get a packed structure. Therefore,
|
123
|
-
# set @unpacked back to nil so we know to unpack when accessors are next accessed.
|
124
|
-
@unpacked = nil
|
125
|
-
# Assignment of inner structure to variable is very important here. Without it,
|
126
|
-
# will not be able to unpack changes to the structure. Alternative, nested unpacks,
|
127
|
-
# does not work (i.e. @struct.unpack("LLP12")[2].unpack("LLP12") results in "no associated pointer")
|
128
|
-
@sec_buffer = [@original_msg_len, SECBUFFER_DATA, @data_buffer, @cbSecurityTrailer, SECBUFFER_TOKEN, @security_trailer].pack("LLPLLP")
|
129
|
-
@struct ||= [SECBUFFER_VERSION, 2, @sec_buffer].pack("LLP")
|
130
|
-
end
|
131
|
-
|
132
|
-
def buffer
|
133
|
-
unpack
|
134
|
-
@buffer
|
135
|
-
end
|
136
|
-
|
137
|
-
private
|
138
|
-
|
139
|
-
# Unpacks the SecurityBufferDesc structure into member variables. We
|
140
|
-
# only want to do this once per struct, so the struct is deleted
|
141
|
-
# after unpacking.
|
142
|
-
def unpack
|
143
|
-
if ! @unpacked && @sec_buffer && @struct
|
144
|
-
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLPLLP")
|
145
|
-
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLP#{dataBufferSize}LLP#{tokenBufferSize}")
|
146
|
-
@buffer = dataBuffer
|
147
|
-
@struct = nil
|
148
|
-
@sec_buffer = nil
|
149
|
-
@unpacked = true
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
class NegotiateAuth
|
155
|
-
# Override to remember password
|
156
|
-
# Creates a new instance ready for authentication as the given user in the given domain.
|
157
|
-
# Defaults to current user and domain as defined by ENV["USERDOMAIN"] and ENV["USERNAME"] if
|
158
|
-
# no arguments are supplied.
|
159
|
-
def initialize(user = nil, domain = nil, password = nil)
|
160
|
-
if user.nil? && domain.nil? && ENV["USERNAME"].nil? && ENV["USERDOMAIN"].nil?
|
161
|
-
raise "A username or domain must be supplied since they cannot be retrieved from the environment"
|
162
|
-
end
|
163
|
-
@user = user || ENV["USERNAME"].dup
|
164
|
-
@domain = domain || ENV["USERDOMAIN"].dup
|
165
|
-
@password = password
|
166
|
-
end
|
167
|
-
|
168
|
-
# Takes a token and gets the next token in the Negotiate authentication chain. Token can be Base64 encoded or not.
|
169
|
-
# The token can include the "Negotiate" header and it will be stripped.
|
170
|
-
# Does not indicate if SEC_I_CONTINUE or SEC_E_OK was returned.
|
171
|
-
# Token returned is Base64 encoded w/ all new lines removed.
|
172
|
-
def complete_authentication(token)
|
173
|
-
raise "This object is no longer usable because its resources have been freed." if @cleaned_up
|
174
|
-
|
175
|
-
# Nil token OK, just set it to empty string
|
176
|
-
token = "" if token.nil?
|
177
|
-
|
178
|
-
if token.include? "Negotiate"
|
179
|
-
# If the Negotiate prefix is passed in, assume we are seeing "Negotiate <token>" and get the token.
|
180
|
-
token = token.split(" ").last
|
181
|
-
end
|
182
|
-
|
183
|
-
if token.include? B64_TOKEN_PREFIX
|
184
|
-
# indicates base64 encoded token
|
185
|
-
token = token.strip.unpack("m")[0]
|
186
|
-
end
|
187
|
-
|
188
|
-
outputBuffer = SecurityBuffer.new
|
189
|
-
result = SSPIResult.new(API::InitializeSecurityContext.call(@credentials.to_p, @context.to_p, nil,
|
190
|
-
REQUEST_FLAGS, 0, SECURITY_NETWORK_DREP, SecurityBuffer.new(token).to_p, 0,
|
191
|
-
@context.to_p,
|
192
|
-
outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p))
|
193
|
-
|
194
|
-
if result.ok? then
|
195
|
-
@auth_successful = true
|
196
|
-
return encode_token(outputBuffer.token)
|
197
|
-
else
|
198
|
-
raise "Error: #{result.to_s}"
|
199
|
-
end
|
200
|
-
ensure
|
201
|
-
# need to make sure we don't clean up if we've already cleaned up.
|
202
|
-
# XXX - clean up later since encrypt/decrypt needs this
|
203
|
-
# clean_up unless @cleaned_up
|
204
|
-
end
|
205
|
-
|
206
|
-
def encrypt_payload(body)
|
207
|
-
if @auth_successful
|
208
|
-
# Approach - http://msdn.microsoft.com/en-us/magazine/cc301890.aspx
|
209
|
-
sizes = SecPkgContext_Sizes.new
|
210
|
-
result = SSPIResult.new(API::QueryContextAttributes.call(@context.to_p, SECPKG_ATTR_SIZES, sizes.to_p))
|
211
|
-
|
212
|
-
outputBuffer = EncryptedSecurityBuffer.new(body, sizes)
|
213
|
-
result = SSPIResult.new(API::EncryptMessage.call(@context.to_p, 0, outputBuffer.to_p, 0 ))
|
214
|
-
encrypted_msg = outputBuffer.buffer
|
215
|
-
|
216
|
-
body = <<-EOF
|
217
|
-
--Encrypted Boundary\r
|
218
|
-
Content-Type: application/HTTP-SPNEGO-session-encrypted\r
|
219
|
-
OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{encrypted_msg.length - sizes.cbSecurityTrailer - 4}\r
|
220
|
-
--Encrypted Boundary\r
|
221
|
-
Content-Type: application/octet-stream\r
|
222
|
-
#{encrypted_msg}--Encrypted Boundary\r
|
223
|
-
EOF
|
224
|
-
end
|
225
|
-
body
|
226
|
-
end
|
227
|
-
|
228
|
-
def decrypt_payload(body)
|
229
|
-
if @auth_successful
|
230
|
-
|
231
|
-
matched_data = /--Encrypted Boundary\s+Content-Type:\s+application\/HTTP-SPNEGO-session-encrypted\s+OriginalContent:\s+type=\S+Length=(\d+)\s+--Encrypted Boundary\s+Content-Type:\s+application\/octet-stream\s+([\S\s]+)--Encrypted Boundary/.match(body)
|
232
|
-
raise "Error: Unencrypted communication not supported. Please check winrm configuration winrm/config/service AllowUnencrypted flag." if matched_data.nil?
|
233
|
-
|
234
|
-
encrypted_msg = matched_data[2]
|
235
|
-
|
236
|
-
outputBuffer = DecryptedSecurityBuffer.new(encrypted_msg)
|
237
|
-
result = SSPIResult.new(API::DecryptMessage.call(@context.to_p, outputBuffer.to_p, 0, 0 ))
|
238
|
-
body = outputBuffer.buffer
|
239
|
-
end
|
240
|
-
body
|
241
|
-
end
|
242
|
-
|
243
|
-
private
|
244
|
-
# ** Override to add password support
|
245
|
-
# Gets credentials based on user, domain or both. If both are nil, an error occurs
|
246
|
-
def get_credentials
|
247
|
-
@credentials = CredHandle.new
|
248
|
-
ts = TimeStamp.new
|
249
|
-
@identity = Identity.new @user, @domain, @password
|
250
|
-
result = SSPIResult.new(API::AcquireCredentialsHandle.call(nil, "Negotiate", SECPKG_CRED_OUTBOUND, nil, @identity.to_p,
|
251
|
-
nil, nil, @credentials.to_p, ts.to_p))
|
252
|
-
raise "Error acquire credentials: #{result}" unless result.ok?
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
end
|
1
|
+
|
2
|
+
require 'winrm/helpers/assert_patch'
|
3
|
+
|
4
|
+
def validate_patch
|
5
|
+
# The code below patches the Win32::SSPI module from ruby core to add support
|
6
|
+
# for encrypt/decrypt as described below.
|
7
|
+
# Add few restrictions to make sure the patched methods are still
|
8
|
+
# available, but still give a way to consciously use later versions
|
9
|
+
PatchAssertions.assert_arity_of_patched_method(Win32::SSPI::NegotiateAuth, "initialize", -1)
|
10
|
+
PatchAssertions.assert_arity_of_patched_method(Win32::SSPI::NegotiateAuth, "complete_authentication", 1)
|
11
|
+
PatchAssertions.assert_arity_of_patched_method(Win32::SSPI::NegotiateAuth, "get_credentials", 0)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Perform the patch validations
|
15
|
+
validate_patch
|
16
|
+
|
17
|
+
# Overrides and enhances the ruby core win32 sspi module to add support to
|
18
|
+
# encrypt/decrypt data to be sent over channel, example using SSP Negotiate auth
|
19
|
+
|
20
|
+
module Win32
|
21
|
+
module SSPI
|
22
|
+
# QueryContextAttributes attributes flags
|
23
|
+
SECPKG_ATTR_SIZES = 0x00000000
|
24
|
+
|
25
|
+
module API
|
26
|
+
QueryContextAttributes = Win32API.new("secur32", "QueryContextAttributes", 'pLp', 'L')
|
27
|
+
EncryptMessage = Win32API.new("secur32", "EncryptMessage", 'pLpL', 'L')
|
28
|
+
DecryptMessage = Win32API.new("secur32", "DecryptMessage", 'ppLp', 'L')
|
29
|
+
end
|
30
|
+
|
31
|
+
class SecPkgContext_Sizes
|
32
|
+
attr_accessor :cbMaxToken, :cbMaxSignature, :cbBlockSize, :cbSecurityTrailer
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@cbMaxToken = @cbMaxSignature = @cbBlockSize = @cbSecurityTrailer = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def cbMaxToken
|
39
|
+
@cbMaxToken = @struct.unpack("LLLL")[0] if @struct
|
40
|
+
end
|
41
|
+
|
42
|
+
def cbMaxSignature
|
43
|
+
@cbMaxSignature = @struct.unpack("LLLL")[1] if @struct
|
44
|
+
end
|
45
|
+
|
46
|
+
def cbBlockSize
|
47
|
+
@cbBlockSize = @struct.unpack("LLLL")[2] if @struct
|
48
|
+
end
|
49
|
+
|
50
|
+
def cbSecurityTrailer
|
51
|
+
@cbSecurityTrailer = @struct.unpack("LLLL")[3] if @struct
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_p
|
55
|
+
@struct ||= [@cbMaxToken, @cbMaxSignature, @cbBlockSize, @cbSecurityTrailer].pack("LLLL")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# SecurityBuffer for data to be encrypted
|
60
|
+
class EncryptedSecurityBuffer
|
61
|
+
|
62
|
+
SECBUFFER_DATA = 0x1 # Security buffer data
|
63
|
+
SECBUFFER_TOKEN = 0x2 # Security token
|
64
|
+
SECBUFFER_VERSION = 0
|
65
|
+
|
66
|
+
def initialize(data_buffer, sizes)
|
67
|
+
@original_msg_len = data_buffer.length
|
68
|
+
@cbSecurityTrailer = sizes.cbSecurityTrailer
|
69
|
+
@data_buffer = data_buffer
|
70
|
+
@security_trailer = "\0" * sizes.cbSecurityTrailer
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_p
|
74
|
+
# Assumption is that when to_p is called we are going to get a packed structure. Therefore,
|
75
|
+
# set @unpacked back to nil so we know to unpack when accessors are next accessed.
|
76
|
+
@unpacked = nil
|
77
|
+
# Assignment of inner structure to variable is very important here. Without it,
|
78
|
+
# will not be able to unpack changes to the structure. Alternative, nested unpacks,
|
79
|
+
# does not work (i.e. @struct.unpack("LLP12")[2].unpack("LLP12") results in "no associated pointer")
|
80
|
+
@sec_buffer = [@original_msg_len, SECBUFFER_DATA, @data_buffer, @cbSecurityTrailer, SECBUFFER_TOKEN, @security_trailer].pack("LLPLLP")
|
81
|
+
@struct ||= [SECBUFFER_VERSION, 2, @sec_buffer].pack("LLP")
|
82
|
+
end
|
83
|
+
|
84
|
+
def buffer
|
85
|
+
unpack
|
86
|
+
@buffer
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Unpacks the SecurityBufferDesc structure into member variables. We
|
92
|
+
# only want to do this once per struct, so the struct is deleted
|
93
|
+
# after unpacking.
|
94
|
+
def unpack
|
95
|
+
if ! @unpacked && @sec_buffer && @struct
|
96
|
+
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLPLLP")
|
97
|
+
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLP#{dataBufferSize}LLP#{tokenBufferSize}")
|
98
|
+
# Form the buffer stream as required by server
|
99
|
+
@buffer = [tokenBufferSize].pack("L")
|
100
|
+
@buffer << tokenBuffer << dataBuffer
|
101
|
+
@struct = nil
|
102
|
+
@sec_buffer = nil
|
103
|
+
@unpacked = true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class DecryptedSecurityBuffer
|
109
|
+
|
110
|
+
SECBUFFER_DATA = 0x1 # Security buffer data
|
111
|
+
SECBUFFER_TOKEN = 0x2 # Security token
|
112
|
+
SECBUFFER_VERSION = 0
|
113
|
+
|
114
|
+
def initialize(buffer)
|
115
|
+
# unpack to extract the msg and token
|
116
|
+
token_size, token_buffer, enc_buffer = buffer.unpack("L")
|
117
|
+
@original_msg_len = buffer.length - token_size - 4
|
118
|
+
@cbSecurityTrailer, @security_trailer, @data_buffer = buffer.unpack("La#{token_size}a#{@original_msg_len}")
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_p
|
122
|
+
# Assumption is that when to_p is called we are going to get a packed structure. Therefore,
|
123
|
+
# set @unpacked back to nil so we know to unpack when accessors are next accessed.
|
124
|
+
@unpacked = nil
|
125
|
+
# Assignment of inner structure to variable is very important here. Without it,
|
126
|
+
# will not be able to unpack changes to the structure. Alternative, nested unpacks,
|
127
|
+
# does not work (i.e. @struct.unpack("LLP12")[2].unpack("LLP12") results in "no associated pointer")
|
128
|
+
@sec_buffer = [@original_msg_len, SECBUFFER_DATA, @data_buffer, @cbSecurityTrailer, SECBUFFER_TOKEN, @security_trailer].pack("LLPLLP")
|
129
|
+
@struct ||= [SECBUFFER_VERSION, 2, @sec_buffer].pack("LLP")
|
130
|
+
end
|
131
|
+
|
132
|
+
def buffer
|
133
|
+
unpack
|
134
|
+
@buffer
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
# Unpacks the SecurityBufferDesc structure into member variables. We
|
140
|
+
# only want to do this once per struct, so the struct is deleted
|
141
|
+
# after unpacking.
|
142
|
+
def unpack
|
143
|
+
if ! @unpacked && @sec_buffer && @struct
|
144
|
+
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLPLLP")
|
145
|
+
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLP#{dataBufferSize}LLP#{tokenBufferSize}")
|
146
|
+
@buffer = dataBuffer
|
147
|
+
@struct = nil
|
148
|
+
@sec_buffer = nil
|
149
|
+
@unpacked = true
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class NegotiateAuth
|
155
|
+
# Override to remember password
|
156
|
+
# Creates a new instance ready for authentication as the given user in the given domain.
|
157
|
+
# Defaults to current user and domain as defined by ENV["USERDOMAIN"] and ENV["USERNAME"] if
|
158
|
+
# no arguments are supplied.
|
159
|
+
def initialize(user = nil, domain = nil, password = nil)
|
160
|
+
if user.nil? && domain.nil? && ENV["USERNAME"].nil? && ENV["USERDOMAIN"].nil?
|
161
|
+
raise "A username or domain must be supplied since they cannot be retrieved from the environment"
|
162
|
+
end
|
163
|
+
@user = user || ENV["USERNAME"].dup
|
164
|
+
@domain = domain || ENV["USERDOMAIN"].dup
|
165
|
+
@password = password
|
166
|
+
end
|
167
|
+
|
168
|
+
# Takes a token and gets the next token in the Negotiate authentication chain. Token can be Base64 encoded or not.
|
169
|
+
# The token can include the "Negotiate" header and it will be stripped.
|
170
|
+
# Does not indicate if SEC_I_CONTINUE or SEC_E_OK was returned.
|
171
|
+
# Token returned is Base64 encoded w/ all new lines removed.
|
172
|
+
def complete_authentication(token)
|
173
|
+
raise "This object is no longer usable because its resources have been freed." if @cleaned_up
|
174
|
+
|
175
|
+
# Nil token OK, just set it to empty string
|
176
|
+
token = "" if token.nil?
|
177
|
+
|
178
|
+
if token.include? "Negotiate"
|
179
|
+
# If the Negotiate prefix is passed in, assume we are seeing "Negotiate <token>" and get the token.
|
180
|
+
token = token.split(" ").last
|
181
|
+
end
|
182
|
+
|
183
|
+
if token.include? B64_TOKEN_PREFIX
|
184
|
+
# indicates base64 encoded token
|
185
|
+
token = token.strip.unpack("m")[0]
|
186
|
+
end
|
187
|
+
|
188
|
+
outputBuffer = SecurityBuffer.new
|
189
|
+
result = SSPIResult.new(API::InitializeSecurityContext.call(@credentials.to_p, @context.to_p, nil,
|
190
|
+
REQUEST_FLAGS, 0, SECURITY_NETWORK_DREP, SecurityBuffer.new(token).to_p, 0,
|
191
|
+
@context.to_p,
|
192
|
+
outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p))
|
193
|
+
|
194
|
+
if result.ok? then
|
195
|
+
@auth_successful = true
|
196
|
+
return encode_token(outputBuffer.token)
|
197
|
+
else
|
198
|
+
raise "Error: #{result.to_s}"
|
199
|
+
end
|
200
|
+
ensure
|
201
|
+
# need to make sure we don't clean up if we've already cleaned up.
|
202
|
+
# XXX - clean up later since encrypt/decrypt needs this
|
203
|
+
# clean_up unless @cleaned_up
|
204
|
+
end
|
205
|
+
|
206
|
+
def encrypt_payload(body)
|
207
|
+
if @auth_successful
|
208
|
+
# Approach - http://msdn.microsoft.com/en-us/magazine/cc301890.aspx
|
209
|
+
sizes = SecPkgContext_Sizes.new
|
210
|
+
result = SSPIResult.new(API::QueryContextAttributes.call(@context.to_p, SECPKG_ATTR_SIZES, sizes.to_p))
|
211
|
+
|
212
|
+
outputBuffer = EncryptedSecurityBuffer.new(body, sizes)
|
213
|
+
result = SSPIResult.new(API::EncryptMessage.call(@context.to_p, 0, outputBuffer.to_p, 0 ))
|
214
|
+
encrypted_msg = outputBuffer.buffer
|
215
|
+
|
216
|
+
body = <<-EOF
|
217
|
+
--Encrypted Boundary\r
|
218
|
+
Content-Type: application/HTTP-SPNEGO-session-encrypted\r
|
219
|
+
OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{encrypted_msg.length - sizes.cbSecurityTrailer - 4}\r
|
220
|
+
--Encrypted Boundary\r
|
221
|
+
Content-Type: application/octet-stream\r
|
222
|
+
#{encrypted_msg}--Encrypted Boundary\r
|
223
|
+
EOF
|
224
|
+
end
|
225
|
+
body
|
226
|
+
end
|
227
|
+
|
228
|
+
def decrypt_payload(body)
|
229
|
+
if @auth_successful
|
230
|
+
|
231
|
+
matched_data = /--Encrypted Boundary\s+Content-Type:\s+application\/HTTP-SPNEGO-session-encrypted\s+OriginalContent:\s+type=\S+Length=(\d+)\s+--Encrypted Boundary\s+Content-Type:\s+application\/octet-stream\s+([\S\s]+)--Encrypted Boundary/.match(body)
|
232
|
+
raise "Error: Unencrypted communication not supported. Please check winrm configuration winrm/config/service AllowUnencrypted flag." if matched_data.nil?
|
233
|
+
|
234
|
+
encrypted_msg = matched_data[2]
|
235
|
+
|
236
|
+
outputBuffer = DecryptedSecurityBuffer.new(encrypted_msg)
|
237
|
+
result = SSPIResult.new(API::DecryptMessage.call(@context.to_p, outputBuffer.to_p, 0, 0 ))
|
238
|
+
body = outputBuffer.buffer
|
239
|
+
end
|
240
|
+
body
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
# ** Override to add password support
|
245
|
+
# Gets credentials based on user, domain or both. If both are nil, an error occurs
|
246
|
+
def get_credentials
|
247
|
+
@credentials = CredHandle.new
|
248
|
+
ts = TimeStamp.new
|
249
|
+
@identity = Identity.new @user, @domain, @password
|
250
|
+
result = SSPIResult.new(API::AcquireCredentialsHandle.call(nil, "Negotiate", SECPKG_CRED_OUTBOUND, nil, @identity.to_p,
|
251
|
+
nil, nil, @credentials.to_p, ts.to_p))
|
252
|
+
raise "Error acquire credentials: #{result}" unless result.ok?
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|