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= context.queryContextAttributes!SecPkgContext_Sizes(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(ptr_diff_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 = context.queryContextAttributes!SecPkgContext_Sizes(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 |
161 						IscReq.replayDetect	| IscReq.confidentiality;
162 
163 	IscReq securityContextFlags;
164 	long dataRep;
165 	string targetSecurityContextProvider;
166 	SecPkgInfoW* packageInfo;
167 	TimeStamp credentialsExpiry;
168 	string packageName;
169 	uint contextAttr;
170 
171 	this(string packageName, string clientName, 
172 		string targetSecurityContextProvider = null,
173 		IscReq securityContextFlags = DefaultSecurityContextFlags, long dataRep = SECURITY_NETWORK_DREP)
174 	{
175 		this.securityContextFlags = securityContextFlags;
176 		this.dataRep = dataRep;
177 		this.targetSecurityContextProvider = targetSecurityContextProvider;
178 		this.packageInfo = querySecurityPackageInfo(packageName);
179 		auto result = acquireCredentialsHandle(packageName);  // clientName,packageInfo.Name, SECPKG_CRED_OUTBOUND,
180 		this.credentialsExpiry = result[0];
181 		this.base.credentials = result[1];
182 		this.packageName = packageName;
183 	}
184 
185 
186 	auto acquireCredentialsHandle(string packageName)
187 	{
188 		TimeStamp lifetime;
189 		SecurityStatus securityStatus = cast(SecurityStatus) AcquireCredentialsHandleW(
190 									null,
191 									cast(wchar*) packageName.toUTF16z,
192 									SECPKG_CRED_OUTBOUND,
193 									null,
194 									null,
195 									null,
196 									null,
197 									&this.base.credentials,
198 									&lifetime);
199 		enforce(securityStatus.secSuccess, securityStatus.to!string);
200 		this.credentialsExpiry = lifetime;
201 		return tuple(lifetime, base.credentials);
202 	}
203 	/// Perform *one* step of the server authentication process.
204 	auto authorize(string data = null)
205 	{
206 		SecurityContextResult result;
207 		bool isFirstStage = (data.length == 0);
208 		ubyte[] retBuf;
209 		auto packageSizes= context.queryContextAttributes!SecPkgContext_Sizes(SecPackageAttribute.sizes);
210 		retBuf.length = isFirstStage ? 0 : packageSizes.cbMaxMessage;
211 		SecBuffer[1] buffersIn, buffersOut;
212 		SecBufferDesc bufferDescIn, bufferDescOut;
213 		DWORD cbOut = 0;
214 
215 		bufferDescOut.ulVersion = SECBUFFER_VERSION;
216 		bufferDescOut.cBuffers = 1;
217 		bufferDescOut.pBuffers = buffersOut.ptr;
218 
219 		buffersOut[0].cbBuffer = cbOut;
220 		buffersOut[0].BufferType = SECBUFFER_TOKEN;
221 		buffersOut[0].pvBuffer = retBuf.ptr;
222 
223 		if(!isFirstStage)
224 		{
225 			bufferDescIn.ulVersion = SECBUFFER_VERSION;
226 			bufferDescIn.cBuffers = 1;
227 			bufferDescIn.pBuffers = buffersIn.ptr;
228 
229 			buffersIn[0].cbBuffer = packageSizes.cbMaxToken;
230 			buffersIn[0].BufferType = SECBUFFER_TOKEN;
231 			buffersIn[0].pvBuffer = cast(void*) data.toStringz;
232 			result = initializeSecurityContext(credentials, context, packageName, cast(uint)securityContextFlags, 0U, cast(uint)dataRep,bufferDescIn,bufferDescOut);
233 		}
234 		else
235 		{
236 			result = initializeSecurityContext(credentials, context, packageName, cast(uint)securityContextFlags, 0, cast(uint)dataRep,null,bufferDescOut);
237 		}
238 
239 		this.contextAttr = result.contextAttribute;
240 		this.credentialsExpiry = result.expiry;
241 		auto securityStatus = result.securityStatus;
242 		this.context = result.newContext;
243 
244 		if (securityStatus & SecurityStatus.completeNeeded || securityStatus & SecurityStatus.completeAndContinue)
245 			context.completeAuthToken(bufferDescOut);
246 		this.isAuthenticated = (securityStatus ==0);
247 		return tuple(securityStatus, buffersOut[0]);
248 
249 	}
250 }
251 
252 version(None):
253 /// Manages the server side of an SSPI authentication handshake
254 struct ServerAuth
255 {
256 	BaseAuth base;
257 	alias base this;
258 	string packageName;
259 	ulong datarap;
260 	IscReq securityContextFlags;
261 	void* packageInfo;
262 	DateTime credentialsExpiry;
263 	bool isAuthenticated;
264 
265 	this(string packageName, securityContextFlags = 0UL, ulong datarep = SECURITY_NETWORK_DREP)
266 	{
267 		this.packageName = packageName;
268 		this.datarep = datarep;
269 
270 		this.securityContextFlags = (securityContextFlags==0) ? 
271 	    		(ASC_REQ_INTEGRITY|ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT|ASC_REQ_CONFIDENTIALITY) :
272 			securityContextFlags;
273 		base = BaseAuth();
274 	}
275 
276 	void setup()
277 	{
278 
279 		packageInfo = QuerySecurityPackageInfo(packageName);
280 		this.credentialsExpiry = AcquireCredentialsHandle(packageName, this.packageInfo.Name, SECPKG_CRED_INBOUND, null, null);
281 	}
282 
283 	/// Perform *one* step of the server authentication process.
284 	auto authorize(string data = null)
285 	{
286 		SecurityStatus result;
287 		bool isFirstStage = (data.length == 0);
288 		ubyte[] retBuf;
289 		retBuf.length = isFirstStage ? 0 : cbMaxMessage; // packageInfo.cbMaxMessage;
290 		SecBuffer[1] buffersIn, buffersOut;
291 		SecBufferDesc bufferDescIn, bufferDescOut;
292 		DWORD cbOut = 0;
293 
294 		bufferDescOut.ulVersion = SECBUFFER_VERSION;
295 		bufferDescOut.cBuffers = 1;
296 		bufferDescOut.pBuffers = buffersOut.ptr;
297 
298 		buffersOut[0].cbBuffer = cbOut;
299 		buffersOut[0].BufferType = SECBUFFER_TOKEN;
300 		buffersOut[0].pvBuffer = retBuf.ptr;
301 
302 		bufferDescOut.cbBuffer = this.packageInfo.cbMaxToken;
303 		bufferDescOut.BufferType = SECBUFFER_TOKEN;
304 
305 		if(!isFirstStage)
306 		{
307 			bufferDescIn.ulVersion = SECBUFFER_VERSION;
308 			bufferDescIn.cBuffers = 1;
309 			bufferDescIn.pBuffers = buffersIn.ptr;
310 
311 			buffersIn[0].cbBuffer = this.packageInfo.cbMaxToken;
312 			buffersIn[0].BufferType = SECBUFFER_TOKEN;
313 			buffersIn[0].pvBuffer = data.toStringz;
314 			result = initializeSecurityContext(	credentials, context, buffersIn, securityContextFlags, dataRep, bufferDescOut);
315 		}
316 		else
317 		{
318 			result = initializeSecurityContext(	credentials, context, null, 	securityContextFlags, dataRep, bufferDescOut);
319 		}
320 
321 		this.contextAttr = result[0];
322 		this.contextExpiry = result[1];
323 		auto securityStatus = result[2];
324 		this.context = result[3];
325 
326 		if (securityStatus & SEC_I_COMPLETE_NEEDED || securityStatus & SEC_I_COMPLETE_AND_CONTINUE)
327 			context.completeAuthToken(bufferDescOut);
328 		this.isAuthenticated = (securityStatus ==0);
329 		return tuple(securityStatus, buffers[0]);
330 
331 	}
332 }