1 module sspi;
2 
3 version(Windows):
4 
5 import core.sys.windows.ntsecpkg;
6 public import core.sys.windows.sspi;
7 import core.sys.windows.windef:DWORD;
8 public import sspi.defines;
9 public import sspi.helpers;
10 
11 enum cbMaxMessage  = 12_000; // from SSPI MS example
12 
13 
14 struct BaseAuth
15 {
16 	SecHandle context;
17 	CredHandle credentials;
18 	uint nextSequenceNumber;
19 	bool isAuthenticated;
20 	string packageName;
21 	TimeStamp credentialsExpiry;
22 	SecPkgInfoW* packageInfo;
23 	enum DefaultSecurityContextFlags = 	IscReq.integrity 	| IscReq.sequenceDetect | IscReq.replayDetect	| IscReq.confidentiality;
24 	IscReq securityContextFlags = DefaultSecurityContextFlags;
25 	uint dataRep;
26 	uint contextAttr;
27 
28 
29 	void dispose()
30 	{
31 		if (this.context != SecHandle.init)
32 			deleteSecurityContext(&this.context);
33 		if (this.credentials != CredHandle.init)
34 			freeCredentialsHandle(&this.credentials);
35 	}
36 
37 	/// Reset everything to an unauthorized state
38 	void reset()
39 	{
40 		this.dispose();
41 		this.context = SecHandle.init;
42 		this.credentials = CredHandle.init;
43 		this.isAuthenticated = false;
44 		this.nextSequenceNumber = 0;
45 		this.packageName = null;
46 		this.credentialsExpiry = TimeStamp.init;
47 		this.packageInfo = null;
48 		this.securityContextFlags = DefaultSecurityContextFlags;
49 		this.dataRep = 0;
50 		this.contextAttr = 0;
51 	}
52 
53 	/// Get the next sequence number for a transmission.  Default implementation is to increment a counter
54 	auto getNextSequenceNumber()
55 	{
56 		auto ret = nextSequenceNumber;
57 		nextSequenceNumber++;
58 		return ret;
59 	}
60 
61 	auto encrypt(string data)
62 	{
63 		import std.conv:to;
64 		import std.typecons:tuple;
65 
66 		// auto packageInfo = context.queryContextAttributes!SecPkgNegInfo(SecPackageAttribute.info);
67 		auto packageSizes= queryContextAttributes!SecPkgContext_Sizes(&context,SecPackageAttribute.sizes);
68 		auto maxSignatureSize = packageSizes.cbMaxSignature;
69 		auto trailerSize = packageSizes.cbSecurityTrailer;
70 
71 		SecBuffer[2] buffers;
72 		SecBufferDesc bufferDesc;
73 
74 		bufferDesc.ulVersion = SECBUFFER_VERSION;
75 		bufferDesc.cBuffers = 2;
76 		bufferDesc.pBuffers = buffers.ptr;
77 
78 		// initial DWORD specifies size of trailer block
79 		ubyte[] outputData;
80 		outputData.length = trailerSize + data.length + DWORD.sizeof;
81 		buffers[0].cbBuffer = trailerSize;
82 		buffers[0].BufferType = SECBUFFER_TOKEN;
83 		buffers[0].pvBuffer = cast(void*)data.ptr + cast(ptrdiff_t) DWORD.sizeof;
84 
85 		buffers[1].cbBuffer = data.length.to!uint;
86 		buffers[1].BufferType = SECBUFFER_DATA;
87 		buffers[1].pvBuffer = cast(void*)data.ptr;
88 
89 		context.encryptMessage(0, bufferDesc, getNextSequenceNumber());
90 		return outputData.idup;
91 	}
92 
93 
94 	/// Decrypt a previously encrypted string, returning the orignal data
95 	auto decrypt(string data, string trailer)
96 	{
97 		import std.conv:to;
98 		import std..string : toStringz,fromStringz;
99 
100 		SecBuffer[2] buffers;
101 		SecBufferDesc bufferDesc;
102 
103 		bufferDesc.ulVersion = SECBUFFER_VERSION;
104 		bufferDesc.cBuffers = 2;
105 		bufferDesc.pBuffers = buffers.ptr;
106 
107 		buffers[0].cbBuffer = trailer.length.to!uint + 1;
108 		buffers[0].BufferType = SECBUFFER_TOKEN;
109 		buffers[0].pvBuffer = cast(void*)trailer.toStringz;
110 
111 		buffers[1].cbBuffer = data.length.to!uint +1; // FIXME - might be null terminated already
112 		buffers[1].BufferType = SECBUFFER_DATA;
113 		buffers[1].pvBuffer = cast(void*)data.toStringz;
114 
115 		auto fQOP = context.decryptMessage(bufferDesc, getNextSequenceNumber());
116 		return (cast(char*)buffers[0].pvBuffer).fromStringz;
117 	}
118 
119 	/// sign a string suitable for transmission, returning the signature.
120 	/// Passing the data and signature to verify will determine if the data is unchanged.
121 	string sign(string data)
122 	{
123 		import std.conv:to;
124 		import std..string : toStringz,fromStringz;
125 
126 		auto packageSizes = queryContextAttributes!SecPkgContext_Sizes(&context,SecPackageAttribute.sizes);
127 		auto trailerSize = packageSizes.cbMaxSignature;
128 
129 		SecBuffer[2] buffers;
130 		SecBufferDesc bufferDesc;
131 
132 		bufferDesc.ulVersion = SECBUFFER_VERSION;
133 		bufferDesc.cBuffers = 2;
134 		bufferDesc.pBuffers = buffers.ptr;
135 
136 		buffers[0].cbBuffer = data.length.to!uint +1; // FIXME - might be null terminated already
137 		buffers[0].BufferType = SECBUFFER_DATA;
138 		buffers[0].pvBuffer = cast(void*) data.toStringz;
139 
140 		buffers[1].cbBuffer = trailerSize;
141 		buffers[1].BufferType = SECBUFFER_TOKEN;
142 		context.makeSignature(0,bufferDesc,this.getNextSequenceNumber());
143 		return (cast(char*)buffers[1].cbBuffer).fromStringz.idup;
144 	}
145 
146         /// Verifies data and its signature.  If verification fails, an sspi.error will be raised.
147 	void verifySignature(string data, string sig)
148 	{
149 		import std.conv:to;
150 		import std..string : toStringz,fromStringz;
151 
152 		SecBuffer[2] buffers;
153 		SecBufferDesc bufferDesc;
154 		bufferDesc.ulVersion = SECBUFFER_VERSION;
155 		bufferDesc.cBuffers = 2;
156 		bufferDesc.pBuffers = buffers.ptr;
157 
158 		buffers[0].cbBuffer = data.length.to!uint +1; // FIXME - might be null terminated already
159 		buffers[0].BufferType = SECBUFFER_DATA;
160 		buffers[0].pvBuffer = cast(void*) data.toStringz;
161 
162 		buffers[1].cbBuffer = sig.length.to!uint +1; // FIXME
163 		buffers[1].BufferType = SECBUFFER_TOKEN;
164 		buffers[1].pvBuffer = cast(void*) sig.toStringz;
165 
166 		context.verifySignature(bufferDesc, this.getNextSequenceNumber());
167 	}
168 
169 	auto acquireCredentialsHandle(string userName, string packageName, CredentialDirection credentialDirection)
170 	{
171 		import std.exception : enforce;
172 		import std.conv:to;
173 		import std.typecons:tuple;
174 		import std.utf:toUTF16z;
175 
176 		TimeStamp lifetime;
177 		auto direction = (credentialDirection == CredentialDirection.inbound) ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND;
178 		SecurityStatus securityStatus = cast(SecurityStatus) AcquireCredentialsHandleW(
179 									(userName is null) ? null : cast(wchar*) userName.toUTF16z,
180 									cast(wchar*) packageName.toUTF16z,
181 									direction,
182 									null,
183 									null,
184 									null,
185 									null,
186 									&this.credentials,
187 									&lifetime);
188 		enforce(securityStatus.secSuccess, securityStatus.to!string);
189 		this.credentialsExpiry = lifetime;
190 		return tuple(lifetime, credentials);
191 	}
192 }
193 
194 
195 enum CredentialDirection
196 {
197 	inbound,
198 	outbound,
199 }
200 
201 
202 struct ClientAuth
203 {
204 	BaseAuth base;
205 	alias base this;
206 	alias DefaultSecurityContextFlags = BaseAuth.DefaultSecurityContextFlags;
207 
208 	string targetSecurityContextProvider;
209 
210 	this(string packageName, string clientName, 
211 		string targetSecurityContextProvider = null,
212 		IscReq securityContextFlags = DefaultSecurityContextFlags, uint dataRep = SECURITY_NETWORK_DREP)
213 	{
214 		import std.stdio;
215 		import std..string:fromStringz;
216 
217 		this.securityContextFlags = securityContextFlags;
218 		this.dataRep = dataRep;
219 		this.targetSecurityContextProvider = targetSecurityContextProvider;
220 		this.packageInfo = querySecurityPackageInfo(packageName);
221 		auto result = acquireCredentialsHandle(clientName,packageName,CredentialDirection.outbound);
222 		this.credentialsExpiry = result[0];
223 		this.base.credentials = result[1];
224 		this.packageName = packageName;
225 	}
226 
227 
228 	/// Perform *one* step of the server authentication process.
229 	auto authorize(const(ubyte)[] data=[])
230 	{
231 		import std.stdio;
232 		import std.conv:to;
233 		import std.typecons:tuple;
234 
235 		SecurityContextResult result;
236 		bool isFirstStage = (data.length == 0);
237 		ubyte[] retBuf;
238 		retBuf.length = cbMaxMessage;
239 		SecBuffer[1] buffersIn, buffersOut;
240 		SecBufferDesc bufferDescIn, bufferDescOut;
241 		DWORD cbOut = isFirstStage?retBuf.length.to!int : 0;
242 
243 		bufferDescOut.ulVersion = SECBUFFER_VERSION;
244 		bufferDescOut.cBuffers = 1;
245 		bufferDescOut.pBuffers = buffersOut.ptr;
246 
247 		buffersOut[0].cbBuffer = cbOut;
248 		buffersOut[0].BufferType = SECBUFFER_TOKEN;
249 		buffersOut[0].pvBuffer = retBuf.ptr;
250 		auto securityContextModFlags = cast(uint)securityContextFlags | ISC_REQ_ALLOCATE_MEMORY;
251 
252 		if(!isFirstStage)
253 		{
254 			bufferDescIn.ulVersion = SECBUFFER_VERSION;
255 			bufferDescIn.cBuffers = 1;
256 			bufferDescIn.pBuffers = buffersIn.ptr;
257 
258 			buffersIn[0].cbBuffer = data.length.to!int;
259 			buffersIn[0].BufferType = SECBUFFER_TOKEN;
260 			buffersIn[0].pvBuffer = cast(void*) data.ptr;
261 
262 			buffersOut[0].pvBuffer = null;
263 			buffersOut[0].cbBuffer   = 0;
264 			result = initializeSecurityContext(credentials, &context, packageName, securityContextModFlags, 0U, cast(uint)dataRep,bufferDescIn,bufferDescOut);
265 			bufferDescOut = result.outputBufferDesc;
266 		}
267 		else
268 		{
269 			buffersOut[0].pvBuffer = null;
270 			buffersOut[0].cbBuffer   = 0;
271 			result = initializeSecurityContextInitial(credentials, &context, this.targetSecurityContextProvider, securityContextModFlags, 0UL, dataRep,bufferDescOut);
272 			bufferDescOut=result.outputBufferDesc;
273 			//auto result2 = queryContextAttributes!SecPkgInfoW(&context,SecPackageAttribute.negotiationInfo);
274 		}
275 
276 		version (SSPI_Leak) {}
277 		else
278 			scope (exit)
279 				if (result.outputBufferDesc.pBuffers !is null)
280 					FreeContextBuffer(cast(void*) result.outputBufferDesc.pBuffers);
281 
282 		this.contextAttr = result.contextAttribute;
283 		this.credentialsExpiry = result.expiry;
284 		auto securityStatus = result.securityStatus;
285 
286 		//auto result2 = context.queryContextAttributes!SecPkgInfoW(SecPackageAttribute.negotiationInfo);
287 		if (securityStatus == SecurityStatus.completeNeeded || securityStatus == SecurityStatus.completeAndContinue)
288 		{
289 			version(Trace) writefln("securityStatus: %s, completeneeded %s completecontinue%s",securityStatus,cast(long)SecurityStatus.completeNeeded,cast(long)SecurityStatus.completeAndContinue);
290 			completeAuthToken(&context,bufferDescOut);
291 		}
292 		this.isAuthenticated = (securityStatus ==0);
293 		auto returnBuffersOut = cast(SecBuffer*)(bufferDescOut.pBuffers);
294 		version(Trace)
295 		{
296 			stderr.writefln("authenticated = %s; %s byte token",securityStatus,returnBuffersOut.cbBuffer);
297 			stderr.writeln(this.isAuthenticated,securityStatus,this.credentialsExpiry);
298 		}
299 		return tuple(securityStatus, ((cast(ubyte*)(returnBuffersOut.pvBuffer)))[0 .. returnBuffersOut.cbBuffer].idup);
300 
301 	}
302 }
303 
304 /// Manages the server side of an SSPI authentication handshake
305 struct ServerAuth
306 {
307 	BaseAuth base;
308 	alias base this;
309 	alias DefaultSecurityContextFlags = BaseAuth.DefaultSecurityContextFlags;
310 
311 	bool isAuthenticated = false;
312 
313 	this(string packageName, 
314 		IscReq securityContextFlags = DefaultSecurityContextFlags, uint dataRep = SECURITY_NETWORK_DREP)
315 	{
316 		this.securityContextFlags = securityContextFlags;
317 		this.dataRep = dataRep;
318 		this.packageName = packageName;
319 		this.packageInfo = querySecurityPackageInfo(packageName);
320 	}
321 
322 	void setup()
323 	{
324 		import std.format : format;
325 		import std.exception : enforce;
326 
327 		packageInfo = querySecurityPackageInfo(packageName);
328 		auto result = acquireCredentialsHandle(null,packageName, CredentialDirection.inbound);
329 		this.credentialsExpiry = result[0];
330 	}
331 
332 	/// Perform *one* step of the server authentication process.
333 	auto authorize(const(ubyte)[] data = [])
334 	{
335 		import std.stdio;
336 		import std.typecons:tuple;
337 
338 		bool isFirstStage = (data.length == 0);
339 		ubyte[] retBuf;
340 		retBuf.length = isFirstStage ? 0 : cbMaxMessage; // packageInfo.cbMaxMessage;
341 		SecBuffer[1] buffersIn, buffersOut;
342 		SecBufferDesc bufferDescIn, bufferDescOut;
343 
344 		auto contextSizes= queryContextAttributes!SecPkgContext_Sizes(&context, SecPackageAttribute.sizes);
345 		auto negotiationInfo = queryContextAttributes!SecPkgContext_NegotiationInfoW(&context,SecPackageAttribute.negotiationInfo);
346 
347 		bufferDescOut.ulVersion = 0;
348 		bufferDescOut.cBuffers = 1;
349 		bufferDescOut.pBuffers = buffersOut.ptr;
350 
351 		buffersOut[0].cbBuffer = this.packageInfo.cbMaxToken; // CHECKME
352 		buffersOut[0].BufferType = SECBUFFER_TOKEN;
353 		buffersOut[0].pvBuffer = retBuf.ptr;
354 
355 		SecurityContextResult result;
356 
357 		auto securityContextModFlags = cast(uint)securityContextFlags | ISC_REQ_ALLOCATE_MEMORY;
358 
359 		if(!isFirstStage)
360 		{
361 			bufferDescIn.ulVersion = 0;
362 			bufferDescIn.cBuffers = 1;
363 			bufferDescIn.pBuffers = buffersIn.ptr;
364 
365 			buffersIn[0].cbBuffer = this.packageInfo.cbMaxToken;
366 			buffersIn[0].BufferType = SECBUFFER_TOKEN;
367 			buffersIn[0].pvBuffer = cast(void*) data.ptr;
368 			result = acceptSecurityContext(	credentials, &context, &bufferDescIn, securityContextModFlags, dataRep, &context,bufferDescOut);
369 		}
370 		else
371 		{
372 			SecHandle newContext;
373 			result = acceptSecurityContext(	credentials, null, &bufferDescIn, securityContextModFlags, dataRep, &newContext,bufferDescOut);
374 			this.context = newContext;
375 		}
376 
377 		scope(exit)
378 		{
379 			if (result.outputBufferDesc.pBuffers !is null)
380 				FreeContextBuffer(cast(void*)result.outputBufferDesc.pBuffers);
381 		}
382 
383 		this.base.contextAttr = result.contextAttribute;
384 		this.base.credentialsExpiry = result.expiry;
385 		auto securityStatus = result.securityStatus;
386 		bufferDescOut = result.outputBufferDesc;
387 
388 		if (securityStatus == SecurityStatus.completeNeeded || securityStatus == SecurityStatus.completeAndContinue)
389 			completeAuthToken(&context,bufferDescOut);
390 		this.isAuthenticated = (securityStatus ==0);
391 		return tuple(securityStatus, ((cast(ubyte*)(bufferDescOut.pBuffers)))[0 .. bufferDescOut.pBuffers.cbBuffer].idup);
392 	}
393 
394 	string impersonate()
395 	{
396 		impersonateSecurityContext(&context);
397 		return getUserName();
398 	}
399 
400 	void revertImpersonate()
401 	{
402 		revertSecurityContext(&context);
403 	}
404 }