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) FreeContextBuffer(cast(void*)result.outputBufferDesc.pBuffers);
251 		this.contextAttr = result.contextAttribute;
252 		this.credentialsExpiry = result.expiry;
253 		auto securityStatus = result.securityStatus;
254 
255 		//auto result2 = context.queryContextAttributes!SecPkgInfoW(SecPackageAttribute.negotiationInfo);
256 		if (securityStatus == SecurityStatus.completeNeeded || securityStatus == SecurityStatus.completeAndContinue)
257 		{
258 			version(Trace) writefln("securityStatus: %s, completeneeded %s completecontinue%s",securityStatus,cast(long)SecurityStatus.completeNeeded,cast(long)SecurityStatus.completeAndContinue);
259 			completeAuthToken(&context,bufferDescOut);
260 		}
261 		this.isAuthenticated = (securityStatus ==0);
262 		auto returnBuffersOut = cast(SecBuffer*)(bufferDescOut.pBuffers);
263 		version(Trace)
264 		{
265 			stderr.writefln("authenticated = %s; %s byte token",securityStatus,returnBuffersOut.cbBuffer);
266 			stderr.writeln(this.isAuthenticated,securityStatus,this.credentialsExpiry);
267 		}
268 		return tuple(securityStatus, ((cast(ubyte*)(returnBuffersOut.pvBuffer)))[0 .. returnBuffersOut.cbBuffer].idup);
269 
270 	}
271 }
272 
273 version(None):
274 /// Manages the server side of an SSPI authentication handshake
275 struct ServerAuth
276 {
277 	BaseAuth base;
278 	alias base this;
279 	string packageName;
280 	ulong datarap;
281 	IscReq securityContextFlags;
282 	void* packageInfo;
283 	DateTime credentialsExpiry;
284 	bool isAuthenticated;
285 
286 	this(string packageName, securityContextFlags = 0UL, ulong datarep = SECURITY_NETWORK_DREP)
287 	{
288 		this.packageName = packageName;
289 		this.datarep = datarep;
290 
291 		this.securityContextFlags = (securityContextFlags==0) ? 
292 	    		(ASC_REQ_INTEGRITY|ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT|ASC_REQ_CONFIDENTIALITY) :
293 			securityContextFlags;
294 		base = BaseAuth();
295 	}
296 
297 	void setup()
298 	{
299 
300 		packageInfo = QuerySecurityPackageInfo(packageName);
301 		this.credentialsExpiry = AcquireCredentialsHandle(packageName, this.packageInfo.Name, SECPKG_CRED_INBOUND, null, null);
302 	}
303 
304 	/// Perform *one* step of the server authentication process.
305 	auto authorize(string data = null)
306 	{
307 		import std.stdio;
308 		SecurityStatus result;
309 		bool isFirstStage = (data.length == 0);
310 		ubyte[] retBuf;
311 		retBuf.length = isFirstStage ? 0 : cbMaxMessage; // packageInfo.cbMaxMessage;
312 		SecBuffer[1] buffersIn, buffersOut;
313 		SecBufferDesc bufferDescIn, bufferDescOut;
314 		DWORD cbOut = 0;
315 
316 		bufferDescOut.ulVersion = SECBUFFER_VERSION;
317 		bufferDescOut.cBuffers = 1;
318 		bufferDescOut.pBuffers = buffersOut.ptr;
319 
320 		buffersOut[0].cbBuffer = cbOut;
321 		buffersOut[0].BufferType = SECBUFFER_TOKEN;
322 		buffersOut[0].pvBuffer = retBuf.ptr;
323 
324 		bufferDescOut.cbBuffer = this.packageInfo.cbMaxToken;
325 		bufferDescOut.BufferType = SECBUFFER_TOKEN;
326 
327 		if(!isFirstStage)
328 		{
329 			bufferDescIn.ulVersion = SECBUFFER_VERSION;
330 			bufferDescIn.cBuffers = 1;
331 			bufferDescIn.pBuffers = buffersIn.ptr;
332 
333 			buffersIn[0].cbBuffer = this.packageInfo.cbMaxToken;
334 			buffersIn[0].BufferType = SECBUFFER_TOKEN;
335 			buffersIn[0].pvBuffer = data.toStringz;
336 			result = initializeSecurityContext(	credentials, context, buffersIn, securityContextFlags, dataRep, bufferDescOut);
337 		}
338 		else
339 		{
340 			//result = initializeSecurityContextInitial(	credentials,  null, 	securityContextFlags, dataRep, bufferDescOut);
341 		}
342 
343 		this.contextAttr = result[0];
344 		this.contextExpiry = result[1];
345 		auto securityStatus = result[2];
346 		this.context = result[3];
347 
348 		if (securityStatus & SEC_I_COMPLETE_NEEDED || securityStatus & SEC_I_COMPLETE_AND_CONTINUE)
349 			context.completeAuthToken(bufferDescOut);
350 		this.isAuthenticated = (securityStatus ==0);
351 		return tuple(securityStatus, buffers[0]);
352 
353 	}
354 }