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